Implementing Incremental View Maintenance

Started by Yugo Nagataabout 7 years ago227 messages
#1Yugo Nagata
nagata@sraoss.co.jp

Hi,

I would like to implement Incremental View Maintenance (IVM) on PostgreSQL.
IVM is a technique to maintain materialized views which computes and applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018 [1]https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/.
Our implementation uses row OIDs to compute deltas for materialized views.
The basic idea is that if we have information about which rows in base tables
are contributing to generate a certain row in a matview then we can identify
the affected rows when a base table is updated. This is based on an idea of
Dr. Masunaga [2]https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&page_id=13&block_id=8&item_id=191254&item_no=1 (Japanese only) who is a member of our group and inspired from ID-based
approach[3]https://dl.acm.org/citation.cfm?id=2750546.

In our implementation, the mapping of the row OIDs of the materialized view
and the base tables are stored in "OID map". When a base relation is modified,
AFTER trigger is executed and the delta is recorded in delta tables using
the transition table feature. The accual udpate of the matview is triggerd
by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS will
be removed since PG12, so OIDs are no longer available. Besides this, it would
be hard to implement this since it needs many changes of executor nodes to
collect base tables's OIDs during execuing a query. Also, the cost of maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without relying on OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4]/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com on incremental matview
maintenance. In this discussion, Kevin proposed to use counting algorithm [5]https://dl.acm.org/citation.cfm?id=170066
to handle projection views (using DISTNICT) properly. This algorithm need an
additional system column, count_t, in materialized views and delta tables of
base tables.

However, the discussion about IVM is now stoped, so we would like to restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables, but using
logical decoding might be better from the point of performance of base table
modification.

If we can represent a change of UPDATE on a base table as query-like rather than
OLD and NEW, it may be possible to update the materialized view directly instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes on base
tables are represented as deltas on this, like "R <- R + dR", and the delta on
the materialized view is computed using base table deltas based on "change
propagation equations". For implementation, we have to derive the equation from
the view definition query (Query tree, or Plan tree?) and describe this as SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection, projection,
join, aggregation, union, difference, intersection, etc. If we can prepare a
module for each operation, it makes IVM extensable, so we can start a simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify which row
in the matview is corresponding to a row in the delta. A naive method is matching
by using all columns in a tuple, but clearly this is unefficient. If thematerialized
view has unique index, we can use this. Maybe, we have to force materialized views
to have all primary key colums in their base tables. In our PoC implementation, we
used OID to identify rows, but this will be no longer available as said above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate (eager) or deferred.

In eager maintenance, the materialized view is updated in the same transaction
where the base table is updated. In deferred maintenance, this is done after the
transaction is commited, for example, when view is accessed, as a response to user
request, etc.

In the previous discussion[4]/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com, it is planned to start from "eager" approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command to perform IVM.
I am not sure which is better as a start point, but I begin to think that the eager
approach may be more simple since we don't have to maintain base table changes in other
past transactions.

In the eager maintenance approache, we have to consider a race condition where two
different transactions change base tables simultaneously as discussed in [4]/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com.

[1]: https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/
[2]: https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1 (Japanese only)
[3]: https://dl.acm.org/citation.cfm?id=2750546
[4]: /messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com
[5]: https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

#2denty
denty@QQdd.eu
In reply to: Yugo Nagata (#1)
Re: Implementing Incremental View Maintenance

Hi Yugo.

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.

Great. :-)

I think it would address an important gap in PostgreSQL’s feature set.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes on
base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on "change
propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe this as
SQL
query to compulte delta to be applied to the materialized view.

We had a similar discussion in this thread
/messages/by-id/FC784A9F-F599-4DCC-A45D-DBF6FA582D30@QQdd.eu,
and I’m very much in agreement that the "change propagation equations”
approach can solve for a very substantial subset of common MV use cases.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

Such a decomposition also allows ’stacking’, allowing complex MV definitions
to be attacked even with only a small handful of modules.

I did a bit of an experiment to see if "change propagation equations” could
be computed directly from the MV’s pg_node_tree representation in the
catalog in PlPgSQL. I found that pg_node_trees are not particularly friendly
to manipulation in PlPgSQL. Even with a more friendly-to-PlPgSQL
representation (I played with JSONB), then the next problem is making sense
of the structures, and unfortunately amongst the many plan/path/tree utility
functions in the code base, I figured only a very few could be sensibly
exposed to PlPgSQL. Ultimately, although I’m still attracted to the idea,
and I think it could be made to work, native code is the way to go at least
for now.

4. When to maintain materialized views

[...]

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command
to perform IVM.
I am not sure which is better as a start point, but I begin to think that
the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

Certainly the eager approach allows progress to be made with less
infrastructure.

I am concerned that the eager approach only addresses a subset of the MV use
case space, though. For example, if we presume that an MV is present because
the underlying direct query would be non-performant, then we have to at
least question whether applying the delta-update would also be detrimental
to some use cases.

In the eager maintenance approache, we have to consider a race condition
where two
different transactions change base tables simultaneously as discussed in
[4]: .

I wonder if that nudges towards a logged approach. If the race is due to
fact of JOIN-worthy tuples been made visible after a COMMIT, but not before,
then does it not follow that the eager approach has to fire some kind of
reconciliation work at COMMIT time? That seems to imply a persistent queue
of some kind, since we can’t assume transactions to be so small to be able
to hold the queue in memory.

Hmm. I hadn’t really thought about that particular corner case. I guess a
‘catch' could be simply be to detect such a concurrent update and demote the
refresh approach by marking the MV stale awaiting a full refresh.

denty.

--
Sent from: http://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

#3Adam Brusselback
adambrusselback@gmail.com
In reply to: denty (#2)
Re: Implementing Incremental View Maintenance

Hi all, just wanted to say I am very happy to see progress made on this,
my codebase has multiple "materialized tables" which are maintained with
statement triggers (transition tables) and custom functions. They are ugly
and a pain to maintain, but they work because I have no other
solution...for now at least.

I am concerned that the eager approach only addresses a subset of the MV use

case space, though. For example, if we presume that an MV is present
because
the underlying direct query would be non-performant, then we have to at
least question whether applying the delta-update would also be detrimental
to some use cases.

I will say that in my case, as long as my reads of the materialized view
are always consistent with the underlying data, that's what's important. I
don't mind if it's eager, or lazy (as long as lazy still means it will
refresh prior to reading).

#4Nguyễn Trần Quốc Vinh
ntquocvinh@gmail.com
In reply to: Adam Brusselback (#3)
Re: Implementing Incremental View Maintenance

Dear all,

We have some result on incremental update for MVs. We generate triggers on
C to do the incremental maintenance. We posted the code to github about 1
year ago, but unfortunately i posted a not-right ctrigger.h header. The
mistake was exposed to me when a person could not compile the generated
triggers and reported to me. And now i re-posted with the right ctrigger.h
file.

You can find the codes of the generator here:
https://github.com/ntqvinh/PgMvIncrementalUpdate/commits/master. You can
find how did we do here:
https://link.springer.com/article/10.1134/S0361768816050066. The paper is
about generating of codes in pl/pgsql. Anyway i see it is useful for
reading the codes. I don't know if i can share the paper or not so that i
don't publish anywhere else. The text about how to generate triggers in C
was published with open-access but unfortunately, it is in Vietnamese.

We are happy if the codes are useful for someone.

Thank you and best regards,

NTQ Vinh

TS. Nguyễn Trần Quốc Vinh
-----------------------------------------------
Chủ nhiệm khoa Tin học
Trường ĐH Sư phạm - ĐH Đà Nẵng
------------------------------------------------
Nguyen Tran Quoc Vinh, PhD
Dean
Faculty of Information Technology
Danang University of Education
Website: http://it.ued.udn.vn; http://www.ued.vn <http://www.ued.udn.vn/&gt;;
http://www.ued.udn.vn
SCV: http://scv.ued.vn/~ntquocvinh <http://scv.ued.udn.vn/~ntquocvinh&gt;
Phone: (+84) 511.6-512-586
Mobile: (+84) 914.78-08-98

On Mon, Dec 31, 2018 at 11:20 PM Adam Brusselback <adambrusselback@gmail.com>
wrote:

Show quoted text

Hi all, just wanted to say I am very happy to see progress made on this,
my codebase has multiple "materialized tables" which are maintained with
statement triggers (transition tables) and custom functions. They are ugly
and a pain to maintain, but they work because I have no other
solution...for now at least.

I am concerned that the eager approach only addresses a subset of the MV

use
case space, though. For example, if we presume that an MV is present
because
the underlying direct query would be non-performant, then we have to at
least question whether applying the delta-update would also be detrimental
to some use cases.

I will say that in my case, as long as my reads of the materialized view
are always consistent with the underlying data, that's what's important. I
don't mind if it's eager, or lazy (as long as lazy still means it will
refresh prior to reading).

#5Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Adam Brusselback (#3)
Re: Implementing Incremental View Maintenance

Hi all, just wanted to say I am very happy to see progress made on this,
my codebase has multiple "materialized tables" which are maintained with
statement triggers (transition tables) and custom functions. They are ugly
and a pain to maintain, but they work because I have no other
solution...for now at least.

I am concerned that the eager approach only addresses a subset of the MV use

case space, though. For example, if we presume that an MV is present
because
the underlying direct query would be non-performant, then we have to at
least question whether applying the delta-update would also be detrimental
to some use cases.

I will say that in my case, as long as my reads of the materialized view
are always consistent with the underlying data, that's what's important. I
don't mind if it's eager, or lazy (as long as lazy still means it will
refresh prior to reading).

Assuming that we want to implement IVM incrementally (that means, for
example, we implement DELETE for IVM in PostgreSQL XX, then INSERT for
IVM for PostgreSQL XX+1... etc.), I think it's hard to do it with an
eager approach if want to MV is always consistent with base tables.

On the other hand, a lazy approach allows to implement IVM
incrementally because we could always let full MV build from scratch
if operations on MV include queries we do not support.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#6Nguyễn Trần Quốc Vinh
ntquocvinh@gmail.com
In reply to: Tatsuo Ishii (#5)
Re: Implementing Incremental View Maintenance

Dear All,

The tool analyzes the input query and then generates triggers (trigger
functions and pl/pgsql scripts as well) on all manipulating events
(insert/updates/delete) for all underlying base tables. The triggers do
incremental updates to the table that contains the query result (MV). You
can build the tool, then see the provided example and try the tool. It is
for synchronous maintenance. It was hard tested but you can use it with
your own risk.

For Asynchronous maintenance, we generate 1) triggers on all manipulating
events on base tables to collect all the data changes and save to the
'special' tables; then 2) the tool to do incremental updates of MVs.

Best regards,

Vinh

TS. Nguyễn Trần Quốc Vinh
-----------------------------------------------
Chủ nhiệm khoa Tin học
Trường ĐH Sư phạm - ĐH Đà Nẵng
------------------------------------------------
Nguyen Tran Quoc Vinh, PhD
Dean
Faculty of Information Technology
Danang University of Education
Website: http://it.ued.udn.vn; http://www.ued.vn <http://www.ued.udn.vn/&gt;;
http://www.ued.udn.vn
SCV: http://scv.ued.vn/~ntquocvinh <http://scv.ued.udn.vn/~ntquocvinh&gt;
Phone: (+84) 511.6-512-586
Mobile: (+84) 914.78-08-98

On Mon, Jan 7, 2019 at 9:00 AM Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

Show quoted text

Hi all, just wanted to say I am very happy to see progress made on this,
my codebase has multiple "materialized tables" which are maintained with
statement triggers (transition tables) and custom functions. They are

ugly

and a pain to maintain, but they work because I have no other
solution...for now at least.

I am concerned that the eager approach only addresses a subset of the MV

use

case space, though. For example, if we presume that an MV is present
because
the underlying direct query would be non-performant, then we have to at
least question whether applying the delta-update would also be

detrimental

to some use cases.

I will say that in my case, as long as my reads of the materialized view
are always consistent with the underlying data, that's what's

important. I

don't mind if it's eager, or lazy (as long as lazy still means it will
refresh prior to reading).

Assuming that we want to implement IVM incrementally (that means, for
example, we implement DELETE for IVM in PostgreSQL XX, then INSERT for
IVM for PostgreSQL XX+1... etc.), I think it's hard to do it with an
eager approach if want to MV is always consistent with base tables.

On the other hand, a lazy approach allows to implement IVM
incrementally because we could always let full MV build from scratch
if operations on MV include queries we do not support.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#7Mitar
mmitar@gmail.com
In reply to: Yugo Nagata (#1)
Re: Implementing Incremental View Maintenance

Hi!

On Thu, Dec 27, 2018 at 4:57 AM Yugo Nagata <nagata@sraoss.co.jp> wrote:

I would like to implement Incremental View Maintenance (IVM) on PostgreSQL.
IVM is a technique to maintain materialized views which computes and applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

That sounds great! I am interested in this topic because I am
interested in reactive/live queries and support for them in
PostgreSQL. [1]/messages/by-id/CAKLmikP+PPB49z8rEEvRjFOD0D2DV72KdqYN7s9fjh9sM_32ZA@mail.gmail.com

In that context, the problem is very similar: based on some state of
query results and updated source tables, determine what should be new
updates to send to the client describing changes to the query results.
So after computing those incremental changes, instead of applying them
to materialized view I would send them to the client. One could see
materialized views only type of consumers of such information about
incremental change.

So I would like to ask if whatever is done in this setting is done in
a way that one could also outside of the context of materialized view.
Not sure what would API be thought.

From the perspective of reactive/live queries, this package [2]https://github.com/nothingisdead/pg-live-query is
interesting. To my understanding, it adds to all base tables two
columns, one for unique ID and one for revision of the row. And then
rewrites queries so that this information is passed all the way to
query results. In this way it can then determine mapping between
inputs and outputs. I am not sure if it then does incremental update
or just uses that to determine if view is invalidated. Not sure if
there is anything about such approach in literature. Or why both index
and revision columns are needed.

For these reasons, we started to think to implement IVM without relying on OIDs
and made a bit more surveys.

I also do not see much difference between asking users to have primary
key on base tables or asking them to have OIDs. Why do you think that
a requirement for primary keys is a hard one? I think we should first
focus on having IVM with base tables with primary keys. Maybe then
later on we could improve on that and make it also work without.

To me personally, having unique index on source tables and also on
materialized view is a reasonable restriction for this feature.
Especially for initial versions of it.

However, the discussion about IVM is now stoped, so we would like to restart and
progress this.

What would be next steps in your view to move this further?

If we can represent a change of UPDATE on a base table as query-like rather than
OLD and NEW, it may be possible to update the materialized view directly instead
of performing delete & insert.

Why do you need OLD and NEW? Don't you need just NEW and a list of
columns which changed from those in NEW? I use such diffing query [4]https://github.com/tozd/node-reactive-postgres/blob/eeda4f28d096b6e552d04c5ea138c258cb5b9389/index.js#L329-L340
to represent changes: first column has a flag telling if the row is
representing insert, update, and remove, the second column tells which
column are being changed in the case of the update, and then the NEW
columns follow.

I think that maybe standardizing structure for representing those
changes would be a good step towards making this modular and reusable.
Because then we can have three parts:

* Recording and storing changes in a standard format.
* A function which given original data, stored changes, computes
updates needed, also in some standard format.
* A function which given original data and updates needed, applies them.

In the previous discussion[4], it is planned to start from "eager" approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command to perform IVM.
I am not sure which is better as a start point, but I begin to think that the eager
approach may be more simple since we don't have to maintain base table changes in other
past transactions.

I think if we split things into three parts as I described above, then
this is just a question of configuration. Or you call all three inside
one trigger to update in "eager" fashion. Or you store computed
updates somewhere and then on demand apply those in "lazy" fashion.

In the eager maintenance approache, we have to consider a race condition where two
different transactions change base tables simultaneously as discussed in [4].

But in the case of "lazy" maintenance there is a mirror problem: what
if later changes to base tables invalidate some previous change to the
materialized view. Imagine that one cell in a base table is first
updated too "foo" and we compute an update for the materialized view
to set it to "foo". And then the same cell is updated to "bar" and we
compute an update for the materialized view again. If we have not
applied any of those updates (because we are "lazy") now the
previously computed update can be discarded. We could still apply
both, but it would not be efficient.

[1]: /messages/by-id/CAKLmikP+PPB49z8rEEvRjFOD0D2DV72KdqYN7s9fjh9sM_32ZA@mail.gmail.com
[2]: https://github.com/nothingisdead/pg-live-query
[3]: https://www.postgresql.org/docs/devel/sql-createtable.html
[4]: https://github.com/tozd/node-reactive-postgres/blob/eeda4f28d096b6e552d04c5ea138c258cb5b9389/index.js#L329-L340

Mitar

--
http://mitar.tnode.com/
https://twitter.com/mitar_m

#8Yugo Nagata
nagata@sraoss.co.jp
In reply to: Nguyễn Trần Quốc Vinh (#4)
Re: Implementing Incremental View Maintenance

On Tue, 1 Jan 2019 14:46:25 +0700
Nguyễn Trần Quốc Vinh <ntquocvinh@gmail.com> wrote:

We have some result on incremental update for MVs. We generate triggers on
C to do the incremental maintenance. We posted the code to github about 1
year ago, but unfortunately i posted a not-right ctrigger.h header. The
mistake was exposed to me when a person could not compile the generated
triggers and reported to me. And now i re-posted with the right ctrigger.h
file.

You can find the codes of the generator here:
https://github.com/ntqvinh/PgMvIncrementalUpdate/commits/master. You can
find how did we do here:
https://link.springer.com/article/10.1134/S0361768816050066. The paper is
about generating of codes in pl/pgsql. Anyway i see it is useful for
reading the codes. I don't know if i can share the paper or not so that i
don't publish anywhere else. The text about how to generate triggers in C
was published with open-access but unfortunately, it is in Vietnamese.

We are happy if the codes are useful for someone.

I have read your paper. It is interesting and great so that the algorithm
is described concretely.

After reading this, I have a few questions about your implementation.
Although I may be able to understand by reading your paper and code carefully,
I would appreciate it if you could answer these.

- It is said there are many limitations on the view definition query.
How does this work when the query is not supported?

- Is it possible to support materialized views that have DISTINCT,
OUTER JOIN, or sub-query in your approach?

- It is said that AVG is splitted to SUM and COUNT. Are these new additional
columns in MV visible for users?

- Does this can handle the race condition discussed in [1]/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com, that is,
if concurrent transactions update different two tables in the join
view definition, is MV updated sucessfully?

[1]: /messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

#9Yugo Nagata
nagata@sraoss.co.jp
In reply to: Mitar (#7)
Re: Implementing Incremental View Maintenance

On Mon, 7 Jan 2019 00:39:00 -0800
Mitar <mmitar@gmail.com> wrote:

That sounds great! I am interested in this topic because I am
interested in reactive/live queries and support for them in
PostgreSQL. [1]

In that context, the problem is very similar: based on some state of
query results and updated source tables, determine what should be new
updates to send to the client describing changes to the query results.
So after computing those incremental changes, instead of applying them
to materialized view I would send them to the client. One could see
materialized views only type of consumers of such information about
incremental change.

So I would like to ask if whatever is done in this setting is done in
a way that one could also outside of the context of materialized view.
Not sure what would API be thought.

I didn't know about reactive/live queries but this seems share a part of
problem with IVM, so we might have common API.

BTW, what is uecase of reactive/live queries? (just curious)

For these reasons, we started to think to implement IVM without relying on OIDs
and made a bit more surveys.

I also do not see much difference between asking users to have primary
key on base tables or asking them to have OIDs. Why do you think that
a requirement for primary keys is a hard one? I think we should first
focus on having IVM with base tables with primary keys. Maybe then
later on we could improve on that and make it also work without.

To me personally, having unique index on source tables and also on
materialized view is a reasonable restriction for this feature.
Especially for initial versions of it.

Initially, I chose to use OIDs for theoretical reason, that is, to handle
"bag-semantics" which allows duplicate rows in tables. However, I agree
that we start from the restriction of having unique index on base tables.

If we can represent a change of UPDATE on a base table as query-like rather than
OLD and NEW, it may be possible to update the materialized view directly instead
of performing delete & insert.

Why do you need OLD and NEW? Don't you need just NEW and a list of
columns which changed from those in NEW? I use such diffing query [4]
to represent changes: first column has a flag telling if the row is
representing insert, update, and remove, the second column tells which
column are being changed in the case of the update, and then the NEW
columns follow.

According the change propagation equation approach, OLD is necessary
to calculate tuples in MV to be deleted or modified. However, if tables
has unique keys, such tuples can be identifeid using the keys, so
OLD may not be needed, at least in eager approach.

In lazy approach, OLD contents of table is useful. For example, with
a join view MV = R * S, when dR is inserted into R and dS is inserted
into S, the delta to be inserted into MV will be

dMV = (R_old * dS) + (dR * S_new)
= (R_old * dS) + (dR * S_old) + (dR * dS)

, hence the old contents of tables R and S are needed.

I think that maybe standardizing structure for representing those
changes would be a good step towards making this modular and reusable.
Because then we can have three parts:

* Recording and storing changes in a standard format.
* A function which given original data, stored changes, computes
updates needed, also in some standard format.
* A function which given original data and updates needed, applies them.

I think if we split things into three parts as I described above, then
this is just a question of configuration. Or you call all three inside
one trigger to update in "eager" fashion. Or you store computed
updates somewhere and then on demand apply those in "lazy" fashion.

I agree that defining the format to represent changes is important. However,
I am not sure both of eager and lazy can be handled in the same manner. I'll
consider about this more.

In the eager maintenance approache, we have to consider a race condition where two
different transactions change base tables simultaneously as discussed in [4].

But in the case of "lazy" maintenance there is a mirror problem: what
if later changes to base tables invalidate some previous change to the
materialized view. Imagine that one cell in a base table is first
updated too "foo" and we compute an update for the materialized view
to set it to "foo". And then the same cell is updated to "bar" and we
compute an update for the materialized view again. If we have not
applied any of those updates (because we are "lazy") now the
previously computed update can be discarded. We could still apply
both, but it would not be efficient.

In our PoC implementation, I handled this situation by removing
old contents from NEW delata table. In your example, when the base
table is updated from "foo" to "bar", the "foo" tuple is removed
from and the "bar" tuple is inserted in NEW delta and the delta
of MV is computed using the final NEW delta.

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

#10Mitar
mmitar@gmail.com
In reply to: Yugo Nagata (#9)
Re: Implementing Incremental View Maintenance

Hi!

On Thu, Jan 31, 2019 at 6:20 AM Yugo Nagata <nagata@sraoss.co.jp> wrote:

BTW, what is uecase of reactive/live queries? (just curious)

It allows syncing the state between client and server. Client can then
have a subset of data and server can push changes as they are
happening to the client. Client can in a reactive manner render that
in the UI to the user. So you can easily create a reactive UI which
always shows up-to-date data without having to poll or something
similar.

How are things progressing? Any news on this topic?

Mitar

--
http://mitar.tnode.com/
https://twitter.com/mitar_m

#11Yugo Nagata
nagata@sraoss.co.jp
In reply to: Yugo Nagata (#1)
Re: Implementing Incremental View Maintenance

On Thu, 27 Dec 2018 21:57:26 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

Hi,

I would like to implement Incremental View Maintenance (IVM) on PostgreSQL.

I am now working on an initial patch for implementing IVM on PostgreSQL.
This enables materialized views to be updated incrementally after one
of their base tables is modified.

At the first patch, I want to start from very simple features.

Firstly, this will handle simple definition views which includes only
selection, projection, and join. Standard aggregations (count, sum, avg,
min, max) are not planned to be implemented in the first patch, but these
are commonly used in materialized views, so I'll implement them later on.
Views which include sub-query, outer-join, CTE, and window functions are also
out of scope of the first patch. Also, views including self-join or views
including other views in their definition is not considered well, either.
I need more investigation on these type of views although I found some papers
explaining how to handle sub-quries and outer-joins.

Next, this will handle materialized views with no duplicates in their
tuples. I am thinking of implementing an algorithm to handle duplicates
called "counting-algorithm" afterward, but I'll start from this
no-duplicates assumption in the first patch for simplicity.

In the first patch, I will implement only "immediate maintenance", that is, materialized views are updated immediately in a transaction where a base
table is modified. On other hand, in "deferred maintenance", materialized
views are updated after the transaction, for example, by the user command
like REFRESH. Although I plan to implement both eventually, I'll start from "immediate" because this seems to need smaller code than "deferred". For
implementing "deferred", it is need to implement a mechanism to maintain logs
for recording changes and an algorithm to compute the delta to be applied to
materialized views are necessary.

I plan to implement the immediate maintenance using AFTER triggers created
automatically on a materialized view's base tables. In AFTER trigger using
transition table features, changes occurs on base tables is recorded ephemeral relations. We can compute the delta to be applied to materialized views by
using these ephemeral relations and the view definition query, then update
the view by applying this delta.

--
Yugo Nagata <nagata@sraoss.co.jp>

#12Greg Stark
stark@mit.edu
In reply to: Yugo Nagata (#11)
Re: Implementing Incremental View Maintenance

On Sun, 31 Mar 2019 at 23:22, Yugo Nagata <nagata@sraoss.co.jp> wrote:

Firstly, this will handle simple definition views which includes only
selection, projection, and join. Standard aggregations (count, sum, avg,
min, max) are not planned to be implemented in the first patch, but these
are commonly used in materialized views, so I'll implement them later on.

It's fine to not have all the features from day 1 of course. But I
just picked up this comment and the followup talking about splitting
AVG into SUM and COUNT and I had a comment. When you do look at
tackling aggregates I don't think you should restrict yourself to
these specific standard aggregations. We have all the necessary
abstractions to handle all aggregations that are feasible, see
https://www.postgresql.org/docs/devel/xaggr.html#XAGGR-MOVING-AGGREGATES

What you need to do -- I think -- is store the "moving aggregate
state" before the final function. Then whenever a row is inserted or
deleted or updated (or whenever another column is updated which causes
the value to row to enter or leave the aggregation) apply either
aggtransfn or aggminvtransfn to the state. I'm not sure if you want to
apply the final function on every update or only lazily either may be
better in some usage.

#13Yugo Nagata
nagata@sraoss.co.jp
In reply to: Yugo Nagata (#11)
1 attachment(s)
Re: Implementing Incremental View Maintenance

On Mon, 1 Apr 2019 12:11:22 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

On Thu, 27 Dec 2018 21:57:26 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

Hi,

I would like to implement Incremental View Maintenance (IVM) on PostgreSQL.

I am now working on an initial patch for implementing IVM on PostgreSQL.
This enables materialized views to be updated incrementally after one
of their base tables is modified.

Attached is a WIP patch of Incremental View Maintenance (IVM).
Major part is written by me, and changes in syntax and pg_class
are Hoshiai-san's work.

Although this is sill a draft patch in work-in-progress, any
suggestions or thoughts would be appreciated.

* What it is

This allows a kind of Immediate Maintenance of materialized views. if a
materialized view is created by CRATE INCREMENTAL MATERIALIZED VIEW command,
the contents of the mateview is updated automatically and incrementally
after base tables are updated. Noted this syntax is just tentative, so it
may be changed.

====== Example 1 ======
postgres=# CREATE INCREMENTAL MATERIALIZED VIEW m AS SELECT * FROM t0;
SELECT 3
postgres=# SELECT * FROM m;
i
---
3
2
1
(3 rows)

postgres=# INSERT INTO t0 VALUES (4);
INSERT 0 1
postgres=# SELECt * FROM m; -- automatically updated
i
---
3
2
1
4
(4 rows)
=============================

This implementation also supports matviews including duplicate tuples or
DISTINCT clause in its view definition query. For example, even if a matview
is defined with DISTINCT to remove duplication of tuples in a base table, this
can perform incremental update of the matview properly. That is, the contents
of the matview doesn't change when exiting tuples are inserted into the base
tables, and a tuple in the matview is deleted only when duplicity of the
corresponding tuple in the base table becomes zero.

This is due to "colunting alogorithm" in which the number of each tuple is
stored in matviews as a special column value.

====== Example 2 ======
postgres=# SELECT * FROM t1;
id | t
----+---
1 | A
2 | B
3 | C
4 | A
(4 rows)

postgres=# CREATE INCREMENTAL MATERIALIZED VIEW m1 AS SELECT t FROM t1;
SELECT 3
postgres=# CREATE INCREMENTAL MATERIALIZED VIEW m2 AS SELECT DISTINCT t FROM t1;
SELECT 3
postgres=# SELECT * FROM m1; -- with duplicity
t
---
A
A
C
B
(4 rows)

postgres=# SELECT * FROM m2;
t
---
A
B
C
(3 rows)

postgres=# INSERT INTO t1 VALUES (5, 'B');
INSERT 0 1
postgres=# DELETE FROM t1 WHERE id IN (1,3); -- delete (1,A),(3,C)
DELETE 2
postgres=# SELECT * FROM m1; -- one A left and one more B
t
---
B
B
A
(3 rows)

postgres=# SELECT * FROM m2; -- only C is removed
t
---
B
A
(2 rows)
=============================

* How it works

1. Creating matview

When a matview is created, AFTER triggers are internally created
on its base tables. When the base tables is modified (INSERT, DELETE,
UPDATE), the matview is updated incrementally in the trigger function.

When populating the matview, GROUP BY and count(*) are added to the
view definition query before this is executed for counting duplicity
of tuples in the matview. The result of count is stored in the matview
as a special column named "__ivm_count__".

2. Maintenance of matview

When base tables are modified, the change set of the table can be
referred as Ephemeral Named Relations (ENRs) thanks to Transition Table
(a feature of trigger implemented since PG10). We can calculate the diff
set of the matview by replacing the base table in the view definition
query with the ENR (at least if it is Selection-Projection -Join view).
As well as view definition time, GROUP BY and count(*) is added in order
to count the duplicity of tuples in the diff set. As a result, two diff
sets (to be deleted from and to be inserted into the matview) are
calculated, and the results are stored into temporary tables respectively.

The matiview is updated by merging these change sets. Instead of executing
DELETE or INSERT simply, the values of __ivm_count__ column in the matview
is decreased or increased. When the values becomes zero, the corresponding
tuple is deleted from the matview.

3. Access to matview

When SELECT is issued for IVM matviews defined with DISTINCT, all columns
except to __ivm_count__ of each tuple in the matview are returned. This is
correct because duplicity of tuples are eliminated by GROUP BY.

When DISTINCT is not used, SELECT for the IVM matviews returns each tuple
__ivm_count__ times. Currently, this is implemented by rewriting the SELECT
query to replace the matview RTE with a subquery which joins the matview
and generate_series function as bellow.

SELECT mv.* FROM mv, generate_series(1, mv.__ivm_count__);

__ivm_count__ column is invisible for users when "SELECT * FROM ..." is
issued, but users can see the value by specifying in target list explicitly.

====== Example 3 ======
postgres=# \d+ m1
Materialized view "public.m1"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
---------------+--------+-----------+----------+---------+----------+--------------+-------------
t | text | | | | extended | |
__ivm_count__ | bigint | | | | plain | |
View definition:
SELECT t1.t
FROM t1;
Access method: heap

postgres=# \d+ m2
Materialized view "public.m2"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
---------------+--------+-----------+----------+---------+----------+--------------+-------------
t | text | | | | extended | |
__ivm_count__ | bigint | | | | plain | |
View definition:
SELECT DISTINCT t1.t
FROM t1;
Access method: heap

postgres=# SELECT *, __ivm_count__ FROM m1;
t | __ivm_count__
---+---------------
B | 2
B | 2
A | 1
(3 rows)

postgres=# SELECT *, __ivm_count__ FROM m2;
t | __ivm_count__
---+---------------
B | 2
A | 1
(2 rows)

postgres=# EXPLAIN SELECT * FROM m1;
QUERY PLAN
------------------------------------------------------------------------------
Nested Loop (cost=0.00..61.03 rows=3000 width=2)
-> Seq Scan on m1 mv (cost=0.00..1.03 rows=3 width=10)
-> Function Scan on generate_series (cost=0.00..10.00 rows=1000 width=0)
(3 rows)
=============================

* Simple Performance Evaluation

I confirmed that "incremental" update of matviews is more effective
than the standard REFRESH by using simple exapmle. I used tables
of pgbench (SF=100) here.

Create two matviews, that is, without and with IVM.

test=# CREATE MATERIALIZED VIEW bench1 AS
SELECT aid, bid, abalance, bbalance
FROM pgbench_accounts JOIN pgbench_branches USING (bid)
WHERE abalance > 0 OR bbalance > 0;
SELECT 5001054
test=# CREATE INCREMENTAL MATERIALIZED VIEW bench2 AS
SELECT aid, bid, abalance, bbalance
FROM pgbench_accounts JOIN pgbench_branches USING (bid)
WHERE abalance > 0 OR bbalance > 0;
SELECT 5001054

The standard REFRESH of bench1 took more than 10 seconds.

test=# \timing
Timing is on.
test=# REFRESH MATERIALIZED VIEW bench1 ;
REFRESH MATERIALIZED VIEW
Time: 11210.563 ms (00:11.211)

Create an index on the IVM matview (bench2).

test=# CREATE INDEX on bench2(aid,bid);
CREATE INDEX

Updating a tuple in pgbench_accounts took 18ms. After this, bench2
was updated automatically and correctly.

test=# SELECT * FROM bench2 WHERE aid = 1;
aid | bid | abalance | bbalance
-----+-----+----------+----------
1 | 1 | 10 | 10
(1 row)

Time: 2.498 ms
test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
UPDATE 1
Time: 18.634 ms
test=# SELECT * FROM bench2 WHERE aid = 1;
aid | bid | abalance | bbalance
-----+-----+----------+----------
1 | 1 | 1000 | 10
(1 row)

However, if there is not the index on bench2, it took 4 sec, so
appropriate indexes are needed on IVM matviews.

test=# DROP INDEX bench2_aid_bid_idx ;
DROP INDEX
Time: 10.613 ms
test=# UPDATE pgbench_accounts SET abalance = 2000 WHERE aid = 1;
UPDATE 1
Time: 3931.274 ms (00:03.931)

* Restrictions on view definition

This patch is still in Work-in-Progress and there are many restrictions
on the view definition query of matviews.

The current implementation supports views including selection, projection,
and inner join with or without DISTINCT. Aggregation and GROUP BY are not
supported yet, but I plan to deal with these by the first release.
Self-join, subqueries, OUTER JOIN, CTE, window functions are not
considered well, either. I need more investigation on these type of views
although I found some papers explaining how to handle sub-queries and
outer-joins.

These unsupported views should be checked when a matview is created, but
this is not implemented yet. Hoshiai-san are working on this.

* Timing of view maintenance

This patch implements a kind of Immediate Maintenance, that is, a matview
is updated immediately when a base table is modified. On other hand, in
"Deferred Maintenance", matviews are updated after the transaction, for
example, by the user command like REFRESH.

For implementing "deferred", it is need to implement a mechanism to maintain
logs for recording changes of base tables and an algorithm to compute the
delta to be applied to matviews.

In addition, there could be another implementation of Immediate Maintenance
in which matview is updated at the end of a transaction that modified base
table, rather than in AFTER trigger. Oracle supports this type of IVM. To
implement this, we will need a mechanism to maintain change logs on base
tables as well as Deferred maintenance.

* Counting algorithm implementation

There will be also discussions on counting-algorithm implementation.
Firstly, the current patch treats "__ivm_count__" as a special column name
in a somewhat ad hoc way. This is used when maintaining and accessing matviews,
and when "SELECT * FROM ..." is issued, __ivm_count__ column is invisible for
users. Maybe this name has to be inhibited in user tables. Is it acceptable
to use such columns for IVM, and is there better way, if not?

Secondly, a matview with duplicate tuples is replaces with a subquery which
uses generate_series function. It does not have to be generate_series, and we
can make a new set returning function for this. Anyway, this internal behaviour
is visible in EXPLAIN results as shown in Example 3. Also, there is a
performance impact because estimated rows number is wrong, and what is worse,
the cost of join is not small when the size of matview is large. Therefore, we
might have to add a new plan node for selecting from matviews rather than using
such a special set returning function.

Ragards,
--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

WIP_immediate_IVM.patchtext/x-diff; name=WIP_immediate_IVM.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4c7e93892a..858b05b427 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1944,6 +1944,13 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       <entry>True if table or index is a partition</entry>
      </row>
 
+     <row>
+      <entry><structfield>relisivm</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if materialized view enables incremental view maintenance</entry>
+     </row>
+
      <row>
       <entry><structfield>relrewrite</structfield></entry>
       <entry><type>oid</type></entry>
diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml
index ec8847ed40..a23366a342 100644
--- a/doc/src/sgml/ref/create_materialized_view.sgml
+++ b/doc/src/sgml/ref/create_materialized_view.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
+CREATE [ INCREMENTAL ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
     [ (<replaceable>column_name</replaceable> [, ...] ) ]
     [ USING <replaceable class="parameter">method</replaceable> ]
     [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
@@ -54,6 +54,16 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
   <title>Parameters</title>
 
   <variablelist>
+   <varlistentry>
+    <term><literal>INCREMENTAL</literal></term>
+    <listitem>
+     <para>
+      If specified, a materialized view enables incremental view maintenance.
+      You can replace only the contents of a materialized view, which based rows are changed.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>IF NOT EXISTS</literal></term>
     <listitem>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index ee6b72e550..823486c968 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -891,6 +891,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
+	values[Anum_pg_class_relisivm - 1] = BoolGetDatum(rd_rel->relisivm);
 	if (relacl != (Datum) 0)
 		values[Anum_pg_class_relacl - 1] = relacl;
 	else
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index c8d22e1b65..57cd30718b 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -910,6 +910,7 @@ index_create(Relation heapRelation,
 	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
 	indexRelation->rd_rel->relam = accessMethodObjectId;
 	indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid);
+	indexRelation->rd_rel->relisivm = false;
 
 	/*
 	 * store index's pg_class entry
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 43c2fa9124..0098531dfd 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -51,6 +51,14 @@
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
 
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
+#include "commands/trigger.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
+#include "parser/parse_func.h"
+#include "nodes/print.h"
+
 
 typedef struct
 {
@@ -74,6 +82,8 @@ static bool intorel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void intorel_shutdown(DestReceiver *self);
 static void intorel_destroy(DestReceiver *self);
 
+static void CreateIvmTrigger(Oid relOid, Oid viewOid, char *matviewname, int16 type);
+static void CreateIvmTriggersOnBaseTables(Query *qry, Node *jtnode, Oid matviewOid, char* matviewname);
 
 /*
  * create_ctas_internal
@@ -109,6 +119,8 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	create->oncommit = into->onCommit;
 	create->tablespacename = into->tableSpaceName;
 	create->if_not_exists = false;
+	/* Using Materialized view only */
+	create->ivm = into->ivm;
 	create->accessMethod = into->accessMethod;
 
 	/*
@@ -239,6 +251,7 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 	List	   *rewritten;
 	PlannedStmt *plan;
 	QueryDesc  *queryDesc;
+	Query	   *copied_query;
 
 	if (stmt->if_not_exists)
 	{
@@ -319,7 +332,29 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		 * and is executed repeatedly.  (See also the same hack in EXPLAIN and
 		 * PREPARE.)
 		 */
-		rewritten = QueryRewrite(copyObject(query));
+
+		copied_query = copyObject(query);
+		if (is_matview && into->ivm)
+		{
+			TargetEntry *tle;
+			Node *node;
+			ParseState *pstate = make_parsestate(NULL);
+
+			FuncCall *fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+			fn->agg_star = true;
+
+			copied_query->groupClause = transformDistinctClause(NULL, &copied_query->targetList, copied_query->sortClause, false);
+			node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+			tle = makeTargetEntry((Expr *) node,
+									  list_length(copied_query->targetList) + 1,
+									  pstrdup("__ivm_count__"),
+									  false);
+			copied_query->targetList = lappend(copied_query->targetList, tle);
+			copied_query->hasAggs = true;
+		}
+
+		rewritten = QueryRewrite(copied_query);
 
 		/* SELECT should never rewrite to more or less than one SELECT query */
 		if (list_length(rewritten) != 1)
@@ -378,11 +413,65 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 
 		/* Restore userid and security context */
 		SetUserIdAndSecContext(save_userid, save_sec_context);
+
+
+		if (into->ivm)
+		{
+			char	   *matviewname;
+			Oid matviewOid = address.objectId;
+			Relation matviewRel = heap_open(matviewOid, NoLock);
+			matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+													 RelationGetRelationName(matviewRel));
+			copied_query = copyObject(query);
+			AcquireRewriteLocks(copied_query, true, false);
+
+			CreateIvmTriggersOnBaseTables(copied_query, (Node *)copied_query->jointree, matviewOid, matviewname);
+
+			heap_close(matviewRel, NoLock);
+		}
 	}
 
 	return address;
 }
 
+static void CreateIvmTriggersOnBaseTables(Query *qry, Node *jtnode, Oid matviewOid, char* matviewname)
+{
+
+	if (jtnode == NULL)
+		return;
+	if (IsA(jtnode, RangeTblRef))
+	{
+		int			rti = ((RangeTblRef *) jtnode)->rtindex;
+		RangeTblEntry *rte = rt_fetch(rti, qry->rtable);
+
+		if (rte->rtekind == RTE_RELATION)
+		{
+			CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_INSERT);
+			CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_DELETE);
+			CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_UPDATE);
+		}
+		else
+			elog(ERROR, "unsupported RTE kind: %d", (int) rte->rtekind);
+	}
+	else if (IsA(jtnode, FromExpr))
+	{
+		FromExpr   *f = (FromExpr *) jtnode;
+		ListCell   *l;
+
+		foreach(l, f->fromlist)
+			CreateIvmTriggersOnBaseTables(qry, lfirst(l), matviewOid, matviewname);
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+
+		CreateIvmTriggersOnBaseTables(qry, j->larg, matviewOid, matviewname);
+		CreateIvmTriggersOnBaseTables(qry, j->rarg, matviewOid, matviewname);
+	}
+	else
+		elog(ERROR, "unrecognized node type: %d", (int) nodeTag(jtnode));
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -547,6 +636,11 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	if (is_matview && !into->skipData)
 		SetMatViewPopulatedState(intoRelationDesc, true);
 
+	/*
+	 * Mark relisivm field, if it's a matview and into->ivm is true.
+	 */
+	if (is_matview && into->ivm)
+		SetMatViewIVMState(intoRelationDesc, true);
 	/*
 	 * Fill private fields of myState for use by later routines
 	 */
@@ -619,3 +713,74 @@ intorel_destroy(DestReceiver *self)
 {
 	pfree(self);
 }
+
+static void
+CreateIvmTrigger(Oid relOid, Oid viewOid, char *matviewname, int16 type)
+{
+	CreateTrigStmt *ivm_trigger;
+	List *transitionRels = NIL;
+	ObjectAddress address, refaddr;
+
+	refaddr.classId = RelationRelationId;
+	refaddr.objectId = viewOid;
+	refaddr.objectSubId = 0;
+
+
+	ivm_trigger = makeNode(CreateTrigStmt);
+	ivm_trigger->relation = NULL;
+	ivm_trigger->row = false;
+	ivm_trigger->timing = TRIGGER_TYPE_AFTER;
+
+	ivm_trigger->events = type;
+
+	switch (type)
+	{
+		case TRIGGER_TYPE_INSERT:
+			ivm_trigger->trigname = "IVM_trigger_ins";
+			break;
+		case TRIGGER_TYPE_DELETE:
+			ivm_trigger->trigname = "IVM_trigger_del";
+			break;
+		case TRIGGER_TYPE_UPDATE:
+			ivm_trigger->trigname = "IVM_trigger_upd";
+			break;
+	}
+
+	if (type == TRIGGER_TYPE_INSERT || type == TRIGGER_TYPE_UPDATE)
+	{
+		TriggerTransition *n = makeNode(TriggerTransition);
+		n->name = "ivm_newtable";
+		n->isNew = true;
+		n->isTable = true;
+
+		transitionRels = lappend(transitionRels, n);
+	}
+	if (type == TRIGGER_TYPE_DELETE || type == TRIGGER_TYPE_UPDATE)
+	{
+		TriggerTransition *n = makeNode(TriggerTransition);
+		n->name = "ivm_oldtable";
+		n->isNew = false;
+		n->isTable = true;
+
+		transitionRels = lappend(transitionRels, n);
+	}
+
+	ivm_trigger->funcname = SystemFuncName("IVM_immediate_maintenance");
+
+	ivm_trigger->columns = NIL;
+	ivm_trigger->transitionRels = transitionRels;
+	ivm_trigger->whenClause = NULL;
+	ivm_trigger->isconstraint = false;
+	ivm_trigger->deferrable = false;
+	ivm_trigger->initdeferred = false;
+	ivm_trigger->constrrel = NULL;
+	ivm_trigger->args = list_make1(makeString(matviewname));
+
+	address = CreateTrigger(ivm_trigger, NULL, relOid, InvalidOid, InvalidOid,
+						 InvalidOid, InvalidOid, InvalidOid, NULL, true, false);
+
+	recordDependencyOn(&address, &refaddr, DEPENDENCY_AUTO);
+
+	/* Make changes-so-far visible */
+	CommandCounterIncrement();
+}
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 99bf3c29f2..a403b93b4c 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -46,6 +46,11 @@
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 
+#include "utils/regproc.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_func.h"
+
 
 typedef struct
 {
@@ -65,6 +70,7 @@ static bool transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void transientrel_shutdown(DestReceiver *self);
 static void transientrel_destroy(DestReceiver *self);
 static uint64 refresh_matview_datafill(DestReceiver *dest, Query *query,
+						 QueryEnvironment *queryEnv,
 						 const char *queryString);
 static char *make_temptable_name_n(char *tempname, int n);
 static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
@@ -74,6 +80,9 @@ static bool is_usable_unique_index(Relation indexRel);
 static void OpenMatViewIncrementalMaintenance(void);
 static void CloseMatViewIncrementalMaintenance(void);
 
+static void apply_delta(Oid matviewOid, Oid tempOid_new, Oid tempOid_old,
+			Oid relowner, int save_sec_context);
+
 /*
  * SetMatViewPopulatedState
  *		Mark a materialized view as populated, or not.
@@ -114,6 +123,46 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
 	CommandCounterIncrement();
 }
 
+/*
+ * SetMatViewIVMState
+ *		Mark a materialized view as IVM, or not.
+ *
+ * NOTE: caller must be holding an appropriate lock on the relation.
+ */
+void
+SetMatViewIVMState(Relation relation, bool newstate)
+{
+	Relation	pgrel;
+	HeapTuple	tuple;
+
+	Assert(relation->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Update relation's pg_class entry.  Crucial side-effect: other backends
+	 * (and this one too!) are sent SI message to make them rebuild relcache
+	 * entries.
+	 */
+	pgrel = table_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(relation)));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u",
+			 RelationGetRelid(relation));
+
+	((Form_pg_class) GETSTRUCT(tuple))->relisivm = newstate;
+
+	CatalogTupleUpdate(pgrel, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	table_close(pgrel, RowExclusiveLock);
+
+	/*
+	 * Advance command counter to make the updated pg_class row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+}
+
 /*
  * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command
  *
@@ -311,7 +360,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 
 	/* Generate the data, if wanted. */
 	if (!stmt->skipData)
-		processed = refresh_matview_datafill(dest, dataQuery, queryString);
+		processed = refresh_matview_datafill(dest, dataQuery, NULL, queryString);
 
 	/* Make the matview match the newly generated data. */
 	if (concurrent)
@@ -369,6 +418,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  */
 static uint64
 refresh_matview_datafill(DestReceiver *dest, Query *query,
+						 QueryEnvironment *queryEnv,
 						 const char *queryString)
 {
 	List	   *rewritten;
@@ -405,7 +455,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 	/* Create a QueryDesc, redirecting output to our tuple receiver */
 	queryDesc = CreateQueryDesc(plan, queryString,
 								GetActiveSnapshot(), InvalidSnapshot,
-								dest, NULL, NULL, 0);
+								dest, NULL, queryEnv ? queryEnv: NULL, 0);
 
 	/* call ExecutorStart to prepare the plan for execution */
 	ExecutorStart(queryDesc, 0);
@@ -926,3 +976,366 @@ CloseMatViewIncrementalMaintenance(void)
 	matview_maintenance_depth--;
 	Assert(matview_maintenance_depth >= 0);
 }
+
+/*
+ * IVM trigger function
+ */
+
+Datum
+IVM_immediate_maintenance(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	Relation	rel;
+	Oid relid;
+	Oid matviewOid;
+	Query	   *query, *old_delta_qry, *new_delta_qry;
+	char*		matviewname = trigdata->tg_trigger->tgargs[0];
+	List	   *names;
+	Relation matviewRel;
+	int old_depth = matview_maintenance_depth;
+
+	Oid			tableSpace;
+	Oid			relowner;
+	Oid			OIDDelta_new = InvalidOid;
+	Oid			OIDDelta_old = InvalidOid;
+	DestReceiver *dest_new = NULL, *dest_old = NULL;
+	char		relpersistence;
+	Oid			save_userid;
+	int			save_sec_context;
+	int			save_nestlevel;
+
+	ParseState *pstate;
+	QueryEnvironment *queryEnv = create_queryEnv();
+
+	/* Create a dummy ParseState for addRangeTableEntryForENR */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+
+	names = stringToQualifiedNameList(matviewname);
+	matviewOid = RangeVarGetRelid(makeRangeVarFromNameList(names), AccessShareLock, true);
+	matviewRel = heap_open(matviewOid, NoLock);
+
+	/* Make sure it is a materialized view. */
+	if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("\"%s\" is not a materialized view",
+						RelationGetRelationName(matviewRel))));
+
+	rel = trigdata->tg_relation;
+	relid = rel->rd_id;
+
+	query = get_view_query(matviewRel);
+
+	new_delta_qry = copyObject(query);
+	old_delta_qry = copyObject(query);
+
+	if (trigdata->tg_newtable)
+	{
+		RangeTblEntry *rte;
+		ListCell   *lc;
+
+		TargetEntry *tle;
+		Node *node;
+		FuncCall *fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+
+		EphemeralNamedRelation enr =
+			palloc(sizeof(EphemeralNamedRelationData));
+
+		enr->md.name = trigdata->tg_trigger->tgnewtable;
+		enr->md.reliddesc = trigdata->tg_relation->rd_id;
+		enr->md.tupdesc = NULL;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(trigdata->tg_newtable);
+		enr->reldata = trigdata->tg_newtable;
+		register_ENR(queryEnv, enr);
+
+		rte = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+		new_delta_qry->rtable = lappend(new_delta_qry->rtable, rte);
+
+		foreach(lc, new_delta_qry->rtable)
+		{
+			RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+			if (r->relid == relid)
+			{
+				lfirst(lc) = rte;
+				break;
+			}
+		}
+
+		fn->agg_star = true;
+		new_delta_qry->groupClause = transformDistinctClause(NULL, &new_delta_qry->targetList, new_delta_qry->sortClause, false);
+		node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+		tle = makeTargetEntry((Expr *) node,
+								  list_length(new_delta_qry->targetList) + 1,
+								  pstrdup("__ivm_count__"),
+								  false);
+		new_delta_qry->targetList = lappend(new_delta_qry->targetList, tle);
+		new_delta_qry->hasAggs = true;
+	}
+
+	if (trigdata->tg_oldtable)
+	{
+		RangeTblEntry *rte;
+		ListCell   *lc;
+
+		TargetEntry *tle;
+		Node *node;
+		FuncCall *fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+
+		EphemeralNamedRelation enr =
+			palloc(sizeof(EphemeralNamedRelationData));
+
+		enr->md.name = trigdata->tg_trigger->tgoldtable;
+		enr->md.reliddesc = trigdata->tg_relation->rd_id;
+		enr->md.tupdesc = NULL;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(trigdata->tg_oldtable);
+		enr->reldata = trigdata->tg_oldtable;
+		register_ENR(queryEnv, enr);
+
+		rte = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+		old_delta_qry->rtable = lappend(old_delta_qry->rtable, rte);
+
+		foreach(lc, old_delta_qry->rtable)
+		{
+			RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+			if (r->relid == relid)
+			{
+				lfirst(lc) = rte;
+				break;
+			}
+		}
+
+		fn->agg_star = true;
+		old_delta_qry->groupClause = transformDistinctClause(NULL, &old_delta_qry->targetList, old_delta_qry->sortClause, false);
+		node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+		tle = makeTargetEntry((Expr *) node,
+								  list_length(old_delta_qry->targetList) + 1,
+								  pstrdup("__ivm_count__"),
+								  false);
+		old_delta_qry->targetList = lappend(old_delta_qry->targetList, tle);
+		old_delta_qry->hasAggs = true;
+	}
+
+
+	/*
+	 * Check for active uses of the relation in the current transaction, such
+	 * as open scans.
+	 *
+	 * NB: We count on this to protect us against problems with refreshing the
+	 * data using TABLE_INSERT_FROZEN.
+	 */
+	CheckTableNotInUse(matviewRel, "REFRESH MATERIALIZED VIEW");
+
+	relowner = matviewRel->rd_rel->relowner;
+
+	/*
+	 * Switch to the owner's userid, so that any functions are run as that
+	 * user.  Also arrange to make GUC variable changes local to this command.
+	 * Don't lock it down too tight to create a temporary table just yet.  We
+	 * will switch modes when we are about to execute user code.
+	 */
+	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+	save_nestlevel = NewGUCNestLevel();
+
+	tableSpace = GetDefaultTablespace(RELPERSISTENCE_TEMP, false);
+	relpersistence = RELPERSISTENCE_TEMP;
+
+	/*
+	 * Create the transient table that will receive the regenerated data. Lock
+	 * it against access by any other process until commit (by which time it
+	 * will be gone).
+	 */
+	if (trigdata->tg_newtable)
+	{
+		OIDDelta_new = make_new_heap(matviewOid, tableSpace, relpersistence,
+									 ExclusiveLock);
+		LockRelationOid(OIDDelta_new, AccessExclusiveLock);
+		dest_new = CreateTransientRelDestReceiver(OIDDelta_new);
+	}
+	if (trigdata->tg_oldtable)
+	{
+		if (trigdata->tg_newtable)
+			OIDDelta_old = make_new_heap(OIDDelta_new, tableSpace, relpersistence,
+										 ExclusiveLock);
+		else
+			OIDDelta_old = make_new_heap(matviewOid, tableSpace, relpersistence,
+										 ExclusiveLock);
+		LockRelationOid(OIDDelta_old, AccessExclusiveLock);
+		dest_old = CreateTransientRelDestReceiver(OIDDelta_old);
+	}
+
+	/*
+	 * Now lock down security-restricted operations.
+	 */
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+
+	/* Generate the data. */
+	if (trigdata->tg_newtable)
+		refresh_matview_datafill(dest_new, new_delta_qry, queryEnv, NULL);
+	if (trigdata->tg_oldtable)
+		refresh_matview_datafill(dest_old, old_delta_qry, queryEnv, NULL);
+
+	PG_TRY();
+	{
+		apply_delta(matviewOid, OIDDelta_new, OIDDelta_old, relowner,
+							   save_sec_context);
+	}
+	PG_CATCH();
+	{
+		matview_maintenance_depth = old_depth;
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	heap_close(matviewRel, NoLock);
+
+	/* Roll back any GUC changes */
+	AtEOXact_GUC(false, save_nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(save_userid, save_sec_context);
+
+	return PointerGetDatum(NULL);
+}
+
+static void
+apply_delta(Oid matviewOid, Oid tempOid_new, Oid tempOid_old,
+			Oid relowner, int save_sec_context)
+{
+	StringInfoData querybuf;
+	StringInfoData mvatts_buf, diffatts_buf;
+	Relation	matviewRel;
+	Relation	tempRel_new = NULL, tempRel_old = NULL;
+	char	   *matviewname;
+	char	   *tempname_new = NULL, *tempname_old = NULL;
+	int i;
+
+
+	initStringInfo(&querybuf);
+	matviewRel = heap_open(matviewOid, NoLock);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	if (OidIsValid(tempOid_new))
+	{
+		tempRel_new = heap_open(tempOid_new, NoLock);
+		tempname_new = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(tempRel_new)),
+												  RelationGetRelationName(tempRel_new));
+	}
+	if (OidIsValid(tempOid_old))
+	{
+		tempRel_old = heap_open(tempOid_old, NoLock);
+		tempname_old = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(tempRel_old)),
+												  RelationGetRelationName(tempRel_old));
+	}
+
+	initStringInfo(&mvatts_buf);
+	initStringInfo(&diffatts_buf);
+	for (i = 0; i < matviewRel->rd_att->natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+
+		if (!strcmp(NameStr(attr->attname), "__ivm_count__"))
+			continue;
+
+		if (i > 0)
+		{
+			appendStringInfo(&mvatts_buf, ", ");
+			appendStringInfo(&diffatts_buf, ", ");
+		}
+		appendStringInfo(&mvatts_buf, "mv.%s", NameStr(attr->attname));
+		appendStringInfo(&diffatts_buf, "diff.%s", NameStr(attr->attname));
+	}
+
+	/* Open SPI context. */
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	/* Analyze the temp table with the new contents. */
+	if (tempname_new)
+	{
+		appendStringInfo(&querybuf, "ANALYZE %s", tempname_new);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+	if (tempname_old)
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf, "ANALYZE %s", tempname_old);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+
+	OpenMatViewIncrementalMaintenance();
+
+	if (tempname_old)
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf,
+						"WITH t AS ("
+						"  SELECT diff.__ivm_count__, (diff.__ivm_count__ = mv.__ivm_count__) AS for_dlt, mv.ctid"
+						"  FROM %s AS mv, %s AS diff WHERE (%s) = (%s)"
+						"), updt AS ("
+						"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ - t.__ivm_count__"
+						"  FROM t WHERE mv.ctid = t.ctid AND NOT for_dlt"
+						") DELETE FROM %s AS mv USING t WHERE mv.ctid = t.ctid AND for_dlt;",
+						matviewname, tempname_old, mvatts_buf.data, diffatts_buf.data, matviewname, matviewname);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+
+	}
+	if (tempname_new)
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf,
+						"WITH updt AS ("
+						"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ + diff.__ivm_count__"
+						"  FROM %s AS diff WHERE (%s) = (%s)"
+						"  RETURNING %s"
+						") INSERT INTO %s (SELECT * FROM %s AS diff WHERE (%s) NOT IN (SELECT * FROM updt));",
+						matviewname, tempname_new, mvatts_buf.data, diffatts_buf.data, diffatts_buf.data, matviewname, tempname_new, diffatts_buf.data);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+
+	}
+
+	/* We're done maintaining the materialized view. */
+	CloseMatViewIncrementalMaintenance();
+
+	if (OidIsValid(tempOid_new))
+		heap_close(tempRel_new, NoLock);
+	if (OidIsValid(tempOid_old))
+		heap_close(tempRel_old, NoLock);
+
+	heap_close(matviewRel, NoLock);
+
+	/* Clean up temp tables. */
+	if (OidIsValid(tempOid_new))
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf, "DROP TABLE %s", tempname_new);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+	if (OidIsValid(tempOid_old))
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf, "DROP TABLE %s", tempname_old);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+
+	/* Close SPI context. */
+	if (SPI_finish() != SPI_OK_FINISH)
+		elog(ERROR, "SPI_finish failed");
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 780d7ab00b..7a43e57186 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2364,6 +2364,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(relisivm);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4f2ebe5118..608d477bd5 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2641,6 +2641,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(relisivm);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 387e4b9b71..2321d3f685 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3045,6 +3045,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3b96492b36..a7bb7dce81 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1366,6 +1366,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3dc0e8a4fb..61704c5c90 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -418,6 +418,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target create_mv_target
+%type <boolean>	incremental
 
 %type <defelt>	createfunc_opt_item common_func_opt_item dostmt_opt_item
 %type <fun_param> func_arg func_arg_with_default table_func_column aggr_arg
@@ -645,7 +646,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
 	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
-	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+	INCLUDING INCREMENT INCREMENTAL INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
 	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
 	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -4054,30 +4055,32 @@ opt_with_data:
  *****************************************************************************/
 
 CreateMatViewStmt:
-		CREATE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
+		CREATE OptNoLog incremental MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $7;
-					ctas->into = $5;
+					ctas->query = $8;
+					ctas->into = $6;
 					ctas->relkind = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = false;
 					/* cram additional flags into the IntoClause */
-					$5->rel->relpersistence = $2;
-					$5->skipData = !($8);
+					$6->rel->relpersistence = $2;
+					$6->skipData = !($9);
+					$6->ivm = $3;
 					$$ = (Node *) ctas;
 				}
-		| CREATE OptNoLog MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
+		| CREATE OptNoLog incremental MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $10;
-					ctas->into = $8;
+					ctas->query = $11;
+					ctas->into = $9;
 					ctas->relkind = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = true;
 					/* cram additional flags into the IntoClause */
-					$8->rel->relpersistence = $2;
-					$8->skipData = !($11);
+					$9->rel->relpersistence = $2;
+					$9->skipData = !($12);
+					$9->ivm = $3;
 					$$ = (Node *) ctas;
 				}
 		;
@@ -4094,9 +4097,14 @@ create_mv_target:
 					$$->tableSpaceName = $5;
 					$$->viewQuery = NULL;		/* filled at analysis time */
 					$$->skipData = false;		/* might get changed later */
+					$$->ivm = false;
 				}
 		;
 
+incremental:	INCREMENTAL				{ $$ = true; }
+				| /*EMPTY*/				{ $$ = false; }
+		;
+
 OptNoLog:	UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
 		;
@@ -15127,6 +15135,7 @@ unreserved_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 0640d11fac..f65c507f49 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -37,6 +37,7 @@
 #include "utils/syscache.h"
 #include "utils/varlena.h"
 
+#include "commands/matview.h"
 
 #define MAX_FUZZY_DISTANCE				3
 
@@ -1238,6 +1239,7 @@ addRangeTableEntry(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -1317,6 +1319,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -2605,6 +2608,9 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 	{
 		Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
 
+		if (!strcmp("__ivm_count__", NameStr(attr->attname)) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		if (attr->attisdropped)
 		{
 			if (include_dropped)
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 6bd889461e..fbab6b41f7 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -765,7 +765,8 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 														attr->atttypmod))));
 	}
 
-	if (i != resultDesc->natts)
+	/* No check for materialized views since this could have special columns for IVM */
+	if ((!isSelect || requireColumnNameMatch) && i != resultDesc->natts)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 isSelect ?
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 39080776b0..38305003d5 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -41,6 +41,8 @@
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 
+#include "parser/parser.h"
+#include "commands/matview.h"
 
 /* We use a list of these to detect recursion in RewriteQuery */
 typedef struct rewrite_event
@@ -1597,6 +1599,50 @@ ApplyRetrieveRule(Query *parsetree,
 	if (rule->qual != NULL)
 		elog(ERROR, "cannot handle qualified ON SELECT rule");
 
+	if (RelationIsIVM(relation))
+	{
+		rule_action = copyObject(linitial(rule->actions));
+
+		if (!rule_action->distinctClause)
+		{
+			StringInfoData str;
+			RawStmt *raw;
+			Query *sub;
+
+			if (rule_action->hasDistinctOn)
+				elog(ERROR, "DISTINCT ON is not supported in IVM");
+
+			initStringInfo(&str);
+			appendStringInfo(&str, "SELECT mv.*, __ivm_count__ FROM %s mv, generate_series(1, mv.__ivm_count__)",
+						quote_qualified_identifier(get_namespace_name(RelationGetNamespace(relation)),
+													RelationGetRelationName(relation)));
+
+			raw = (RawStmt*)linitial(raw_parser(str.data));
+			sub = transformStmt(make_parsestate(NULL),raw->stmt);
+
+			rte = rt_fetch(rt_index, parsetree->rtable);
+
+			rte->rtekind = RTE_SUBQUERY;
+			rte->subquery = sub;
+			rte->security_barrier = RelationIsSecurityView(relation);
+			/* Clear fields that should not be set in a subquery RTE */
+			rte->relid = InvalidOid;
+			rte->relkind = 0;
+			rte->rellockmode = 0;
+			rte->tablesample = NULL;
+			rte->inh = false;			/* must not be set for a subquery */
+
+			rte->requiredPerms = 0;		/* no permission check on subquery itself */
+			rte->checkAsUser = InvalidOid;
+			rte->selectedCols = NULL;
+			rte->insertedCols = NULL;
+			rte->updatedCols = NULL;
+			rte->extraUpdatedCols = NULL;
+		}
+
+		return parsetree;
+	}
+
 	if (rt_index == parsetree->resultRelation)
 	{
 		/*
@@ -1906,7 +1952,8 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
 		 * In that case this test would need to be postponed till after we've
 		 * opened the rel, so that we could check its state.
 		 */
-		if (rte->relkind == RELKIND_MATVIEW)
+		if (rte->relkind == RELKIND_MATVIEW &&
+			(!rte->relisivm || MatViewIncrementalMaintenanceIsEnabled() || parsetree->commandType != CMD_SELECT))
 			continue;
 
 		/*
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index b4f2d0f35a..82a2fd99bd 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1844,6 +1844,30 @@ get_rel_relispartition(Oid relid)
 		return false;
 }
 
+/*
+ * get_rel_relisivm
+ *
+ *		Returns the relisivm flag associated with a given relation.
+ */
+bool
+get_rel_relisivm(Oid relid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp);
+		bool		result;
+
+		result = reltup->relisivm;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return false;
+}
+
 /*
  * get_rel_tablespace
  *
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index eaf2b18820..b3586cb273 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1861,6 +1861,8 @@ formrdesc(const char *relationName, Oid relationReltype,
 
 	/* ... and they're always populated, too */
 	relation->rd_rel->relispopulated = true;
+	/* ... and they're always no ivm, too */
+	relation->rd_rel->relisivm = false;
 
 	relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
 	relation->rd_rel->relpages = 0;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e4c03de221..7bead138d0 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -985,6 +985,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"INCREMENTAL MATERIALIZED VIEW", NULL, NULL, &Query_for_list_of_matviews},
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2467,7 +2468,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
-		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
+		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW", "INCREMENTAL MATERIALIZED VIEW");
 	/* Complete PARTITION BY with RANGE ( or LIST ( or ... */
 	else if (TailMatches("PARTITION", "BY"))
 		COMPLETE_WITH("RANGE (", "LIST (", "HASH (");
@@ -2672,13 +2673,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches("CREATE", "MATERIALIZED"))
+	else if (Matches("CREATE", "MATERIALIZED") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED"))
 		COMPLETE_WITH("VIEW");
-	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	/* Complete CREATE MATERIALIZED VIEW <name> with AS  */
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny) ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH("SELECT");
 
 /* CREATE EVENT TRIGGER */
diff --git a/src/include/catalog/pg_class.dat b/src/include/catalog/pg_class.dat
index 9bcf28676d..7deda405af 100644
--- a/src/include/catalog/pg_class.dat
+++ b/src/include/catalog/pg_class.dat
@@ -27,7 +27,7 @@
   relpersistence => 'p', relkind => 'r', relnatts => '31', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 { oid => '1249',
@@ -37,7 +37,7 @@
   relpersistence => 'p', relkind => 'r', relnatts => '25', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 { oid => '1255',
@@ -47,17 +47,17 @@
   relpersistence => 'p', relkind => 'r', relnatts => '29', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 { oid => '1259',
   relname => 'pg_class', reltype => 'pg_class', relam => 'heap',
   relfilenode => '0', relpages => '0', reltuples => '0', relallvisible => '0',
   reltoastrelid => '0', relhasindex => 'f', relisshared => 'f',
-  relpersistence => 'p', relkind => 'r', relnatts => '33', relchecks => '0',
+  relpersistence => 'p', relkind => 'r', relnatts => '34', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 090b6ba907..ff535f5504 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
 	/* is relation a partition? */
 	bool		relispartition;
 
+	/* is relation a matview with ivm? */
+	bool		relisivm;
+
 	/* heap for rewrite during DDL, link to original rel */
 	Oid			relrewrite BKI_DEFAULT(0);
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87335248a0..5de996a72c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10677,4 +10677,9 @@
   proname => 'pg_partition_root', prorettype => 'regclass',
   proargtypes => 'regclass', prosrc => 'pg_partition_root' },
 
+# IVM
+{ oid => '784', descr => 'ivm trigger',
+  proname => 'IVM_immediate_maintenance', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_maintenance' },
+
 ]
diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h
index d4461dd802..779037e3c3 100644
--- a/src/include/commands/matview.h
+++ b/src/include/commands/matview.h
@@ -23,6 +23,8 @@
 
 extern void SetMatViewPopulatedState(Relation relation, bool newstate);
 
+extern void SetMatViewIVMState(Relation relation, bool newstate);
+
 extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 				   ParamListInfo params, char *completionTag);
 
@@ -30,4 +32,6 @@ extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
 
 extern bool MatViewIncrementalMaintenanceIsEnabled(void);
 
+extern Datum IVM_immediate_maintenance(PG_FUNCTION_ARGS);
+
 #endif							/* MATVIEW_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 12e9730dd0..745d048d37 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1002,6 +1002,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		relisivm;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -2059,6 +2060,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		ivm;			/* incremental view maintenance is used by materialized view */
 } CreateStmt;
 
 /* ----------
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index f9b1cf2df7..105c2af4ce 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -117,6 +117,7 @@ typedef struct IntoClause
 	char	   *tableSpaceName; /* table space to use, or NULL */
 	Node	   *viewQuery;		/* materialized view's SELECT query */
 	bool		skipData;		/* true for WITH NO DATA */
+	bool		ivm;			/* true for WITH IVM */
 } IntoClause;
 
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 00ace8425e..d682ee11cc 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -198,6 +198,7 @@ PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
 PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
+PG_KEYWORD("incremental", INCREMENTAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
 PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 9606d021b1..c66cc81141 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -129,6 +129,7 @@ extern Oid	get_rel_namespace(Oid relid);
 extern Oid	get_rel_type_id(Oid relid);
 extern char get_rel_relkind(Oid relid);
 extern bool get_rel_relispartition(Oid relid);
+extern bool get_rel_relisivm(Oid relid);
 extern Oid	get_rel_tablespace(Oid relid);
 extern char get_rel_persistence(Oid relid);
 extern Oid	get_transform_fromsql(Oid typid, Oid langid, List *trftypes);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d7f33abce3..057f38d5de 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -562,6 +562,8 @@ typedef struct ViewOptions
  */
 #define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated)
 
+#define RelationIsIVM(relation) ((relation)->rd_rel->relisivm)
+
 /*
  * RelationIsAccessibleInLogicalDecoding
  *		True if we need to log enough information to have access via
#14Yugo Nagata
nagata@sraoss.co.jp
In reply to: Yugo Nagata (#13)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi hackers,

Thank you for your many questions and feedbacks at PGCon 2019.
Attached is the patch rebased for the current master branch.

Regards,
Yugo Nagata

On Tue, 14 May 2019 15:46:48 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

On Mon, 1 Apr 2019 12:11:22 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

On Thu, 27 Dec 2018 21:57:26 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

Hi,

I would like to implement Incremental View Maintenance (IVM) on PostgreSQL.

I am now working on an initial patch for implementing IVM on PostgreSQL.
This enables materialized views to be updated incrementally after one
of their base tables is modified.

Attached is a WIP patch of Incremental View Maintenance (IVM).
Major part is written by me, and changes in syntax and pg_class
are Hoshiai-san's work.

Although this is sill a draft patch in work-in-progress, any
suggestions or thoughts would be appreciated.

* What it is

This allows a kind of Immediate Maintenance of materialized views. if a
materialized view is created by CRATE INCREMENTAL MATERIALIZED VIEW command,
the contents of the mateview is updated automatically and incrementally
after base tables are updated. Noted this syntax is just tentative, so it
may be changed.

====== Example 1 ======
postgres=# CREATE INCREMENTAL MATERIALIZED VIEW m AS SELECT * FROM t0;
SELECT 3
postgres=# SELECT * FROM m;
i
---
3
2
1
(3 rows)

postgres=# INSERT INTO t0 VALUES (4);
INSERT 0 1
postgres=# SELECt * FROM m; -- automatically updated
i
---
3
2
1
4
(4 rows)
=============================

This implementation also supports matviews including duplicate tuples or
DISTINCT clause in its view definition query. For example, even if a matview
is defined with DISTINCT to remove duplication of tuples in a base table, this
can perform incremental update of the matview properly. That is, the contents
of the matview doesn't change when exiting tuples are inserted into the base
tables, and a tuple in the matview is deleted only when duplicity of the
corresponding tuple in the base table becomes zero.

This is due to "colunting alogorithm" in which the number of each tuple is
stored in matviews as a special column value.

====== Example 2 ======
postgres=# SELECT * FROM t1;
id | t
----+---
1 | A
2 | B
3 | C
4 | A
(4 rows)

postgres=# CREATE INCREMENTAL MATERIALIZED VIEW m1 AS SELECT t FROM t1;
SELECT 3
postgres=# CREATE INCREMENTAL MATERIALIZED VIEW m2 AS SELECT DISTINCT t FROM t1;
SELECT 3
postgres=# SELECT * FROM m1; -- with duplicity
t
---
A
A
C
B
(4 rows)

postgres=# SELECT * FROM m2;
t
---
A
B
C
(3 rows)

postgres=# INSERT INTO t1 VALUES (5, 'B');
INSERT 0 1
postgres=# DELETE FROM t1 WHERE id IN (1,3); -- delete (1,A),(3,C)
DELETE 2
postgres=# SELECT * FROM m1; -- one A left and one more B
t
---
B
B
A
(3 rows)

postgres=# SELECT * FROM m2; -- only C is removed
t
---
B
A
(2 rows)
=============================

* How it works

1. Creating matview

When a matview is created, AFTER triggers are internally created
on its base tables. When the base tables is modified (INSERT, DELETE,
UPDATE), the matview is updated incrementally in the trigger function.

When populating the matview, GROUP BY and count(*) are added to the
view definition query before this is executed for counting duplicity
of tuples in the matview. The result of count is stored in the matview
as a special column named "__ivm_count__".

2. Maintenance of matview

When base tables are modified, the change set of the table can be
referred as Ephemeral Named Relations (ENRs) thanks to Transition Table
(a feature of trigger implemented since PG10). We can calculate the diff
set of the matview by replacing the base table in the view definition
query with the ENR (at least if it is Selection-Projection -Join view).
As well as view definition time, GROUP BY and count(*) is added in order
to count the duplicity of tuples in the diff set. As a result, two diff
sets (to be deleted from and to be inserted into the matview) are
calculated, and the results are stored into temporary tables respectively.

The matiview is updated by merging these change sets. Instead of executing
DELETE or INSERT simply, the values of __ivm_count__ column in the matview
is decreased or increased. When the values becomes zero, the corresponding
tuple is deleted from the matview.

3. Access to matview

When SELECT is issued for IVM matviews defined with DISTINCT, all columns
except to __ivm_count__ of each tuple in the matview are returned. This is
correct because duplicity of tuples are eliminated by GROUP BY.

When DISTINCT is not used, SELECT for the IVM matviews returns each tuple
__ivm_count__ times. Currently, this is implemented by rewriting the SELECT
query to replace the matview RTE with a subquery which joins the matview
and generate_series function as bellow.

SELECT mv.* FROM mv, generate_series(1, mv.__ivm_count__);

__ivm_count__ column is invisible for users when "SELECT * FROM ..." is
issued, but users can see the value by specifying in target list explicitly.

====== Example 3 ======
postgres=# \d+ m1
Materialized view "public.m1"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
---------------+--------+-----------+----------+---------+----------+--------------+-------------
t | text | | | | extended | |
__ivm_count__ | bigint | | | | plain | |
View definition:
SELECT t1.t
FROM t1;
Access method: heap

postgres=# \d+ m2
Materialized view "public.m2"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
---------------+--------+-----------+----------+---------+----------+--------------+-------------
t | text | | | | extended | |
__ivm_count__ | bigint | | | | plain | |
View definition:
SELECT DISTINCT t1.t
FROM t1;
Access method: heap

postgres=# SELECT *, __ivm_count__ FROM m1;
t | __ivm_count__
---+---------------
B | 2
B | 2
A | 1
(3 rows)

postgres=# SELECT *, __ivm_count__ FROM m2;
t | __ivm_count__
---+---------------
B | 2
A | 1
(2 rows)

postgres=# EXPLAIN SELECT * FROM m1;
QUERY PLAN
------------------------------------------------------------------------------
Nested Loop (cost=0.00..61.03 rows=3000 width=2)
-> Seq Scan on m1 mv (cost=0.00..1.03 rows=3 width=10)
-> Function Scan on generate_series (cost=0.00..10.00 rows=1000 width=0)
(3 rows)
=============================

* Simple Performance Evaluation

I confirmed that "incremental" update of matviews is more effective
than the standard REFRESH by using simple exapmle. I used tables
of pgbench (SF=100) here.

Create two matviews, that is, without and with IVM.

test=# CREATE MATERIALIZED VIEW bench1 AS
SELECT aid, bid, abalance, bbalance
FROM pgbench_accounts JOIN pgbench_branches USING (bid)
WHERE abalance > 0 OR bbalance > 0;
SELECT 5001054
test=# CREATE INCREMENTAL MATERIALIZED VIEW bench2 AS
SELECT aid, bid, abalance, bbalance
FROM pgbench_accounts JOIN pgbench_branches USING (bid)
WHERE abalance > 0 OR bbalance > 0;
SELECT 5001054

The standard REFRESH of bench1 took more than 10 seconds.

test=# \timing
Timing is on.
test=# REFRESH MATERIALIZED VIEW bench1 ;
REFRESH MATERIALIZED VIEW
Time: 11210.563 ms (00:11.211)

Create an index on the IVM matview (bench2).

test=# CREATE INDEX on bench2(aid,bid);
CREATE INDEX

Updating a tuple in pgbench_accounts took 18ms. After this, bench2
was updated automatically and correctly.

test=# SELECT * FROM bench2 WHERE aid = 1;
aid | bid | abalance | bbalance
-----+-----+----------+----------
1 | 1 | 10 | 10
(1 row)

Time: 2.498 ms
test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
UPDATE 1
Time: 18.634 ms
test=# SELECT * FROM bench2 WHERE aid = 1;
aid | bid | abalance | bbalance
-----+-----+----------+----------
1 | 1 | 1000 | 10
(1 row)

However, if there is not the index on bench2, it took 4 sec, so
appropriate indexes are needed on IVM matviews.

test=# DROP INDEX bench2_aid_bid_idx ;
DROP INDEX
Time: 10.613 ms
test=# UPDATE pgbench_accounts SET abalance = 2000 WHERE aid = 1;
UPDATE 1
Time: 3931.274 ms (00:03.931)

* Restrictions on view definition

This patch is still in Work-in-Progress and there are many restrictions
on the view definition query of matviews.

The current implementation supports views including selection, projection,
and inner join with or without DISTINCT. Aggregation and GROUP BY are not
supported yet, but I plan to deal with these by the first release.
Self-join, subqueries, OUTER JOIN, CTE, window functions are not
considered well, either. I need more investigation on these type of views
although I found some papers explaining how to handle sub-queries and
outer-joins.

These unsupported views should be checked when a matview is created, but
this is not implemented yet. Hoshiai-san are working on this.

* Timing of view maintenance

This patch implements a kind of Immediate Maintenance, that is, a matview
is updated immediately when a base table is modified. On other hand, in
"Deferred Maintenance", matviews are updated after the transaction, for
example, by the user command like REFRESH.

For implementing "deferred", it is need to implement a mechanism to maintain
logs for recording changes of base tables and an algorithm to compute the
delta to be applied to matviews.

In addition, there could be another implementation of Immediate Maintenance
in which matview is updated at the end of a transaction that modified base
table, rather than in AFTER trigger. Oracle supports this type of IVM. To
implement this, we will need a mechanism to maintain change logs on base
tables as well as Deferred maintenance.

* Counting algorithm implementation

There will be also discussions on counting-algorithm implementation.
Firstly, the current patch treats "__ivm_count__" as a special column name
in a somewhat ad hoc way. This is used when maintaining and accessing matviews,
and when "SELECT * FROM ..." is issued, __ivm_count__ column is invisible for
users. Maybe this name has to be inhibited in user tables. Is it acceptable
to use such columns for IVM, and is there better way, if not?

Secondly, a matview with duplicate tuples is replaces with a subquery which
uses generate_series function. It does not have to be generate_series, and we
can make a new set returning function for this. Anyway, this internal behaviour
is visible in EXPLAIN results as shown in Example 3. Also, there is a
performance impact because estimated rows number is wrong, and what is worse,
the cost of join is not small when the size of matview is large. Therefore, we
might have to add a new plan node for selecting from matviews rather than using
such a special set returning function.

Ragards,
--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

WIP_immediate_IVM_v2.patchtext/x-diff; name=WIP_immediate_IVM_v2.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 1300c7bbaa..ddac6032ba 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1949,6 +1949,13 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       <entry>True if table or index is a partition</entry>
      </row>
 
+     <row>
+      <entry><structfield>relisivm</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if materialized view enables incremental view maintenance</entry>
+     </row>
+
      <row>
       <entry><structfield>relrewrite</structfield></entry>
       <entry><type>oid</type></entry>
diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml
index ec8847ed40..a23366a342 100644
--- a/doc/src/sgml/ref/create_materialized_view.sgml
+++ b/doc/src/sgml/ref/create_materialized_view.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
+CREATE [ INCREMENTAL ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
     [ (<replaceable>column_name</replaceable> [, ...] ) ]
     [ USING <replaceable class="parameter">method</replaceable> ]
     [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
@@ -54,6 +54,16 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
   <title>Parameters</title>
 
   <variablelist>
+   <varlistentry>
+    <term><literal>INCREMENTAL</literal></term>
+    <listitem>
+     <para>
+      If specified, a materialized view enables incremental view maintenance.
+      You can replace only the contents of a materialized view, which based rows are changed.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>IF NOT EXISTS</literal></term>
     <listitem>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 86820eecfc..f0f0e3bb84 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -891,6 +891,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
+	values[Anum_pg_class_relisivm - 1] = BoolGetDatum(rd_rel->relisivm);
 	if (relacl != (Datum) 0)
 		values[Anum_pg_class_relacl - 1] = relacl;
 	else
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 587b717242..d26cdfc057 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -910,6 +910,7 @@ index_create(Relation heapRelation,
 	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
 	indexRelation->rd_rel->relam = accessMethodObjectId;
 	indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid);
+	indexRelation->rd_rel->relisivm = false;
 
 	/*
 	 * store index's pg_class entry
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 4c1d909d38..34678666a3 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -51,6 +51,14 @@
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
 
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
+#include "commands/trigger.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
+#include "parser/parse_func.h"
+#include "nodes/print.h"
+
 
 typedef struct
 {
@@ -74,6 +82,8 @@ static bool intorel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void intorel_shutdown(DestReceiver *self);
 static void intorel_destroy(DestReceiver *self);
 
+static void CreateIvmTrigger(Oid relOid, Oid viewOid, char *matviewname, int16 type);
+static void CreateIvmTriggersOnBaseTables(Query *qry, Node *jtnode, Oid matviewOid, char* matviewname);
 
 /*
  * create_ctas_internal
@@ -109,6 +119,8 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	create->oncommit = into->onCommit;
 	create->tablespacename = into->tableSpaceName;
 	create->if_not_exists = false;
+	/* Using Materialized view only */
+	create->ivm = into->ivm;
 	create->accessMethod = into->accessMethod;
 
 	/*
@@ -239,6 +251,7 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 	List	   *rewritten;
 	PlannedStmt *plan;
 	QueryDesc  *queryDesc;
+	Query	   *copied_query;
 
 	if (stmt->if_not_exists)
 	{
@@ -319,7 +332,29 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		 * and is executed repeatedly.  (See also the same hack in EXPLAIN and
 		 * PREPARE.)
 		 */
-		rewritten = QueryRewrite(copyObject(query));
+
+		copied_query = copyObject(query);
+		if (is_matview && into->ivm)
+		{
+			TargetEntry *tle;
+			Node *node;
+			ParseState *pstate = make_parsestate(NULL);
+
+			FuncCall *fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+			fn->agg_star = true;
+
+			copied_query->groupClause = transformDistinctClause(NULL, &copied_query->targetList, copied_query->sortClause, false);
+			node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+			tle = makeTargetEntry((Expr *) node,
+									  list_length(copied_query->targetList) + 1,
+									  pstrdup("__ivm_count__"),
+									  false);
+			copied_query->targetList = lappend(copied_query->targetList, tle);
+			copied_query->hasAggs = true;
+		}
+
+		rewritten = QueryRewrite(copied_query);
 
 		/* SELECT should never rewrite to more or less than one SELECT query */
 		if (list_length(rewritten) != 1)
@@ -378,11 +413,65 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 
 		/* Restore userid and security context */
 		SetUserIdAndSecContext(save_userid, save_sec_context);
+
+
+		if (into->ivm)
+		{
+			char	   *matviewname;
+			Oid matviewOid = address.objectId;
+			Relation matviewRel = table_open(matviewOid, NoLock);
+			matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+													 RelationGetRelationName(matviewRel));
+			copied_query = copyObject(query);
+			AcquireRewriteLocks(copied_query, true, false);
+
+			CreateIvmTriggersOnBaseTables(copied_query, (Node *)copied_query->jointree, matviewOid, matviewname);
+
+			table_close(matviewRel, NoLock);
+		}
 	}
 
 	return address;
 }
 
+static void CreateIvmTriggersOnBaseTables(Query *qry, Node *jtnode, Oid matviewOid, char* matviewname)
+{
+
+	if (jtnode == NULL)
+		return;
+	if (IsA(jtnode, RangeTblRef))
+	{
+		int			rti = ((RangeTblRef *) jtnode)->rtindex;
+		RangeTblEntry *rte = rt_fetch(rti, qry->rtable);
+
+		if (rte->rtekind == RTE_RELATION)
+		{
+			CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_INSERT);
+			CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_DELETE);
+			CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_UPDATE);
+		}
+		else
+			elog(ERROR, "unsupported RTE kind: %d", (int) rte->rtekind);
+	}
+	else if (IsA(jtnode, FromExpr))
+	{
+		FromExpr   *f = (FromExpr *) jtnode;
+		ListCell   *l;
+
+		foreach(l, f->fromlist)
+			CreateIvmTriggersOnBaseTables(qry, lfirst(l), matviewOid, matviewname);
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+
+		CreateIvmTriggersOnBaseTables(qry, j->larg, matviewOid, matviewname);
+		CreateIvmTriggersOnBaseTables(qry, j->rarg, matviewOid, matviewname);
+	}
+	else
+		elog(ERROR, "unrecognized node type: %d", (int) nodeTag(jtnode));
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -547,6 +636,11 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	if (is_matview && !into->skipData)
 		SetMatViewPopulatedState(intoRelationDesc, true);
 
+	/*
+	 * Mark relisivm field, if it's a matview and into->ivm is true.
+	 */
+	if (is_matview && into->ivm)
+		SetMatViewIVMState(intoRelationDesc, true);
 	/*
 	 * Fill private fields of myState for use by later routines
 	 */
@@ -619,3 +713,74 @@ intorel_destroy(DestReceiver *self)
 {
 	pfree(self);
 }
+
+static void
+CreateIvmTrigger(Oid relOid, Oid viewOid, char *matviewname, int16 type)
+{
+	CreateTrigStmt *ivm_trigger;
+	List *transitionRels = NIL;
+	ObjectAddress address, refaddr;
+
+	refaddr.classId = RelationRelationId;
+	refaddr.objectId = viewOid;
+	refaddr.objectSubId = 0;
+
+
+	ivm_trigger = makeNode(CreateTrigStmt);
+	ivm_trigger->relation = NULL;
+	ivm_trigger->row = false;
+	ivm_trigger->timing = TRIGGER_TYPE_AFTER;
+
+	ivm_trigger->events = type;
+
+	switch (type)
+	{
+		case TRIGGER_TYPE_INSERT:
+			ivm_trigger->trigname = "IVM_trigger_ins";
+			break;
+		case TRIGGER_TYPE_DELETE:
+			ivm_trigger->trigname = "IVM_trigger_del";
+			break;
+		case TRIGGER_TYPE_UPDATE:
+			ivm_trigger->trigname = "IVM_trigger_upd";
+			break;
+	}
+
+	if (type == TRIGGER_TYPE_INSERT || type == TRIGGER_TYPE_UPDATE)
+	{
+		TriggerTransition *n = makeNode(TriggerTransition);
+		n->name = "ivm_newtable";
+		n->isNew = true;
+		n->isTable = true;
+
+		transitionRels = lappend(transitionRels, n);
+	}
+	if (type == TRIGGER_TYPE_DELETE || type == TRIGGER_TYPE_UPDATE)
+	{
+		TriggerTransition *n = makeNode(TriggerTransition);
+		n->name = "ivm_oldtable";
+		n->isNew = false;
+		n->isTable = true;
+
+		transitionRels = lappend(transitionRels, n);
+	}
+
+	ivm_trigger->funcname = SystemFuncName("IVM_immediate_maintenance");
+
+	ivm_trigger->columns = NIL;
+	ivm_trigger->transitionRels = transitionRels;
+	ivm_trigger->whenClause = NULL;
+	ivm_trigger->isconstraint = false;
+	ivm_trigger->deferrable = false;
+	ivm_trigger->initdeferred = false;
+	ivm_trigger->constrrel = NULL;
+	ivm_trigger->args = list_make1(makeString(matviewname));
+
+	address = CreateTrigger(ivm_trigger, NULL, relOid, InvalidOid, InvalidOid,
+						 InvalidOid, InvalidOid, InvalidOid, NULL, true, false);
+
+	recordDependencyOn(&address, &refaddr, DEPENDENCY_AUTO);
+
+	/* Make changes-so-far visible */
+	CommandCounterIncrement();
+}
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 537d0e8cef..05ab22715d 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -46,6 +46,11 @@
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 
+#include "utils/regproc.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_func.h"
+
 
 typedef struct
 {
@@ -65,7 +70,8 @@ static bool transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void transientrel_shutdown(DestReceiver *self);
 static void transientrel_destroy(DestReceiver *self);
 static uint64 refresh_matview_datafill(DestReceiver *dest, Query *query,
-									   const char *queryString);
+						 QueryEnvironment *queryEnv,
+						 const char *queryString);
 static char *make_temptable_name_n(char *tempname, int n);
 static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 								   int save_sec_context);
@@ -74,6 +80,9 @@ static bool is_usable_unique_index(Relation indexRel);
 static void OpenMatViewIncrementalMaintenance(void);
 static void CloseMatViewIncrementalMaintenance(void);
 
+static void apply_delta(Oid matviewOid, Oid tempOid_new, Oid tempOid_old,
+			Oid relowner, int save_sec_context);
+
 /*
  * SetMatViewPopulatedState
  *		Mark a materialized view as populated, or not.
@@ -114,6 +123,46 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
 	CommandCounterIncrement();
 }
 
+/*
+ * SetMatViewIVMState
+ *		Mark a materialized view as IVM, or not.
+ *
+ * NOTE: caller must be holding an appropriate lock on the relation.
+ */
+void
+SetMatViewIVMState(Relation relation, bool newstate)
+{
+	Relation	pgrel;
+	HeapTuple	tuple;
+
+	Assert(relation->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Update relation's pg_class entry.  Crucial side-effect: other backends
+	 * (and this one too!) are sent SI message to make them rebuild relcache
+	 * entries.
+	 */
+	pgrel = table_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(relation)));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u",
+			 RelationGetRelid(relation));
+
+	((Form_pg_class) GETSTRUCT(tuple))->relisivm = newstate;
+
+	CatalogTupleUpdate(pgrel, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	table_close(pgrel, RowExclusiveLock);
+
+	/*
+	 * Advance command counter to make the updated pg_class row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+}
+
 /*
  * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command
  *
@@ -311,7 +360,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 
 	/* Generate the data, if wanted. */
 	if (!stmt->skipData)
-		processed = refresh_matview_datafill(dest, dataQuery, queryString);
+		processed = refresh_matview_datafill(dest, dataQuery, NULL, queryString);
 
 	/* Make the matview match the newly generated data. */
 	if (concurrent)
@@ -369,6 +418,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  */
 static uint64
 refresh_matview_datafill(DestReceiver *dest, Query *query,
+						 QueryEnvironment *queryEnv,
 						 const char *queryString)
 {
 	List	   *rewritten;
@@ -405,7 +455,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 	/* Create a QueryDesc, redirecting output to our tuple receiver */
 	queryDesc = CreateQueryDesc(plan, queryString,
 								GetActiveSnapshot(), InvalidSnapshot,
-								dest, NULL, NULL, 0);
+								dest, NULL, queryEnv ? queryEnv: NULL, 0);
 
 	/* call ExecutorStart to prepare the plan for execution */
 	ExecutorStart(queryDesc, 0);
@@ -926,3 +976,366 @@ CloseMatViewIncrementalMaintenance(void)
 	matview_maintenance_depth--;
 	Assert(matview_maintenance_depth >= 0);
 }
+
+/*
+ * IVM trigger function
+ */
+
+Datum
+IVM_immediate_maintenance(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	Relation	rel;
+	Oid relid;
+	Oid matviewOid;
+	Query	   *query, *old_delta_qry, *new_delta_qry;
+	char*		matviewname = trigdata->tg_trigger->tgargs[0];
+	List	   *names;
+	Relation matviewRel;
+	int old_depth = matview_maintenance_depth;
+
+	Oid			tableSpace;
+	Oid			relowner;
+	Oid			OIDDelta_new = InvalidOid;
+	Oid			OIDDelta_old = InvalidOid;
+	DestReceiver *dest_new = NULL, *dest_old = NULL;
+	char		relpersistence;
+	Oid			save_userid;
+	int			save_sec_context;
+	int			save_nestlevel;
+
+	ParseState *pstate;
+	QueryEnvironment *queryEnv = create_queryEnv();
+
+	/* Create a dummy ParseState for addRangeTableEntryForENR */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+
+	names = stringToQualifiedNameList(matviewname);
+	matviewOid = RangeVarGetRelid(makeRangeVarFromNameList(names), AccessShareLock, true);
+	matviewRel = table_open(matviewOid, NoLock);
+
+	/* Make sure it is a materialized view. */
+	if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("\"%s\" is not a materialized view",
+						RelationGetRelationName(matviewRel))));
+
+	rel = trigdata->tg_relation;
+	relid = rel->rd_id;
+
+	query = get_view_query(matviewRel);
+
+	new_delta_qry = copyObject(query);
+	old_delta_qry = copyObject(query);
+
+	if (trigdata->tg_newtable)
+	{
+		RangeTblEntry *rte;
+		ListCell   *lc;
+
+		TargetEntry *tle;
+		Node *node;
+		FuncCall *fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+
+		EphemeralNamedRelation enr =
+			palloc(sizeof(EphemeralNamedRelationData));
+
+		enr->md.name = trigdata->tg_trigger->tgnewtable;
+		enr->md.reliddesc = trigdata->tg_relation->rd_id;
+		enr->md.tupdesc = NULL;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(trigdata->tg_newtable);
+		enr->reldata = trigdata->tg_newtable;
+		register_ENR(queryEnv, enr);
+
+		rte = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+		new_delta_qry->rtable = lappend(new_delta_qry->rtable, rte);
+
+		foreach(lc, new_delta_qry->rtable)
+		{
+			RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+			if (r->relid == relid)
+			{
+				lfirst(lc) = rte;
+				break;
+			}
+		}
+
+		fn->agg_star = true;
+		new_delta_qry->groupClause = transformDistinctClause(NULL, &new_delta_qry->targetList, new_delta_qry->sortClause, false);
+		node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+		tle = makeTargetEntry((Expr *) node,
+								  list_length(new_delta_qry->targetList) + 1,
+								  pstrdup("__ivm_count__"),
+								  false);
+		new_delta_qry->targetList = lappend(new_delta_qry->targetList, tle);
+		new_delta_qry->hasAggs = true;
+	}
+
+	if (trigdata->tg_oldtable)
+	{
+		RangeTblEntry *rte;
+		ListCell   *lc;
+
+		TargetEntry *tle;
+		Node *node;
+		FuncCall *fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+
+		EphemeralNamedRelation enr =
+			palloc(sizeof(EphemeralNamedRelationData));
+
+		enr->md.name = trigdata->tg_trigger->tgoldtable;
+		enr->md.reliddesc = trigdata->tg_relation->rd_id;
+		enr->md.tupdesc = NULL;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(trigdata->tg_oldtable);
+		enr->reldata = trigdata->tg_oldtable;
+		register_ENR(queryEnv, enr);
+
+		rte = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+		old_delta_qry->rtable = lappend(old_delta_qry->rtable, rte);
+
+		foreach(lc, old_delta_qry->rtable)
+		{
+			RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+			if (r->relid == relid)
+			{
+				lfirst(lc) = rte;
+				break;
+			}
+		}
+
+		fn->agg_star = true;
+		old_delta_qry->groupClause = transformDistinctClause(NULL, &old_delta_qry->targetList, old_delta_qry->sortClause, false);
+		node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+		tle = makeTargetEntry((Expr *) node,
+								  list_length(old_delta_qry->targetList) + 1,
+								  pstrdup("__ivm_count__"),
+								  false);
+		old_delta_qry->targetList = lappend(old_delta_qry->targetList, tle);
+		old_delta_qry->hasAggs = true;
+	}
+
+
+	/*
+	 * Check for active uses of the relation in the current transaction, such
+	 * as open scans.
+	 *
+	 * NB: We count on this to protect us against problems with refreshing the
+	 * data using TABLE_INSERT_FROZEN.
+	 */
+	CheckTableNotInUse(matviewRel, "REFRESH MATERIALIZED VIEW");
+
+	relowner = matviewRel->rd_rel->relowner;
+
+	/*
+	 * Switch to the owner's userid, so that any functions are run as that
+	 * user.  Also arrange to make GUC variable changes local to this command.
+	 * Don't lock it down too tight to create a temporary table just yet.  We
+	 * will switch modes when we are about to execute user code.
+	 */
+	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+	save_nestlevel = NewGUCNestLevel();
+
+	tableSpace = GetDefaultTablespace(RELPERSISTENCE_TEMP, false);
+	relpersistence = RELPERSISTENCE_TEMP;
+
+	/*
+	 * Create the transient table that will receive the regenerated data. Lock
+	 * it against access by any other process until commit (by which time it
+	 * will be gone).
+	 */
+	if (trigdata->tg_newtable)
+	{
+		OIDDelta_new = make_new_heap(matviewOid, tableSpace, relpersistence,
+									 ExclusiveLock);
+		LockRelationOid(OIDDelta_new, AccessExclusiveLock);
+		dest_new = CreateTransientRelDestReceiver(OIDDelta_new);
+	}
+	if (trigdata->tg_oldtable)
+	{
+		if (trigdata->tg_newtable)
+			OIDDelta_old = make_new_heap(OIDDelta_new, tableSpace, relpersistence,
+										 ExclusiveLock);
+		else
+			OIDDelta_old = make_new_heap(matviewOid, tableSpace, relpersistence,
+										 ExclusiveLock);
+		LockRelationOid(OIDDelta_old, AccessExclusiveLock);
+		dest_old = CreateTransientRelDestReceiver(OIDDelta_old);
+	}
+
+	/*
+	 * Now lock down security-restricted operations.
+	 */
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+
+	/* Generate the data. */
+	if (trigdata->tg_newtable)
+		refresh_matview_datafill(dest_new, new_delta_qry, queryEnv, NULL);
+	if (trigdata->tg_oldtable)
+		refresh_matview_datafill(dest_old, old_delta_qry, queryEnv, NULL);
+
+	PG_TRY();
+	{
+		apply_delta(matviewOid, OIDDelta_new, OIDDelta_old, relowner,
+							   save_sec_context);
+	}
+	PG_CATCH();
+	{
+		matview_maintenance_depth = old_depth;
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	table_close(matviewRel, NoLock);
+
+	/* Roll back any GUC changes */
+	AtEOXact_GUC(false, save_nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(save_userid, save_sec_context);
+
+	return PointerGetDatum(NULL);
+}
+
+static void
+apply_delta(Oid matviewOid, Oid tempOid_new, Oid tempOid_old,
+			Oid relowner, int save_sec_context)
+{
+	StringInfoData querybuf;
+	StringInfoData mvatts_buf, diffatts_buf;
+	Relation	matviewRel;
+	Relation	tempRel_new = NULL, tempRel_old = NULL;
+	char	   *matviewname;
+	char	   *tempname_new = NULL, *tempname_old = NULL;
+	int i;
+
+
+	initStringInfo(&querybuf);
+	matviewRel = table_open(matviewOid, NoLock);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	if (OidIsValid(tempOid_new))
+	{
+		tempRel_new = table_open(tempOid_new, NoLock);
+		tempname_new = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(tempRel_new)),
+												  RelationGetRelationName(tempRel_new));
+	}
+	if (OidIsValid(tempOid_old))
+	{
+		tempRel_old = table_open(tempOid_old, NoLock);
+		tempname_old = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(tempRel_old)),
+												  RelationGetRelationName(tempRel_old));
+	}
+
+	initStringInfo(&mvatts_buf);
+	initStringInfo(&diffatts_buf);
+	for (i = 0; i < matviewRel->rd_att->natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+
+		if (!strcmp(NameStr(attr->attname), "__ivm_count__"))
+			continue;
+
+		if (i > 0)
+		{
+			appendStringInfo(&mvatts_buf, ", ");
+			appendStringInfo(&diffatts_buf, ", ");
+		}
+		appendStringInfo(&mvatts_buf, "mv.%s", NameStr(attr->attname));
+		appendStringInfo(&diffatts_buf, "diff.%s", NameStr(attr->attname));
+	}
+
+	/* Open SPI context. */
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	/* Analyze the temp table with the new contents. */
+	if (tempname_new)
+	{
+		appendStringInfo(&querybuf, "ANALYZE %s", tempname_new);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+	if (tempname_old)
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf, "ANALYZE %s", tempname_old);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+
+	OpenMatViewIncrementalMaintenance();
+
+	if (tempname_old)
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf,
+						"WITH t AS ("
+						"  SELECT diff.__ivm_count__, (diff.__ivm_count__ = mv.__ivm_count__) AS for_dlt, mv.ctid"
+						"  FROM %s AS mv, %s AS diff WHERE (%s) = (%s)"
+						"), updt AS ("
+						"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ - t.__ivm_count__"
+						"  FROM t WHERE mv.ctid = t.ctid AND NOT for_dlt"
+						") DELETE FROM %s AS mv USING t WHERE mv.ctid = t.ctid AND for_dlt;",
+						matviewname, tempname_old, mvatts_buf.data, diffatts_buf.data, matviewname, matviewname);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+
+	}
+	if (tempname_new)
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf,
+						"WITH updt AS ("
+						"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ + diff.__ivm_count__"
+						"  FROM %s AS diff WHERE (%s) = (%s)"
+						"  RETURNING %s"
+						") INSERT INTO %s (SELECT * FROM %s AS diff WHERE (%s) NOT IN (SELECT * FROM updt));",
+						matviewname, tempname_new, mvatts_buf.data, diffatts_buf.data, diffatts_buf.data, matviewname, tempname_new, diffatts_buf.data);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+
+	}
+
+	/* We're done maintaining the materialized view. */
+	CloseMatViewIncrementalMaintenance();
+
+	if (OidIsValid(tempOid_new))
+		table_close(tempRel_new, NoLock);
+	if (OidIsValid(tempOid_old))
+		table_close(tempRel_old, NoLock);
+
+	table_close(matviewRel, NoLock);
+
+	/* Clean up temp tables. */
+	if (OidIsValid(tempOid_new))
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf, "DROP TABLE %s", tempname_new);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+	if (OidIsValid(tempOid_old))
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf, "DROP TABLE %s", tempname_old);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+
+	/* Close SPI context. */
+	if (SPI_finish() != SPI_OK_FINISH)
+		elog(ERROR, "SPI_finish failed");
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 78deade89b..8262ac039b 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2361,6 +2361,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(relisivm);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4f2ebe5118..608d477bd5 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2641,6 +2641,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(relisivm);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8400dd319e..7897dd9c57 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3042,6 +3042,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6c2626ee62..aa01e205c3 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1366,6 +1366,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8311b1dd46..7cae68218b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -418,6 +418,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target create_mv_target
+%type <boolean>	incremental
 
 %type <defelt>	createfunc_opt_item common_func_opt_item dostmt_opt_item
 %type <fun_param> func_arg func_arg_with_default table_func_column aggr_arg
@@ -645,7 +646,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
 	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
-	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+	INCLUDING INCREMENT INCREMENTAL INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
 	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
 	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -4054,30 +4055,32 @@ opt_with_data:
  *****************************************************************************/
 
 CreateMatViewStmt:
-		CREATE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
+		CREATE OptNoLog incremental MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $7;
-					ctas->into = $5;
+					ctas->query = $8;
+					ctas->into = $6;
 					ctas->relkind = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = false;
 					/* cram additional flags into the IntoClause */
-					$5->rel->relpersistence = $2;
-					$5->skipData = !($8);
+					$6->rel->relpersistence = $2;
+					$6->skipData = !($9);
+					$6->ivm = $3;
 					$$ = (Node *) ctas;
 				}
-		| CREATE OptNoLog MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
+		| CREATE OptNoLog incremental MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $10;
-					ctas->into = $8;
+					ctas->query = $11;
+					ctas->into = $9;
 					ctas->relkind = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = true;
 					/* cram additional flags into the IntoClause */
-					$8->rel->relpersistence = $2;
-					$8->skipData = !($11);
+					$9->rel->relpersistence = $2;
+					$9->skipData = !($12);
+					$9->ivm = $3;
 					$$ = (Node *) ctas;
 				}
 		;
@@ -4094,9 +4097,14 @@ create_mv_target:
 					$$->tableSpaceName = $5;
 					$$->viewQuery = NULL;		/* filled at analysis time */
 					$$->skipData = false;		/* might get changed later */
+					$$->ivm = false;
 				}
 		;
 
+incremental:	INCREMENTAL				{ $$ = true; }
+				| /*EMPTY*/				{ $$ = false; }
+		;
+
 OptNoLog:	UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
 		;
@@ -15128,6 +15136,7 @@ unreserved_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 77a48b039d..740ce89c34 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -37,6 +37,7 @@
 #include "utils/syscache.h"
 #include "utils/varlena.h"
 
+#include "commands/matview.h"
 
 #define MAX_FUZZY_DISTANCE				3
 
@@ -1238,6 +1239,7 @@ addRangeTableEntry(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -1317,6 +1319,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -2605,6 +2608,9 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 	{
 		Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
 
+		if (!strcmp("__ivm_count__", NameStr(attr->attname)) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		if (attr->attisdropped)
 		{
 			if (include_dropped)
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 7df2b6154c..5385a038b1 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -765,7 +765,8 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 														attr->atttypmod))));
 	}
 
-	if (i != resultDesc->natts)
+	/* No check for materialized views since this could have special columns for IVM */
+	if ((!isSelect || requireColumnNameMatch) && i != resultDesc->natts)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 isSelect ?
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index ea40c28733..5b63c16616 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -41,6 +41,8 @@
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 
+#include "parser/parser.h"
+#include "commands/matview.h"
 
 /* We use a list of these to detect recursion in RewriteQuery */
 typedef struct rewrite_event
@@ -1597,6 +1599,50 @@ ApplyRetrieveRule(Query *parsetree,
 	if (rule->qual != NULL)
 		elog(ERROR, "cannot handle qualified ON SELECT rule");
 
+	if (RelationIsIVM(relation))
+	{
+		rule_action = copyObject(linitial(rule->actions));
+
+		if (!rule_action->distinctClause)
+		{
+			StringInfoData str;
+			RawStmt *raw;
+			Query *sub;
+
+			if (rule_action->hasDistinctOn)
+				elog(ERROR, "DISTINCT ON is not supported in IVM");
+
+			initStringInfo(&str);
+			appendStringInfo(&str, "SELECT mv.*, __ivm_count__ FROM %s mv, generate_series(1, mv.__ivm_count__)",
+						quote_qualified_identifier(get_namespace_name(RelationGetNamespace(relation)),
+													RelationGetRelationName(relation)));
+
+			raw = (RawStmt*)linitial(raw_parser(str.data));
+			sub = transformStmt(make_parsestate(NULL),raw->stmt);
+
+			rte = rt_fetch(rt_index, parsetree->rtable);
+
+			rte->rtekind = RTE_SUBQUERY;
+			rte->subquery = sub;
+			rte->security_barrier = RelationIsSecurityView(relation);
+			/* Clear fields that should not be set in a subquery RTE */
+			rte->relid = InvalidOid;
+			rte->relkind = 0;
+			rte->rellockmode = 0;
+			rte->tablesample = NULL;
+			rte->inh = false;			/* must not be set for a subquery */
+
+			rte->requiredPerms = 0;		/* no permission check on subquery itself */
+			rte->checkAsUser = InvalidOid;
+			rte->selectedCols = NULL;
+			rte->insertedCols = NULL;
+			rte->updatedCols = NULL;
+			rte->extraUpdatedCols = NULL;
+		}
+
+		return parsetree;
+	}
+
 	if (rt_index == parsetree->resultRelation)
 	{
 		/*
@@ -1906,7 +1952,8 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
 		 * In that case this test would need to be postponed till after we've
 		 * opened the rel, so that we could check its state.
 		 */
-		if (rte->relkind == RELKIND_MATVIEW)
+		if (rte->relkind == RELKIND_MATVIEW &&
+			(!rte->relisivm || MatViewIncrementalMaintenanceIsEnabled() || parsetree->commandType != CMD_SELECT))
 			continue;
 
 		/*
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index c13c08a97b..296cc53ffd 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1844,6 +1844,30 @@ get_rel_relispartition(Oid relid)
 		return false;
 }
 
+/*
+ * get_rel_relisivm
+ *
+ *		Returns the relisivm flag associated with a given relation.
+ */
+bool
+get_rel_relisivm(Oid relid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp);
+		bool		result;
+
+		result = reltup->relisivm;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return false;
+}
+
 /*
  * get_rel_tablespace
  *
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2b992d7832..d80bb30696 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1861,6 +1861,8 @@ formrdesc(const char *relationName, Oid relationReltype,
 
 	/* ... and they're always populated, too */
 	relation->rd_rel->relispopulated = true;
+	/* ... and they're always no ivm, too */
+	relation->rd_rel->relisivm = false;
 
 	relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
 	relation->rd_rel->relpages = 0;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5e38f46399..d285ab6740 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -998,6 +998,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"INCREMENTAL MATERIALIZED VIEW", NULL, NULL, &Query_for_list_of_matviews},
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2482,7 +2483,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
-		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
+		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW", "INCREMENTAL MATERIALIZED VIEW");
 	/* Complete PARTITION BY with RANGE ( or LIST ( or ... */
 	else if (TailMatches("PARTITION", "BY"))
 		COMPLETE_WITH("RANGE (", "LIST (", "HASH (");
@@ -2691,13 +2692,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches("CREATE", "MATERIALIZED"))
+	else if (Matches("CREATE", "MATERIALIZED") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED"))
 		COMPLETE_WITH("VIEW");
-	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	/* Complete CREATE MATERIALIZED VIEW <name> with AS  */
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny) ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH("SELECT");
 
 /* CREATE EVENT TRIGGER */
diff --git a/src/include/catalog/pg_class.dat b/src/include/catalog/pg_class.dat
index 9bcf28676d..7deda405af 100644
--- a/src/include/catalog/pg_class.dat
+++ b/src/include/catalog/pg_class.dat
@@ -27,7 +27,7 @@
   relpersistence => 'p', relkind => 'r', relnatts => '31', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 { oid => '1249',
@@ -37,7 +37,7 @@
   relpersistence => 'p', relkind => 'r', relnatts => '25', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 { oid => '1255',
@@ -47,17 +47,17 @@
   relpersistence => 'p', relkind => 'r', relnatts => '29', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 { oid => '1259',
   relname => 'pg_class', reltype => 'pg_class', relam => 'heap',
   relfilenode => '0', relpages => '0', reltuples => '0', relallvisible => '0',
   reltoastrelid => '0', relhasindex => 'f', relisshared => 'f',
-  relpersistence => 'p', relkind => 'r', relnatts => '33', relchecks => '0',
+  relpersistence => 'p', relkind => 'r', relnatts => '34', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 090b6ba907..ff535f5504 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
 	/* is relation a partition? */
 	bool		relispartition;
 
+	/* is relation a matview with ivm? */
+	bool		relisivm;
+
 	/* heap for rewrite during DDL, link to original rel */
 	Oid			relrewrite BKI_DEFAULT(0);
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87335248a0..5de996a72c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10677,4 +10677,9 @@
   proname => 'pg_partition_root', prorettype => 'regclass',
   proargtypes => 'regclass', prosrc => 'pg_partition_root' },
 
+# IVM
+{ oid => '784', descr => 'ivm trigger',
+  proname => 'IVM_immediate_maintenance', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_maintenance' },
+
 ]
diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h
index edf04bf415..8dd8193d9c 100644
--- a/src/include/commands/matview.h
+++ b/src/include/commands/matview.h
@@ -23,6 +23,8 @@
 
 extern void SetMatViewPopulatedState(Relation relation, bool newstate);
 
+extern void SetMatViewIVMState(Relation relation, bool newstate);
+
 extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										ParamListInfo params, char *completionTag);
 
@@ -30,4 +32,6 @@ extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
 
 extern bool MatViewIncrementalMaintenanceIsEnabled(void);
 
+extern Datum IVM_immediate_maintenance(PG_FUNCTION_ARGS);
+
 #endif							/* MATVIEW_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 94ded3c135..5c8a5ae3ca 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1002,6 +1002,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		relisivm;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -2059,6 +2060,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		ivm;			/* incremental view maintenance is used by materialized view */
 } CreateStmt;
 
 /* ----------
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 7c278c0e56..0aaef1ef15 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -117,6 +117,7 @@ typedef struct IntoClause
 	char	   *tableSpaceName; /* table space to use, or NULL */
 	Node	   *viewQuery;		/* materialized view's SELECT query */
 	bool		skipData;		/* true for WITH NO DATA */
+	bool		ivm;			/* true for WITH IVM */
 } IntoClause;
 
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 00ace8425e..d682ee11cc 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -198,6 +198,7 @@ PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
 PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
+PG_KEYWORD("incremental", INCREMENTAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
 PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index c8df5bff9f..8fd919349e 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -129,6 +129,7 @@ extern Oid	get_rel_namespace(Oid relid);
 extern Oid	get_rel_type_id(Oid relid);
 extern char get_rel_relkind(Oid relid);
 extern bool get_rel_relispartition(Oid relid);
+extern bool get_rel_relisivm(Oid relid);
 extern Oid	get_rel_tablespace(Oid relid);
 extern char get_rel_persistence(Oid relid);
 extern Oid	get_transform_fromsql(Oid typid, Oid langid, List *trftypes);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d7f33abce3..057f38d5de 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -562,6 +562,8 @@ typedef struct ViewOptions
  */
 #define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated)
 
+#define RelationIsIVM(relation) ((relation)->rd_rel->relisivm)
+
 /*
  * RelationIsAccessibleInLogicalDecoding
  *		True if we need to log enough information to have access via
#15Jim Finnerty
jfinnert@amazon.com
In reply to: Yugo Nagata (#14)
Re: Implementing Incremental View Maintenance

Hi Yugo,

I'd like to compare the performance of your MV refresh algorithm versus
an approach that logs changes into an mv log table, and can then apply the
changes at some later point in time. I'd like to handle the materialized
join view (mjv) case first, specifically a 2-way left outer join, with a UDF
in the SELECT list of the mjv.

Does your refresh algorithm handle mjv's with connected join graphs that
consist entirely of inner and left outer joins?

If so, I'd like to measure the overhead of your refresh algorithm on
pgbench, modified to include an mjv, versus a (hand coded) incremental
maintenance algorithm that uses mv log tables populated by ordinary
triggers. We may also want to look at capturing the deltas using logical
replication, which ought to be faster than a trigger-based solution.

I have someone available to do the performance testing for another 2
months, so if you can connect with me off-list to coordinate, we can set up
the performance experiments and run them on our AWS clusters.

best regards,

/Jim F

-----
Jim Finnerty, AWS, Amazon Aurora PostgreSQL
--
Sent from: http://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

#16Yugo Nagata
nagata@sraoss.co.jp
In reply to: Jim Finnerty (#15)
Re: Implementing Incremental View Maintenance

Hi Jim,

On Fri, 21 Jun 2019 08:41:11 -0700 (MST)
Jim Finnerty <jfinnert@amazon.com> wrote:

Hi Yugo,

I'd like to compare the performance of your MV refresh algorithm versus
an approach that logs changes into an mv log table, and can then apply the
changes at some later point in time. I'd like to handle the materialized
join view (mjv) case first, specifically a 2-way left outer join, with a UDF
in the SELECT list of the mjv.

Do you mean you have your implementation of IVM that using log tables?
I'm so interested in this, and I would appreciate it if you explain the
detail.

Does your refresh algorithm handle mjv's with connected join graphs that
consist entirely of inner and left outer joins?

If so, I'd like to measure the overhead of your refresh algorithm on
pgbench, modified to include an mjv, versus a (hand coded) incremental
maintenance algorithm that uses mv log tables populated by ordinary
triggers. We may also want to look at capturing the deltas using logical
replication, which ought to be faster than a trigger-based solution.

In the current our implementation, outer joins is not yet supported though
we plan to handle this in future. So,we would not be able to compare these
directly in the same workload in the current status.

However, the current our implementation supports only the way to update
materialized views in a trigger, and the performance of modifying base tables
will be lower than the approach which uses log tables. This is because queries
to update materialized views are issued in the trigger. This is not only a
overhead itself, but also takes a lock on a materialized view, which has an ]
impact on concurrent execution performance.

In the previous our PoC, we implemented IVM using log tables, in which logs are
captured by triggers and materialized views are update incrementally by a user
command[1]https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/. However, to implement log table approach, we need a infrastructure
to maintain these logs. For example, which logs are necessary and which logs
can be discarded, etc. We thought this is not trivial work, so we decided to
start from the current approach which doesn't use log tables. We are now
preparing to implement this in the next step because this is also needed to
support deferred maintenance of views.

[1]: https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/

I agree that capturing the deltas using logical decoding will be faster than
using a trigger although we haven't yet consider this well.

Best regadrds,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

#17Yugo Nagata
nagata@sraoss.co.jp
In reply to: Yugo Nagata (#14)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

Attached is a WIP patch of IVM which supports some aggregate functions.

Currently, only count and sum are supported. Avg, min, or max is not supported
although I think supporting this would not be so hard.

As a restriction, expressions specified in GROUP BY must appear in the target
list of views because tuples to be updated in MV are identified by using this
group keys.

In the case of views without aggregate functions, only the number of tuple
duplicates (__ivm_count__) are updated at incremental maintenance. On the other
hand, in the case of vies with aggregations, the aggregated values are also
updated. The way of update depends the kind of aggregate function.

In the case of sum (or agg functions except to count), NULL in input values is
ignored, and this returns a null value when no rows are selected. To support
this specification, the number of not-NULL input values is counted and stored
in MV as a hidden column whose name is like __ivm_count_sum__, for example.

In the case of count, this returns zero when no rows are selected, and count(*)
doesn't ignore NULL input. These specification are also supported.

Tuples to be updated in MV are identified by using keys specified by GROUP BY
clause. However, in the case of aggregation without GROUP BY, there is only one
tuple in the view, so keys are not uses to identify tuples.

In addition, a race condition which occurred in the previous version is
prevented in this patch. In the previous version, when two translocations
change a base tables concurrently, an anormal update of MV was possible because
a change in one transaction was not visible for another transaction even in
READ COMMITTED level.

To prevent this, I fix this to take a lock in early stage of view maintenance
to wait for concurrent transactions which are updating the same MV end. Also,
we have to get the latest snapshot before computting delta tables because any
changes which occurs in other transaction during lock waiting is not visible
even in READ COMMITTED level.

In REPEATABLE READ or SERIALIZABLE level, don't wait a lock, and raise an error
immediately to prevent anormal update. These solutions might be ugly, but
something to prevent anormal update is anyway necessary. There may be better
way.

Moreover, some regression test are added for aggregate functions support.
This is Hoshiai-san's work.

Although the code is not refined yet and I will need a deal of refactoring
and reorganizing, I submitted this to share the current status.

* Exapmle (from regression test)

=======================================================================
(1) creating tables

CREATE TABLE mv_base_a (i int, j int);
INSERT INTO mv_base_a VALUES
(1,10),
(2,20),
(3,30),
(4,40),
(5,50);

(2) Views sith SUM() and COUNT() aggregation function

BEGIN;
CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i) FROM mv_base_a GROUP BY i;
SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
i | sum | count
---+-----+-------
1 | 10 | 1
2 | 20 | 1
3 | 30 | 1
4 | 40 | 1
5 | 50 | 1
(5 rows)

INSERT INTO mv_base_a VALUES(2,100);
SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
i | sum | count
---+-----+-------
1 | 10 | 1
2 | 120 | 2
3 | 30 | 1
4 | 40 | 1
5 | 50 | 1
(5 rows)

UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
i | sum | count
---+-----+-------
1 | 10 | 1
2 | 220 | 2
3 | 30 | 1
4 | 40 | 1
5 | 50 | 1
(5 rows)

DELETE FROM mv_base_a WHERE (i,j) = (2,200);
SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
i | sum | count
---+-----+-------
1 | 10 | 1
2 | 20 | 1
3 | 30 | 1
4 | 40 | 1
5 | 50 | 1
(5 rows)

ROLLBACK;

(3) Views with COUNT(*) aggregation function

BEGIN;
CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j),COUNT(*) FROM mv_base_a GROUP BY i;
SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
i | sum | count
---+-----+-------
1 | 10 | 1
2 | 20 | 1
3 | 30 | 1
4 | 40 | 1
5 | 50 | 1
(5 rows)

INSERT INTO mv_base_a VALUES(2,100);
SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
i | sum | count
---+-----+-------
1 | 10 | 1
2 | 120 | 2
3 | 30 | 1
4 | 40 | 1
5 | 50 | 1
(5 rows)

ROLLBACK;

(4) Views with aggregation function without GROUP clause

BEGIN;
CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j)FROM mv_base_a;
SELECT * FROM mv_ivm_group ORDER BY 1;
sum
-----
150
(1 row)

INSERT INTO mv_base_a VALUES(6,20);
SELECT * FROM mv_ivm_group ORDER BY 1;
sum
-----
170
(1 row)
=======================================================================

On Thu, 20 Jun 2019 16:44:10 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

Hi hackers,

Thank you for your many questions and feedbacks at PGCon 2019.
Attached is the patch rebased for the current master branch.

Regards,
Yugo Nagata

On Tue, 14 May 2019 15:46:48 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

On Mon, 1 Apr 2019 12:11:22 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

On Thu, 27 Dec 2018 21:57:26 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

Hi,

I would like to implement Incremental View Maintenance (IVM) on PostgreSQL.

I am now working on an initial patch for implementing IVM on PostgreSQL.
This enables materialized views to be updated incrementally after one
of their base tables is modified.

Attached is a WIP patch of Incremental View Maintenance (IVM).
Major part is written by me, and changes in syntax and pg_class
are Hoshiai-san's work.

Although this is sill a draft patch in work-in-progress, any
suggestions or thoughts would be appreciated.

* What it is

This allows a kind of Immediate Maintenance of materialized views. if a
materialized view is created by CRATE INCREMENTAL MATERIALIZED VIEW command,
the contents of the mateview is updated automatically and incrementally
after base tables are updated. Noted this syntax is just tentative, so it
may be changed.

====== Example 1 ======
postgres=# CREATE INCREMENTAL MATERIALIZED VIEW m AS SELECT * FROM t0;
SELECT 3
postgres=# SELECT * FROM m;
i
---
3
2
1
(3 rows)

postgres=# INSERT INTO t0 VALUES (4);
INSERT 0 1
postgres=# SELECt * FROM m; -- automatically updated
i
---
3
2
1
4
(4 rows)
=============================

This implementation also supports matviews including duplicate tuples or
DISTINCT clause in its view definition query. For example, even if a matview
is defined with DISTINCT to remove duplication of tuples in a base table, this
can perform incremental update of the matview properly. That is, the contents
of the matview doesn't change when exiting tuples are inserted into the base
tables, and a tuple in the matview is deleted only when duplicity of the
corresponding tuple in the base table becomes zero.

This is due to "colunting alogorithm" in which the number of each tuple is
stored in matviews as a special column value.

====== Example 2 ======
postgres=# SELECT * FROM t1;
id | t
----+---
1 | A
2 | B
3 | C
4 | A
(4 rows)

postgres=# CREATE INCREMENTAL MATERIALIZED VIEW m1 AS SELECT t FROM t1;
SELECT 3
postgres=# CREATE INCREMENTAL MATERIALIZED VIEW m2 AS SELECT DISTINCT t FROM t1;
SELECT 3
postgres=# SELECT * FROM m1; -- with duplicity
t
---
A
A
C
B
(4 rows)

postgres=# SELECT * FROM m2;
t
---
A
B
C
(3 rows)

postgres=# INSERT INTO t1 VALUES (5, 'B');
INSERT 0 1
postgres=# DELETE FROM t1 WHERE id IN (1,3); -- delete (1,A),(3,C)
DELETE 2
postgres=# SELECT * FROM m1; -- one A left and one more B
t
---
B
B
A
(3 rows)

postgres=# SELECT * FROM m2; -- only C is removed
t
---
B
A
(2 rows)
=============================

* How it works

1. Creating matview

When a matview is created, AFTER triggers are internally created
on its base tables. When the base tables is modified (INSERT, DELETE,
UPDATE), the matview is updated incrementally in the trigger function.

When populating the matview, GROUP BY and count(*) are added to the
view definition query before this is executed for counting duplicity
of tuples in the matview. The result of count is stored in the matview
as a special column named "__ivm_count__".

2. Maintenance of matview

When base tables are modified, the change set of the table can be
referred as Ephemeral Named Relations (ENRs) thanks to Transition Table
(a feature of trigger implemented since PG10). We can calculate the diff
set of the matview by replacing the base table in the view definition
query with the ENR (at least if it is Selection-Projection -Join view).
As well as view definition time, GROUP BY and count(*) is added in order
to count the duplicity of tuples in the diff set. As a result, two diff
sets (to be deleted from and to be inserted into the matview) are
calculated, and the results are stored into temporary tables respectively.

The matiview is updated by merging these change sets. Instead of executing
DELETE or INSERT simply, the values of __ivm_count__ column in the matview
is decreased or increased. When the values becomes zero, the corresponding
tuple is deleted from the matview.

3. Access to matview

When SELECT is issued for IVM matviews defined with DISTINCT, all columns
except to __ivm_count__ of each tuple in the matview are returned. This is
correct because duplicity of tuples are eliminated by GROUP BY.

When DISTINCT is not used, SELECT for the IVM matviews returns each tuple
__ivm_count__ times. Currently, this is implemented by rewriting the SELECT
query to replace the matview RTE with a subquery which joins the matview
and generate_series function as bellow.

SELECT mv.* FROM mv, generate_series(1, mv.__ivm_count__);

__ivm_count__ column is invisible for users when "SELECT * FROM ..." is
issued, but users can see the value by specifying in target list explicitly.

====== Example 3 ======
postgres=# \d+ m1
Materialized view "public.m1"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
---------------+--------+-----------+----------+---------+----------+--------------+-------------
t | text | | | | extended | |
__ivm_count__ | bigint | | | | plain | |
View definition:
SELECT t1.t
FROM t1;
Access method: heap

postgres=# \d+ m2
Materialized view "public.m2"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
---------------+--------+-----------+----------+---------+----------+--------------+-------------
t | text | | | | extended | |
__ivm_count__ | bigint | | | | plain | |
View definition:
SELECT DISTINCT t1.t
FROM t1;
Access method: heap

postgres=# SELECT *, __ivm_count__ FROM m1;
t | __ivm_count__
---+---------------
B | 2
B | 2
A | 1
(3 rows)

postgres=# SELECT *, __ivm_count__ FROM m2;
t | __ivm_count__
---+---------------
B | 2
A | 1
(2 rows)

postgres=# EXPLAIN SELECT * FROM m1;
QUERY PLAN
------------------------------------------------------------------------------
Nested Loop (cost=0.00..61.03 rows=3000 width=2)
-> Seq Scan on m1 mv (cost=0.00..1.03 rows=3 width=10)
-> Function Scan on generate_series (cost=0.00..10.00 rows=1000 width=0)
(3 rows)
=============================

* Simple Performance Evaluation

I confirmed that "incremental" update of matviews is more effective
than the standard REFRESH by using simple exapmle. I used tables
of pgbench (SF=100) here.

Create two matviews, that is, without and with IVM.

test=# CREATE MATERIALIZED VIEW bench1 AS
SELECT aid, bid, abalance, bbalance
FROM pgbench_accounts JOIN pgbench_branches USING (bid)
WHERE abalance > 0 OR bbalance > 0;
SELECT 5001054
test=# CREATE INCREMENTAL MATERIALIZED VIEW bench2 AS
SELECT aid, bid, abalance, bbalance
FROM pgbench_accounts JOIN pgbench_branches USING (bid)
WHERE abalance > 0 OR bbalance > 0;
SELECT 5001054

The standard REFRESH of bench1 took more than 10 seconds.

test=# \timing
Timing is on.
test=# REFRESH MATERIALIZED VIEW bench1 ;
REFRESH MATERIALIZED VIEW
Time: 11210.563 ms (00:11.211)

Create an index on the IVM matview (bench2).

test=# CREATE INDEX on bench2(aid,bid);
CREATE INDEX

Updating a tuple in pgbench_accounts took 18ms. After this, bench2
was updated automatically and correctly.

test=# SELECT * FROM bench2 WHERE aid = 1;
aid | bid | abalance | bbalance
-----+-----+----------+----------
1 | 1 | 10 | 10
(1 row)

Time: 2.498 ms
test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
UPDATE 1
Time: 18.634 ms
test=# SELECT * FROM bench2 WHERE aid = 1;
aid | bid | abalance | bbalance
-----+-----+----------+----------
1 | 1 | 1000 | 10
(1 row)

However, if there is not the index on bench2, it took 4 sec, so
appropriate indexes are needed on IVM matviews.

test=# DROP INDEX bench2_aid_bid_idx ;
DROP INDEX
Time: 10.613 ms
test=# UPDATE pgbench_accounts SET abalance = 2000 WHERE aid = 1;
UPDATE 1
Time: 3931.274 ms (00:03.931)

* Restrictions on view definition

This patch is still in Work-in-Progress and there are many restrictions
on the view definition query of matviews.

The current implementation supports views including selection, projection,
and inner join with or without DISTINCT. Aggregation and GROUP BY are not
supported yet, but I plan to deal with these by the first release.
Self-join, subqueries, OUTER JOIN, CTE, window functions are not
considered well, either. I need more investigation on these type of views
although I found some papers explaining how to handle sub-queries and
outer-joins.

These unsupported views should be checked when a matview is created, but
this is not implemented yet. Hoshiai-san are working on this.

* Timing of view maintenance

This patch implements a kind of Immediate Maintenance, that is, a matview
is updated immediately when a base table is modified. On other hand, in
"Deferred Maintenance", matviews are updated after the transaction, for
example, by the user command like REFRESH.

For implementing "deferred", it is need to implement a mechanism to maintain
logs for recording changes of base tables and an algorithm to compute the
delta to be applied to matviews.

In addition, there could be another implementation of Immediate Maintenance
in which matview is updated at the end of a transaction that modified base
table, rather than in AFTER trigger. Oracle supports this type of IVM. To
implement this, we will need a mechanism to maintain change logs on base
tables as well as Deferred maintenance.

* Counting algorithm implementation

There will be also discussions on counting-algorithm implementation.
Firstly, the current patch treats "__ivm_count__" as a special column name
in a somewhat ad hoc way. This is used when maintaining and accessing matviews,
and when "SELECT * FROM ..." is issued, __ivm_count__ column is invisible for
users. Maybe this name has to be inhibited in user tables. Is it acceptable
to use such columns for IVM, and is there better way, if not?

Secondly, a matview with duplicate tuples is replaces with a subquery which
uses generate_series function. It does not have to be generate_series, and we
can make a new set returning function for this. Anyway, this internal behaviour
is visible in EXPLAIN results as shown in Example 3. Also, there is a
performance impact because estimated rows number is wrong, and what is worse,
the cost of join is not small when the size of matview is large. Therefore, we
might have to add a new plan node for selecting from matviews rather than using
such a special set returning function.

Ragards,
--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

WIP_immediate_IVM_v3.patchtext/x-diff; name=WIP_immediate_IVM_v3.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index f2b9d404cb..e28b07698d 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1952,6 +1952,13 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       <entry>True if table or index is a partition</entry>
      </row>
 
+     <row>
+      <entry><structfield>relisivm</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if materialized view enables incremental view maintenance</entry>
+     </row>
+
      <row>
       <entry><structfield>relrewrite</structfield></entry>
       <entry><type>oid</type></entry>
diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml
index ec8847ed40..a23366a342 100644
--- a/doc/src/sgml/ref/create_materialized_view.sgml
+++ b/doc/src/sgml/ref/create_materialized_view.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
+CREATE [ INCREMENTAL ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
     [ (<replaceable>column_name</replaceable> [, ...] ) ]
     [ USING <replaceable class="parameter">method</replaceable> ]
     [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
@@ -54,6 +54,16 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
   <title>Parameters</title>
 
   <variablelist>
+   <varlistentry>
+    <term><literal>INCREMENTAL</literal></term>
+    <listitem>
+     <para>
+      If specified, a materialized view enables incremental view maintenance.
+      You can replace only the contents of a materialized view, which based rows are changed.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>IF NOT EXISTS</literal></term>
     <listitem>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 86820eecfc..f0f0e3bb84 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -891,6 +891,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
+	values[Anum_pg_class_relisivm - 1] = BoolGetDatum(rd_rel->relisivm);
 	if (relacl != (Datum) 0)
 		values[Anum_pg_class_relacl - 1] = relacl;
 	else
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bb60b23093..0e22a643d7 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -910,6 +910,7 @@ index_create(Relation heapRelation,
 	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
 	indexRelation->rd_rel->relam = accessMethodObjectId;
 	indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid);
+	indexRelation->rd_rel->relisivm = false;
 
 	/*
 	 * store index's pg_class entry
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 4c1d909d38..829c2b7bcd 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -51,6 +51,16 @@
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
 
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
+#include "commands/trigger.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
+#include "parser/parse_func.h"
+#include "nodes/print.h"
+#include "optimizer/optimizer.h"
+#include "commands/defrem.h"
+
 
 typedef struct
 {
@@ -74,6 +84,8 @@ static bool intorel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void intorel_shutdown(DestReceiver *self);
 static void intorel_destroy(DestReceiver *self);
 
+static void CreateIvmTrigger(Oid relOid, Oid viewOid, char *matviewname, int16 type);
+static void CreateIvmTriggersOnBaseTables(Query *qry, Node *jtnode, Oid matviewOid, char* matviewname);
 
 /*
  * create_ctas_internal
@@ -109,6 +121,8 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	create->oncommit = into->onCommit;
 	create->tablespacename = into->tableSpaceName;
 	create->if_not_exists = false;
+	/* Using Materialized view only */
+	create->ivm = into->ivm;
 	create->accessMethod = into->accessMethod;
 
 	/*
@@ -239,6 +253,7 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 	List	   *rewritten;
 	PlannedStmt *plan;
 	QueryDesc  *queryDesc;
+	Query	   *copied_query;
 
 	if (stmt->if_not_exists)
 	{
@@ -319,7 +334,97 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		 * and is executed repeatedly.  (See also the same hack in EXPLAIN and
 		 * PREPARE.)
 		 */
-		rewritten = QueryRewrite(copyObject(query));
+
+		copied_query = copyObject(query);
+		if (is_matview && into->ivm)
+		{
+			TargetEntry *tle;
+			Node *node;
+			ParseState *pstate = make_parsestate(NULL);
+			FuncCall *fn;
+
+			/* group keys must be in targetlist */
+			if (copied_query->groupClause)
+			{
+				ListCell *lc;
+				foreach(lc, copied_query->groupClause)
+				{
+					SortGroupClause *scl = (SortGroupClause *) lfirst(lc);
+					TargetEntry *tle = get_sortgroupclause_tle(scl, copied_query->targetList);
+
+					if (tle->resjunk)
+						elog(ERROR, "GROUP BY expression must appear in select list for incremental materialized views");
+				}
+			}
+			else if (!copied_query->hasAggs)
+				copied_query->groupClause = transformDistinctClause(NULL, &copied_query->targetList, copied_query->sortClause, false);
+
+			if (copied_query->hasAggs)
+			{
+				ListCell *lc;
+				List *agg_counts = NIL;
+				AttrNumber next_resno = list_length(copied_query->targetList) + 1;
+				Const	*dmy_arg = makeConst(INT4OID,
+											 -1,
+											 InvalidOid,
+											 sizeof(int32),
+											 Int32GetDatum(1),
+											 false,
+											 true); /* pass by value */
+
+				foreach(lc, copied_query->targetList)
+				{
+					TargetEntry *tle = (TargetEntry *) lfirst(lc);
+					TargetEntry *tle_count;
+
+
+					if (IsA(tle->expr, Aggref))
+					{
+						Aggref *aggref = (Aggref *) tle->expr;
+						const char *aggname = get_func_name(aggref->aggfnoid);
+
+						/* XXX: need some generalization */
+						if (strcmp(aggname, "sum") !=0 && strcmp(aggname, "count") != 0)
+							elog(ERROR, "Aggrege function %s is not supported", aggname);
+
+						/* For aggregate functions except to count, add count func with the same arg parameters. */
+						if (strcmp(aggname, "count") != 0)
+						{
+							fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+
+							/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+							node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+							((Aggref *)node)->args = aggref->args;
+
+							tle_count = makeTargetEntry((Expr *) node,
+														next_resno,
+														pstrdup(makeObjectName("__ivm_count",tle->resname, "_")),
+														false);
+							agg_counts = lappend(agg_counts, tle_count);
+							next_resno++;
+						}
+
+					}
+				}
+				copied_query->targetList = list_concat(copied_query->targetList, agg_counts);
+
+			}
+
+			/* Add count(*) for counting algorithm */
+			fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+			fn->agg_star = true;
+
+			node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+			tle = makeTargetEntry((Expr *) node,
+								  	list_length(copied_query->targetList) + 1,
+								  	pstrdup("__ivm_count__"),
+								  	false);
+			copied_query->targetList = lappend(copied_query->targetList, tle);
+			copied_query->hasAggs = true;
+		}
+
+		rewritten = QueryRewrite(copied_query);
 
 		/* SELECT should never rewrite to more or less than one SELECT query */
 		if (list_length(rewritten) != 1)
@@ -378,11 +483,65 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 
 		/* Restore userid and security context */
 		SetUserIdAndSecContext(save_userid, save_sec_context);
+
+
+		if (into->ivm)
+		{
+			char	   *matviewname;
+			Oid matviewOid = address.objectId;
+			Relation matviewRel = table_open(matviewOid, NoLock);
+			matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+													 RelationGetRelationName(matviewRel));
+			copied_query = copyObject(query);
+			AcquireRewriteLocks(copied_query, true, false);
+
+			CreateIvmTriggersOnBaseTables(copied_query, (Node *)copied_query->jointree, matviewOid, matviewname);
+
+			table_close(matviewRel, NoLock);
+		}
 	}
 
 	return address;
 }
 
+static void CreateIvmTriggersOnBaseTables(Query *qry, Node *jtnode, Oid matviewOid, char* matviewname)
+{
+
+	if (jtnode == NULL)
+		return;
+	if (IsA(jtnode, RangeTblRef))
+	{
+		int			rti = ((RangeTblRef *) jtnode)->rtindex;
+		RangeTblEntry *rte = rt_fetch(rti, qry->rtable);
+
+		if (rte->rtekind == RTE_RELATION)
+		{
+			CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_INSERT);
+			CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_DELETE);
+			CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_UPDATE);
+		}
+		else
+			elog(ERROR, "unsupported RTE kind: %d", (int) rte->rtekind);
+	}
+	else if (IsA(jtnode, FromExpr))
+	{
+		FromExpr   *f = (FromExpr *) jtnode;
+		ListCell   *l;
+
+		foreach(l, f->fromlist)
+			CreateIvmTriggersOnBaseTables(qry, lfirst(l), matviewOid, matviewname);
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+
+		CreateIvmTriggersOnBaseTables(qry, j->larg, matviewOid, matviewname);
+		CreateIvmTriggersOnBaseTables(qry, j->rarg, matviewOid, matviewname);
+	}
+	else
+		elog(ERROR, "unrecognized node type: %d", (int) nodeTag(jtnode));
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -547,6 +706,11 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	if (is_matview && !into->skipData)
 		SetMatViewPopulatedState(intoRelationDesc, true);
 
+	/*
+	 * Mark relisivm field, if it's a matview and into->ivm is true.
+	 */
+	if (is_matview && into->ivm)
+		SetMatViewIVMState(intoRelationDesc, true);
 	/*
 	 * Fill private fields of myState for use by later routines
 	 */
@@ -619,3 +783,74 @@ intorel_destroy(DestReceiver *self)
 {
 	pfree(self);
 }
+
+static void
+CreateIvmTrigger(Oid relOid, Oid viewOid, char *matviewname, int16 type)
+{
+	CreateTrigStmt *ivm_trigger;
+	List *transitionRels = NIL;
+	ObjectAddress address, refaddr;
+
+	refaddr.classId = RelationRelationId;
+	refaddr.objectId = viewOid;
+	refaddr.objectSubId = 0;
+
+
+	ivm_trigger = makeNode(CreateTrigStmt);
+	ivm_trigger->relation = NULL;
+	ivm_trigger->row = false;
+	ivm_trigger->timing = TRIGGER_TYPE_AFTER;
+
+	ivm_trigger->events = type;
+
+	switch (type)
+	{
+		case TRIGGER_TYPE_INSERT:
+			ivm_trigger->trigname = "IVM_trigger_ins";
+			break;
+		case TRIGGER_TYPE_DELETE:
+			ivm_trigger->trigname = "IVM_trigger_del";
+			break;
+		case TRIGGER_TYPE_UPDATE:
+			ivm_trigger->trigname = "IVM_trigger_upd";
+			break;
+	}
+
+	if (type == TRIGGER_TYPE_INSERT || type == TRIGGER_TYPE_UPDATE)
+	{
+		TriggerTransition *n = makeNode(TriggerTransition);
+		n->name = "ivm_newtable";
+		n->isNew = true;
+		n->isTable = true;
+
+		transitionRels = lappend(transitionRels, n);
+	}
+	if (type == TRIGGER_TYPE_DELETE || type == TRIGGER_TYPE_UPDATE)
+	{
+		TriggerTransition *n = makeNode(TriggerTransition);
+		n->name = "ivm_oldtable";
+		n->isNew = false;
+		n->isTable = true;
+
+		transitionRels = lappend(transitionRels, n);
+	}
+
+	ivm_trigger->funcname = SystemFuncName("IVM_immediate_maintenance");
+
+	ivm_trigger->columns = NIL;
+	ivm_trigger->transitionRels = transitionRels;
+	ivm_trigger->whenClause = NULL;
+	ivm_trigger->isconstraint = false;
+	ivm_trigger->deferrable = false;
+	ivm_trigger->initdeferred = false;
+	ivm_trigger->constrrel = NULL;
+	ivm_trigger->args = list_make1(makeString(matviewname));
+
+	address = CreateTrigger(ivm_trigger, NULL, relOid, InvalidOid, InvalidOid,
+						 InvalidOid, InvalidOid, InvalidOid, NULL, true, false);
+
+	recordDependencyOn(&address, &refaddr, DEPENDENCY_AUTO);
+
+	/* Make changes-so-far visible */
+	CommandCounterIncrement();
+}
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 537d0e8cef..6b023e6832 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -46,6 +46,15 @@
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 
+#include "utils/regproc.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_func.h"
+#include "nodes/print.h"
+#include "catalog/pg_type_d.h"
+#include "optimizer/optimizer.h"
+#include "commands/defrem.h"
+
 
 typedef struct
 {
@@ -65,7 +74,8 @@ static bool transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void transientrel_shutdown(DestReceiver *self);
 static void transientrel_destroy(DestReceiver *self);
 static uint64 refresh_matview_datafill(DestReceiver *dest, Query *query,
-									   const char *queryString);
+						 QueryEnvironment *queryEnv,
+						 const char *queryString);
 static char *make_temptable_name_n(char *tempname, int n);
 static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 								   int save_sec_context);
@@ -74,6 +84,9 @@ static bool is_usable_unique_index(Relation indexRel);
 static void OpenMatViewIncrementalMaintenance(void);
 static void CloseMatViewIncrementalMaintenance(void);
 
+static void apply_delta(Oid matviewOid, Oid tempOid_new, Oid tempOid_old,
+			Query *query, Oid relowner, int save_sec_context);
+
 /*
  * SetMatViewPopulatedState
  *		Mark a materialized view as populated, or not.
@@ -114,6 +127,46 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
 	CommandCounterIncrement();
 }
 
+/*
+ * SetMatViewIVMState
+ *		Mark a materialized view as IVM, or not.
+ *
+ * NOTE: caller must be holding an appropriate lock on the relation.
+ */
+void
+SetMatViewIVMState(Relation relation, bool newstate)
+{
+	Relation	pgrel;
+	HeapTuple	tuple;
+
+	Assert(relation->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Update relation's pg_class entry.  Crucial side-effect: other backends
+	 * (and this one too!) are sent SI message to make them rebuild relcache
+	 * entries.
+	 */
+	pgrel = table_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(relation)));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u",
+			 RelationGetRelid(relation));
+
+	((Form_pg_class) GETSTRUCT(tuple))->relisivm = newstate;
+
+	CatalogTupleUpdate(pgrel, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	table_close(pgrel, RowExclusiveLock);
+
+	/*
+	 * Advance command counter to make the updated pg_class row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+}
+
 /*
  * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command
  *
@@ -311,7 +364,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 
 	/* Generate the data, if wanted. */
 	if (!stmt->skipData)
-		processed = refresh_matview_datafill(dest, dataQuery, queryString);
+		processed = refresh_matview_datafill(dest, dataQuery, NULL, queryString);
 
 	/* Make the matview match the newly generated data. */
 	if (concurrent)
@@ -369,6 +422,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  */
 static uint64
 refresh_matview_datafill(DestReceiver *dest, Query *query,
+						 QueryEnvironment *queryEnv,
 						 const char *queryString)
 {
 	List	   *rewritten;
@@ -405,7 +459,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 	/* Create a QueryDesc, redirecting output to our tuple receiver */
 	queryDesc = CreateQueryDesc(plan, queryString,
 								GetActiveSnapshot(), InvalidSnapshot,
-								dest, NULL, NULL, 0);
+								dest, NULL, queryEnv ? queryEnv: NULL, 0);
 
 	/* call ExecutorStart to prepare the plan for execution */
 	ExecutorStart(queryDesc, 0);
@@ -926,3 +980,622 @@ CloseMatViewIncrementalMaintenance(void)
 	matview_maintenance_depth--;
 	Assert(matview_maintenance_depth >= 0);
 }
+
+/*
+ * IVM trigger function
+ */
+
+Datum
+IVM_immediate_maintenance(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	Relation	rel;
+	Oid relid;
+	Oid matviewOid;
+	Query	   *query, *old_delta_qry, *new_delta_qry;
+	char*		matviewname = trigdata->tg_trigger->tgargs[0];
+	List	   *names;
+	Relation matviewRel;
+	int old_depth = matview_maintenance_depth;
+
+	Oid			tableSpace;
+	Oid			relowner;
+	Oid			OIDDelta_new = InvalidOid;
+	Oid			OIDDelta_old = InvalidOid;
+	DestReceiver *dest_new = NULL, *dest_old = NULL;
+	char		relpersistence;
+	Oid			save_userid;
+	int			save_sec_context;
+	int			save_nestlevel;
+
+	ParseState *pstate;
+	QueryEnvironment *queryEnv = create_queryEnv();
+
+	Const	*dmy_arg = makeConst(INT4OID,
+								 -1,
+								 InvalidOid,
+								 sizeof(int32),
+								 Int32GetDatum(1),
+								 false,
+								 true); /* pass by value */
+
+	/* Create a dummy ParseState for addRangeTableEntryForENR */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+
+	names = stringToQualifiedNameList(matviewname);
+
+	/*
+	 * Wait for concurrent transactions which update this materialized view at READ COMMITED.
+	 * This is needed to see changes commited in othre transactions. No wait and raise an error
+	 * at REPEATABLE READ or SERIALIZABLE to prevent anormal update of matviews.
+	 * XXX: dead-lock is possible here.
+	 */
+	if (!IsolationUsesXactSnapshot())
+		matviewOid = RangeVarGetRelid(makeRangeVarFromNameList(names), ExclusiveLock, true);
+	else
+		matviewOid = RangeVarGetRelidExtended(makeRangeVarFromNameList(names), ExclusiveLock, RVR_MISSING_OK | RVR_NOWAIT, NULL, NULL);
+
+	matviewRel = table_open(matviewOid, NoLock);
+
+	/*
+	 * Get and push the latast snapshot to see any changes which is commited during waiting in
+	 * other transactions at READ COMMITTED level.
+	 * XXX: Is this safe?
+	 */
+	PushActiveSnapshot(GetTransactionSnapshot());
+
+	/* Make sure it is a materialized view. */
+	if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("\"%s\" is not a materialized view",
+						RelationGetRelationName(matviewRel))));
+
+	rel = trigdata->tg_relation;
+	relid = rel->rd_id;
+
+	query = get_view_query(matviewRel);
+
+	new_delta_qry = copyObject(query);
+	old_delta_qry = copyObject(query);
+
+	if (trigdata->tg_newtable)
+	{
+		RangeTblEntry *rte;
+		ListCell   *lc;
+
+		TargetEntry *tle;
+		Node *node;
+		FuncCall *fn;
+
+		EphemeralNamedRelation enr =
+			palloc(sizeof(EphemeralNamedRelationData));
+
+		enr->md.name = trigdata->tg_trigger->tgnewtable;
+		enr->md.reliddesc = trigdata->tg_relation->rd_id;
+		enr->md.tupdesc = NULL;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(trigdata->tg_newtable);
+		enr->reldata = trigdata->tg_newtable;
+		register_ENR(queryEnv, enr);
+
+		rte = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+		new_delta_qry->rtable = lappend(new_delta_qry->rtable, rte);
+
+		foreach(lc, new_delta_qry->rtable)
+		{
+			RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+			if (r->relid == relid)
+			{
+				lfirst(lc) = rte;
+				break;
+			}
+		}
+
+		if (query->hasAggs)
+		{
+			ListCell *lc;
+			List *agg_counts = NIL;
+			AttrNumber next_resno = list_length(query->targetList) + 1;
+			Node *node;
+
+			foreach(lc, query->targetList)
+			{
+				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+				TargetEntry *tle_count;
+
+				if (IsA(tle->expr, Aggref))
+				{
+					Aggref *aggref = (Aggref *) tle->expr;
+					const char *aggname = get_func_name(aggref->aggfnoid);
+
+					if (strcmp(aggname, "count") != 0)
+					{
+						fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+
+						node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+						((Aggref *)node)->args = aggref->args;
+
+						tle_count = makeTargetEntry((Expr *) node,
+												next_resno,
+												pstrdup(makeObjectName("__ivm_count",tle->resname, "_")),
+												false);
+						agg_counts = lappend(agg_counts, tle_count);
+						next_resno++;
+					}
+				}
+
+			}
+			new_delta_qry->targetList = list_concat(new_delta_qry->targetList, agg_counts);
+		}
+
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+		fn->agg_star = true;
+		if (!new_delta_qry->groupClause && !new_delta_qry->hasAggs)
+			new_delta_qry->groupClause = transformDistinctClause(NULL, &new_delta_qry->targetList, new_delta_qry->sortClause, false);
+
+		node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+		tle = makeTargetEntry((Expr *) node,
+								  list_length(new_delta_qry->targetList) + 1,
+								  pstrdup("__ivm_count__"),
+								  false);
+		new_delta_qry->targetList = lappend(new_delta_qry->targetList, tle);
+		new_delta_qry->hasAggs = true;
+	}
+
+	if (trigdata->tg_oldtable)
+	{
+		RangeTblEntry *rte;
+		ListCell   *lc;
+
+		TargetEntry *tle;
+		Node *node;
+		FuncCall *fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+
+		EphemeralNamedRelation enr =
+			palloc(sizeof(EphemeralNamedRelationData));
+
+		enr->md.name = trigdata->tg_trigger->tgoldtable;
+		enr->md.reliddesc = trigdata->tg_relation->rd_id;
+		enr->md.tupdesc = NULL;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(trigdata->tg_oldtable);
+		enr->reldata = trigdata->tg_oldtable;
+		register_ENR(queryEnv, enr);
+
+		rte = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+		old_delta_qry->rtable = lappend(old_delta_qry->rtable, rte);
+
+		foreach(lc, old_delta_qry->rtable)
+		{
+			RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+			if (r->relid == relid)
+			{
+				lfirst(lc) = rte;
+				break;
+			}
+		}
+
+		if (query->hasAggs)
+		{
+			ListCell *lc;
+			List *agg_counts = NIL;
+			AttrNumber next_resno = list_length(query->targetList) + 1;
+			Node *node;
+
+			foreach(lc, query->targetList)
+			{
+				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+				TargetEntry *tle_count;
+
+				if (IsA(tle->expr, Aggref))
+				{
+					Aggref *aggref = (Aggref *) tle->expr;
+					const char *aggname = get_func_name(aggref->aggfnoid);
+
+					if (strcmp(aggname, "count") != 0)
+					{
+						fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+
+						node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+						((Aggref *)node)->args = aggref->args;
+
+						tle_count = makeTargetEntry((Expr *) node,
+												next_resno,
+												pstrdup(makeObjectName("__ivm_count",tle->resname, "_")),
+												false);
+						agg_counts = lappend(agg_counts, tle_count);
+						next_resno++;
+					}
+				}
+
+			}
+			old_delta_qry->targetList = list_concat(old_delta_qry->targetList, agg_counts);
+		}
+
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+		fn->agg_star = true;
+
+		if (!old_delta_qry->groupClause && !old_delta_qry->hasAggs)
+			old_delta_qry->groupClause = transformDistinctClause(NULL, &old_delta_qry->targetList, old_delta_qry->sortClause, false);
+
+		node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+		tle = makeTargetEntry((Expr *) node,
+								  list_length(old_delta_qry->targetList) + 1,
+								  pstrdup("__ivm_count__"),
+								  false);
+		old_delta_qry->targetList = lappend(old_delta_qry->targetList, tle);
+		old_delta_qry->hasAggs = true;
+	}
+
+
+	/*
+	 * Check for active uses of the relation in the current transaction, such
+	 * as open scans.
+	 *
+	 * NB: We count on this to protect us against problems with refreshing the
+	 * data using TABLE_INSERT_FROZEN.
+	 */
+	CheckTableNotInUse(matviewRel, "REFRESH MATERIALIZED VIEW");
+
+	relowner = matviewRel->rd_rel->relowner;
+
+	/*
+	 * Switch to the owner's userid, so that any functions are run as that
+	 * user.  Also arrange to make GUC variable changes local to this command.
+	 * Don't lock it down too tight to create a temporary table just yet.  We
+	 * will switch modes when we are about to execute user code.
+	 */
+	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+	save_nestlevel = NewGUCNestLevel();
+
+	tableSpace = GetDefaultTablespace(RELPERSISTENCE_TEMP, false);
+	relpersistence = RELPERSISTENCE_TEMP;
+
+	/*
+	 * Create the transient table that will receive the regenerated data. Lock
+	 * it against access by any other process until commit (by which time it
+	 * will be gone).
+	 */
+	if (trigdata->tg_newtable)
+	{
+		OIDDelta_new = make_new_heap(matviewOid, tableSpace, relpersistence,
+									 ExclusiveLock);
+		LockRelationOid(OIDDelta_new, AccessExclusiveLock);
+		dest_new = CreateTransientRelDestReceiver(OIDDelta_new);
+	}
+	if (trigdata->tg_oldtable)
+	{
+		if (trigdata->tg_newtable)
+			OIDDelta_old = make_new_heap(OIDDelta_new, tableSpace, relpersistence,
+										 ExclusiveLock);
+		else
+			OIDDelta_old = make_new_heap(matviewOid, tableSpace, relpersistence,
+										 ExclusiveLock);
+		LockRelationOid(OIDDelta_old, AccessExclusiveLock);
+		dest_old = CreateTransientRelDestReceiver(OIDDelta_old);
+	}
+
+	/*
+	 * Now lock down security-restricted operations.
+	 */
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+
+	/* Generate the data. */
+	if (trigdata->tg_newtable)
+		refresh_matview_datafill(dest_new, new_delta_qry, queryEnv, NULL);
+	if (trigdata->tg_oldtable)
+		refresh_matview_datafill(dest_old, old_delta_qry, queryEnv, NULL);
+
+	PG_TRY();
+	{
+		apply_delta(matviewOid, OIDDelta_new, OIDDelta_old,
+					query, relowner, save_sec_context);
+	}
+	PG_CATCH();
+	{
+		matview_maintenance_depth = old_depth;
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	/* Pop the original snapshot. */
+	PopActiveSnapshot();
+
+	table_close(matviewRel, NoLock);
+
+	/* Roll back any GUC changes */
+	AtEOXact_GUC(false, save_nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(save_userid, save_sec_context);
+
+	return PointerGetDatum(NULL);
+}
+
+static void
+apply_delta(Oid matviewOid, Oid tempOid_new, Oid tempOid_old,
+			Query *query, Oid relowner, int save_sec_context)
+{
+	StringInfoData querybuf;
+	StringInfoData mvatts_buf, diffatts_buf;
+	StringInfoData mv_gkeys_buf, diff_gkeys_buf, updt_gkeys_buf;
+	StringInfoData diff_aggs_buf, update_aggs_old, update_aggs_new;
+	Relation	matviewRel;
+	Relation	tempRel_new = NULL, tempRel_old = NULL;
+	char	   *matviewname;
+	char	   *tempname_new = NULL, *tempname_old = NULL;
+	ListCell	*lc;
+	char	   *sep, *sep_agg;
+	bool		with_group = query->groupClause != NULL;
+
+
+	initStringInfo(&querybuf);
+	matviewRel = table_open(matviewOid, NoLock);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	if (OidIsValid(tempOid_new))
+	{
+		tempRel_new = table_open(tempOid_new, NoLock);
+		tempname_new = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(tempRel_new)),
+												  RelationGetRelationName(tempRel_new));
+	}
+	if (OidIsValid(tempOid_old))
+	{
+		tempRel_old = table_open(tempOid_old, NoLock);
+		tempname_old = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(tempRel_old)),
+												  RelationGetRelationName(tempRel_old));
+	}
+
+	initStringInfo(&mvatts_buf);
+	initStringInfo(&diffatts_buf);
+	initStringInfo(&diff_aggs_buf);
+	initStringInfo(&update_aggs_old);
+	initStringInfo(&update_aggs_new);
+
+	sep = "";
+	sep_agg= "";
+	foreach (lc, query->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);;
+
+		if (tle->resjunk)
+			continue;
+
+		appendStringInfo(&mvatts_buf, "%s", sep);
+		appendStringInfo(&diffatts_buf, "%s", sep);
+		sep = ", ";
+
+		appendStringInfo(&mvatts_buf, "%s", quote_qualified_identifier("mv", tle->resname));
+		appendStringInfo(&diffatts_buf, "%s", quote_qualified_identifier("diff", tle->resname));
+		if (query->hasAggs && IsA(tle->expr, Aggref))
+		{
+			Aggref *aggref = (Aggref *) tle->expr;
+			const char *aggname = get_func_name(aggref->aggfnoid);
+
+			appendStringInfo(&update_aggs_old, "%s", sep_agg);
+			appendStringInfo(&update_aggs_new, "%s", sep_agg);
+			appendStringInfo(&diff_aggs_buf, "%s", sep_agg);
+
+			sep_agg = ", ";
+
+			if (!strcmp(aggname, "count"))
+			{
+				appendStringInfo(&update_aggs_old,
+					"%s = %s - %s",
+					quote_qualified_identifier(NULL, tle->resname),
+					quote_qualified_identifier("mv", tle->resname),
+					quote_qualified_identifier("t", tle->resname)
+				);
+				appendStringInfo(&update_aggs_new,
+					"%s = %s + %s",
+					quote_qualified_identifier(NULL, tle->resname),
+					quote_qualified_identifier("mv", tle->resname),
+					quote_qualified_identifier("diff", tle->resname)
+				);
+
+				appendStringInfo(&diff_aggs_buf, "%s",
+					quote_qualified_identifier("diff", tle->resname)
+				);
+			}
+			else if (!strcmp(aggname, "sum"))
+			{
+				appendStringInfo(&update_aggs_old,
+					"%s = CASE WHEN %s = %s THEN NULL ELSE COALESCE(%s,0) - COALESCE(%s, 0) END, "
+					"%s = %s - %s",
+					quote_qualified_identifier(NULL, tle->resname),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("t", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("mv", tle->resname),
+					quote_qualified_identifier("t", tle->resname),
+					quote_qualified_identifier(NULL, makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("t", makeObjectName("__ivm_count",tle->resname,"_"))
+				);
+				appendStringInfo(&update_aggs_new,
+					"%s = CASE WHEN %s = 0 AND NULL = 0 THEN %s ELSE COALESCE(%s,0) + COALESCE(%s, 0) END, "
+					"%s = %s + %s",
+					quote_qualified_identifier(NULL, tle->resname),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("mv", tle->resname),
+					quote_qualified_identifier("diff", tle->resname),
+					quote_qualified_identifier(NULL, makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",tle->resname,"_"))
+				);
+
+				appendStringInfo(&diff_aggs_buf, "%s, %s",
+					quote_qualified_identifier("diff", tle->resname),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",tle->resname,"_"))
+				);
+			}
+			else
+				elog(ERROR, "unsupported aggregate function: %s", aggname);
+
+		}
+	}
+
+	if (query->hasAggs)
+	{
+		initStringInfo(&mv_gkeys_buf);
+		initStringInfo(&diff_gkeys_buf);
+		initStringInfo(&updt_gkeys_buf);
+
+		if (with_group)
+		{
+			sep_agg= "";
+			foreach (lc, query->groupClause)
+			{
+				SortGroupClause *sgcl = (SortGroupClause *) lfirst(lc);
+				TargetEntry *tle = get_sortgroupclause_tle(sgcl, query->targetList);
+
+				appendStringInfo(&mv_gkeys_buf, "%s", sep_agg);
+				appendStringInfo(&diff_gkeys_buf, "%s", sep_agg);
+				appendStringInfo(&updt_gkeys_buf, "%s", sep_agg);
+
+				sep_agg = ", ";
+
+				appendStringInfo(&mv_gkeys_buf, "%s", quote_qualified_identifier("mv", tle->resname));
+				appendStringInfo(&diff_gkeys_buf, "%s", quote_qualified_identifier("diff", tle->resname));
+				appendStringInfo(&updt_gkeys_buf, "%s", quote_qualified_identifier("updt", tle->resname));
+			}
+		}
+		else
+		{
+			appendStringInfo(&mv_gkeys_buf, "1");
+			appendStringInfo(&diff_gkeys_buf, "1");
+			appendStringInfo(&updt_gkeys_buf, "1");
+		}
+	}
+
+	/* Open SPI context. */
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	/* Analyze the temp table with the new contents. */
+	if (tempname_new)
+	{
+		appendStringInfo(&querybuf, "ANALYZE %s", tempname_new);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+	if (tempname_old)
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf, "ANALYZE %s", tempname_old);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+
+	OpenMatViewIncrementalMaintenance();
+
+	if (query->hasAggs)
+	{
+		if (tempname_old)
+		{
+			resetStringInfo(&querybuf);
+			appendStringInfo(&querybuf,
+							"WITH t AS ("
+							"  SELECT diff.__ivm_count__, (diff.__ivm_count__ = mv.__ivm_count__) AS for_dlt, mv.ctid"
+							", %s"
+							"  FROM %s AS mv, %s AS diff WHERE (%s) = (%s)"
+							"), updt AS ("
+							"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ - t.__ivm_count__"
+							", %s "
+							"  FROM t WHERE mv.ctid = t.ctid AND NOT for_dlt"
+							") DELETE FROM %s AS mv USING t WHERE mv.ctid = t.ctid AND for_dlt;",
+							diff_aggs_buf.data,
+							matviewname, tempname_old, mv_gkeys_buf.data, diff_gkeys_buf.data,
+							matviewname, update_aggs_old.data, matviewname);
+			if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+				elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+		}
+		if (tempname_new)
+		{
+			resetStringInfo(&querybuf);
+			appendStringInfo(&querybuf,
+							"WITH updt AS ("
+							"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ + diff.__ivm_count__"
+							", %s "
+							"  FROM %s AS diff WHERE (%s) = (%s)"
+							"  RETURNING %s"
+							") INSERT INTO %s (SELECT * FROM %s AS diff WHERE (%s) NOT IN (SELECT %s FROM updt));",
+							matviewname, update_aggs_new.data, tempname_new,
+							mv_gkeys_buf.data, diff_gkeys_buf.data, diff_gkeys_buf.data,
+							matviewname, tempname_new, diff_gkeys_buf.data, updt_gkeys_buf.data);
+			if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+				elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+		}
+	}
+	else
+	{
+		if (tempname_old)
+		{
+			resetStringInfo(&querybuf);
+			appendStringInfo(&querybuf,
+							"WITH t AS ("
+							"  SELECT diff.__ivm_count__, (diff.__ivm_count__ = mv.__ivm_count__) AS for_dlt, mv.ctid"
+							"  FROM %s AS mv, %s AS diff WHERE (%s) = (%s)"
+							"), updt AS ("
+							"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ - t.__ivm_count__"
+							"  FROM t WHERE mv.ctid = t.ctid AND NOT for_dlt"
+							") DELETE FROM %s AS mv USING t WHERE mv.ctid = t.ctid AND for_dlt;",
+							matviewname, tempname_old, mvatts_buf.data, diffatts_buf.data, matviewname, matviewname);
+			if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+				elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+		}
+		if (tempname_new)
+		{
+			resetStringInfo(&querybuf);
+			appendStringInfo(&querybuf,
+							"WITH updt AS ("
+							"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ + diff.__ivm_count__"
+							"  FROM %s AS diff WHERE (%s) = (%s)"
+							"  RETURNING %s"
+							") INSERT INTO %s (SELECT * FROM %s AS diff WHERE (%s) NOT IN (SELECT * FROM updt));",
+							matviewname, tempname_new, mvatts_buf.data, diffatts_buf.data, diffatts_buf.data, matviewname, tempname_new, diffatts_buf.data);
+			if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+				elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+		}
+	}
+
+	/* We're done maintaining the materialized view. */
+	CloseMatViewIncrementalMaintenance();
+
+	if (OidIsValid(tempOid_new))
+		table_close(tempRel_new, NoLock);
+	if (OidIsValid(tempOid_old))
+		table_close(tempRel_old, NoLock);
+
+	table_close(matviewRel, NoLock);
+
+	/* Clean up temp tables. */
+	if (OidIsValid(tempOid_new))
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf, "DROP TABLE %s", tempname_new);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+	if (OidIsValid(tempOid_old))
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf, "DROP TABLE %s", tempname_old);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+
+	/* Close SPI context. */
+	if (SPI_finish() != SPI_OK_FINISH)
+		elog(ERROR, "SPI_finish failed");
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 78deade89b..8262ac039b 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2361,6 +2361,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(relisivm);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4f2ebe5118..608d477bd5 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2641,6 +2641,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(relisivm);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8400dd319e..7897dd9c57 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3042,6 +3042,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6c2626ee62..aa01e205c3 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1366,6 +1366,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8311b1dd46..7cae68218b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -418,6 +418,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target create_mv_target
+%type <boolean>	incremental
 
 %type <defelt>	createfunc_opt_item common_func_opt_item dostmt_opt_item
 %type <fun_param> func_arg func_arg_with_default table_func_column aggr_arg
@@ -645,7 +646,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
 	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
-	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+	INCLUDING INCREMENT INCREMENTAL INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
 	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
 	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -4054,30 +4055,32 @@ opt_with_data:
  *****************************************************************************/
 
 CreateMatViewStmt:
-		CREATE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
+		CREATE OptNoLog incremental MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $7;
-					ctas->into = $5;
+					ctas->query = $8;
+					ctas->into = $6;
 					ctas->relkind = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = false;
 					/* cram additional flags into the IntoClause */
-					$5->rel->relpersistence = $2;
-					$5->skipData = !($8);
+					$6->rel->relpersistence = $2;
+					$6->skipData = !($9);
+					$6->ivm = $3;
 					$$ = (Node *) ctas;
 				}
-		| CREATE OptNoLog MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
+		| CREATE OptNoLog incremental MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $10;
-					ctas->into = $8;
+					ctas->query = $11;
+					ctas->into = $9;
 					ctas->relkind = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = true;
 					/* cram additional flags into the IntoClause */
-					$8->rel->relpersistence = $2;
-					$8->skipData = !($11);
+					$9->rel->relpersistence = $2;
+					$9->skipData = !($12);
+					$9->ivm = $3;
 					$$ = (Node *) ctas;
 				}
 		;
@@ -4094,9 +4097,14 @@ create_mv_target:
 					$$->tableSpaceName = $5;
 					$$->viewQuery = NULL;		/* filled at analysis time */
 					$$->skipData = false;		/* might get changed later */
+					$$->ivm = false;
 				}
 		;
 
+incremental:	INCREMENTAL				{ $$ = true; }
+				| /*EMPTY*/				{ $$ = false; }
+		;
+
 OptNoLog:	UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
 		;
@@ -15128,6 +15136,7 @@ unreserved_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 77a48b039d..3903b8ffd5 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -37,6 +37,7 @@
 #include "utils/syscache.h"
 #include "utils/varlena.h"
 
+#include "commands/matview.h"
 
 #define MAX_FUZZY_DISTANCE				3
 
@@ -56,9 +57,10 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							int count, int offset,
 							int rtindex, int sublevels_up,
 							int location, bool include_dropped,
-							List **colnames, List **colvars);
+							List **colnames, List **colvars, bool is_ivm);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool isIvmColumn(const char *s);
 
 
 /*
@@ -1238,6 +1240,7 @@ addRangeTableEntry(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -1317,6 +1320,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -2315,7 +2319,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 						expandTupleDesc(tupdesc, rte->eref,
 										rtfunc->funccolcount, atts_done,
 										rtindex, sublevels_up, location,
-										include_dropped, colnames, colvars);
+										include_dropped, colnames, colvars, false);
 					}
 					else if (functypclass == TYPEFUNC_SCALAR)
 					{
@@ -2567,10 +2571,19 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
 	expandTupleDesc(rel->rd_att, eref, rel->rd_att->natts, 0,
 					rtindex, sublevels_up,
 					location, include_dropped,
-					colnames, colvars);
+					colnames, colvars, RelationIsIVM(rel));
 	relation_close(rel, AccessShareLock);
 }
 
+static bool
+isIvmColumn(const char *s)
+{
+	char pre[7];
+ 
+ 	strlcpy(pre, s, sizeof(pre));
+	return (strcmp(pre, "__ivm_") == 0); 
+}
+
 /*
  * expandTupleDesc -- expandRTE subroutine
  *
@@ -2584,7 +2597,7 @@ static void
 expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 				int rtindex, int sublevels_up,
 				int location, bool include_dropped,
-				List **colnames, List **colvars)
+				List **colnames, List **colvars, bool is_ivm)
 {
 	ListCell   *aliascell = list_head(eref->colnames);
 	int			varattno;
@@ -2605,6 +2618,9 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 	{
 		Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
 
+		if (is_ivm && isIvmColumn(NameStr(attr->attname)) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		if (attr->attisdropped)
 		{
 			if (include_dropped)
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 7df2b6154c..5385a038b1 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -765,7 +765,8 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 														attr->atttypmod))));
 	}
 
-	if (i != resultDesc->natts)
+	/* No check for materialized views since this could have special columns for IVM */
+	if ((!isSelect || requireColumnNameMatch) && i != resultDesc->natts)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 isSelect ?
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index ea40c28733..6e8dc1f1cd 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -41,6 +41,8 @@
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 
+#include "parser/parser.h"
+#include "commands/matview.h"
 
 /* We use a list of these to detect recursion in RewriteQuery */
 typedef struct rewrite_event
@@ -1597,6 +1599,50 @@ ApplyRetrieveRule(Query *parsetree,
 	if (rule->qual != NULL)
 		elog(ERROR, "cannot handle qualified ON SELECT rule");
 
+	if (RelationIsIVM(relation))
+	{
+		rule_action = copyObject(linitial(rule->actions));
+
+		if (!rule_action->distinctClause && !rule_action->groupClause && !rule_action->hasAggs)
+		{
+			StringInfoData str;
+			RawStmt *raw;
+			Query *sub;
+
+			if (rule_action->hasDistinctOn)
+				elog(ERROR, "DISTINCT ON is not supported in IVM");
+
+			initStringInfo(&str);
+			appendStringInfo(&str, "SELECT mv.*, __ivm_count__ FROM %s mv, generate_series(1, mv.__ivm_count__)",
+						quote_qualified_identifier(get_namespace_name(RelationGetNamespace(relation)),
+													RelationGetRelationName(relation)));
+
+			raw = (RawStmt*)linitial(raw_parser(str.data));
+			sub = transformStmt(make_parsestate(NULL),raw->stmt);
+
+			rte = rt_fetch(rt_index, parsetree->rtable);
+
+			rte->rtekind = RTE_SUBQUERY;
+			rte->subquery = sub;
+			rte->security_barrier = RelationIsSecurityView(relation);
+			/* Clear fields that should not be set in a subquery RTE */
+			rte->relid = InvalidOid;
+			rte->relkind = 0;
+			rte->rellockmode = 0;
+			rte->tablesample = NULL;
+			rte->inh = false;			/* must not be set for a subquery */
+
+			rte->requiredPerms = 0;		/* no permission check on subquery itself */
+			rte->checkAsUser = InvalidOid;
+			rte->selectedCols = NULL;
+			rte->insertedCols = NULL;
+			rte->updatedCols = NULL;
+			rte->extraUpdatedCols = NULL;
+		}
+
+		return parsetree;
+	}
+
 	if (rt_index == parsetree->resultRelation)
 	{
 		/*
@@ -1906,7 +1952,8 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
 		 * In that case this test would need to be postponed till after we've
 		 * opened the rel, so that we could check its state.
 		 */
-		if (rte->relkind == RELKIND_MATVIEW)
+		if (rte->relkind == RELKIND_MATVIEW &&
+			(!rte->relisivm || MatViewIncrementalMaintenanceIsEnabled() || parsetree->commandType != CMD_SELECT))
 			continue;
 
 		/*
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index c13c08a97b..296cc53ffd 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1844,6 +1844,30 @@ get_rel_relispartition(Oid relid)
 		return false;
 }
 
+/*
+ * get_rel_relisivm
+ *
+ *		Returns the relisivm flag associated with a given relation.
+ */
+bool
+get_rel_relisivm(Oid relid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp);
+		bool		result;
+
+		result = reltup->relisivm;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return false;
+}
+
 /*
  * get_rel_tablespace
  *
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2b992d7832..d80bb30696 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1861,6 +1861,8 @@ formrdesc(const char *relationName, Oid relationReltype,
 
 	/* ... and they're always populated, too */
 	relation->rd_rel->relispopulated = true;
+	/* ... and they're always no ivm, too */
+	relation->rd_rel->relisivm = false;
 
 	relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
 	relation->rd_rel->relpages = 0;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 97167d2c4b..9654e62a49 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1491,6 +1491,7 @@ describeOneTableDetails(const char *schemaname,
 		char		relpersistence;
 		char		relreplident;
 		char	   *relam;
+		bool		isivm;
 	}			tableinfo;
 	bool		show_column_details = false;
 
@@ -1511,6 +1512,7 @@ describeOneTableDetails(const char *schemaname,
 						  "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
 						  "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
 						  "c.relpersistence, c.relreplident, am.amname\n"
+						  ",c.relisivm\n"
 						  "FROM pg_catalog.pg_class c\n "
 						  "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
 						  "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
@@ -1687,6 +1689,9 @@ describeOneTableDetails(const char *schemaname,
 			(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
 	else
 		tableinfo.relam = NULL;
+	/* TODO: This will supported when sversion >= 130000 (or later). */
+	if (pset.sversion >= 120000)
+		tableinfo.isivm = strcmp(PQgetvalue(res, 0, 15), "t") == 0;
 	PQclear(res);
 	res = NULL;
 
@@ -3260,6 +3265,12 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
 			printTableAddFooter(&cont, buf.data);
 		}
+
+		/* Incremental view maintance info */
+		if (verbose && tableinfo.relkind == RELKIND_MATVIEW && tableinfo.isivm)
+		{
+			printTableAddFooter(&cont, _("Incremental view maintenance: yes"));
+		}
 	}
 
 	/* reloptions, if verbose */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 7dcf342413..c14f0e1f98 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -998,6 +998,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"INCREMENTAL MATERIALIZED VIEW", NULL, NULL, &Query_for_list_of_matviews},
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2483,7 +2484,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
-		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
+		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW", "INCREMENTAL MATERIALIZED VIEW");
 	/* Complete PARTITION BY with RANGE ( or LIST ( or ... */
 	else if (TailMatches("PARTITION", "BY"))
 		COMPLETE_WITH("RANGE (", "LIST (", "HASH (");
@@ -2692,13 +2693,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches("CREATE", "MATERIALIZED"))
+	else if (Matches("CREATE", "MATERIALIZED") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED"))
 		COMPLETE_WITH("VIEW");
-	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	/* Complete CREATE MATERIALIZED VIEW <name> with AS  */
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny) ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH("SELECT");
 
 /* CREATE EVENT TRIGGER */
diff --git a/src/include/catalog/pg_class.dat b/src/include/catalog/pg_class.dat
index 9bcf28676d..7deda405af 100644
--- a/src/include/catalog/pg_class.dat
+++ b/src/include/catalog/pg_class.dat
@@ -27,7 +27,7 @@
   relpersistence => 'p', relkind => 'r', relnatts => '31', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 { oid => '1249',
@@ -37,7 +37,7 @@
   relpersistence => 'p', relkind => 'r', relnatts => '25', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 { oid => '1255',
@@ -47,17 +47,17 @@
   relpersistence => 'p', relkind => 'r', relnatts => '29', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 { oid => '1259',
   relname => 'pg_class', reltype => 'pg_class', relam => 'heap',
   relfilenode => '0', relpages => '0', reltuples => '0', relallvisible => '0',
   reltoastrelid => '0', relhasindex => 'f', relisshared => 'f',
-  relpersistence => 'p', relkind => 'r', relnatts => '33', relchecks => '0',
+  relpersistence => 'p', relkind => 'r', relnatts => '34', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 090b6ba907..ff535f5504 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
 	/* is relation a partition? */
 	bool		relispartition;
 
+	/* is relation a matview with ivm? */
+	bool		relisivm;
+
 	/* heap for rewrite during DDL, link to original rel */
 	Oid			relrewrite BKI_DEFAULT(0);
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87335248a0..5de996a72c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10677,4 +10677,9 @@
   proname => 'pg_partition_root', prorettype => 'regclass',
   proargtypes => 'regclass', prosrc => 'pg_partition_root' },
 
+# IVM
+{ oid => '784', descr => 'ivm trigger',
+  proname => 'IVM_immediate_maintenance', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_maintenance' },
+
 ]
diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h
index edf04bf415..8dd8193d9c 100644
--- a/src/include/commands/matview.h
+++ b/src/include/commands/matview.h
@@ -23,6 +23,8 @@
 
 extern void SetMatViewPopulatedState(Relation relation, bool newstate);
 
+extern void SetMatViewIVMState(Relation relation, bool newstate);
+
 extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										ParamListInfo params, char *completionTag);
 
@@ -30,4 +32,6 @@ extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
 
 extern bool MatViewIncrementalMaintenanceIsEnabled(void);
 
+extern Datum IVM_immediate_maintenance(PG_FUNCTION_ARGS);
+
 #endif							/* MATVIEW_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 94ded3c135..5c8a5ae3ca 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1002,6 +1002,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		relisivm;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -2059,6 +2060,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		ivm;			/* incremental view maintenance is used by materialized view */
 } CreateStmt;
 
 /* ----------
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 7c278c0e56..0aaef1ef15 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -117,6 +117,7 @@ typedef struct IntoClause
 	char	   *tableSpaceName; /* table space to use, or NULL */
 	Node	   *viewQuery;		/* materialized view's SELECT query */
 	bool		skipData;		/* true for WITH NO DATA */
+	bool		ivm;			/* true for WITH IVM */
 } IntoClause;
 
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 00ace8425e..d682ee11cc 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -198,6 +198,7 @@ PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
 PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
+PG_KEYWORD("incremental", INCREMENTAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
 PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index c8df5bff9f..8fd919349e 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -129,6 +129,7 @@ extern Oid	get_rel_namespace(Oid relid);
 extern Oid	get_rel_type_id(Oid relid);
 extern char get_rel_relkind(Oid relid);
 extern bool get_rel_relispartition(Oid relid);
+extern bool get_rel_relisivm(Oid relid);
 extern Oid	get_rel_tablespace(Oid relid);
 extern char get_rel_persistence(Oid relid);
 extern Oid	get_transform_fromsql(Oid typid, Oid langid, List *trftypes);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d7f33abce3..057f38d5de 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -562,6 +562,8 @@ typedef struct ViewOptions
  */
 #define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated)
 
+#define RelationIsIVM(relation) ((relation)->rd_rel->relisivm)
+
 /*
  * RelationIsAccessibleInLogicalDecoding
  *		True if we need to log enough information to have access via
diff --git a/src/test/regress/expected/incremental_matview.out b/src/test/regress/expected/incremental_matview.out
new file mode 100644
index 0000000000..e58b9ad6d0
--- /dev/null
+++ b/src/test/regress/expected/incremental_matview.out
@@ -0,0 +1,215 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- immediaite maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- result of materliazied view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 20
+ 30
+ 40
+ 50
+(6 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+ROLLBACK;
+-- support SUM() and COUNT() aggregation function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 |  20 |     1
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 | 120 |     2
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 | 220 |     2
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 |  20 |     1
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+ROLLBACK;
+-- support COUNT(*) aggregation function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j),COUNT(*)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 |  20 |     1
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 | 120 |     2
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+ROLLBACK;
+-- support having only aggregation function without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j)FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum 
+-----
+ 150
+(1 row)
+
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum 
+-----
+ 170
+(1 row)
+
+ROLLBACK;
+-- unsupport aggregation function except for SUM(),COUNT()
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min AS SELECT i, MIN(j)  FROM mv_base_a GROUP BY i;
+ERROR:  Aggrege function min is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_max AS SELECT i, MAX(j)  FROM mv_base_a GROUP BY i;
+ERROR:  Aggrege function max is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg AS SELECT i, AVG(j)  FROM mv_base_a GROUP BY i;
+ERROR:  Aggrege function avg is not supported
+DROP TABLE mv_base_b CASCADE;
+NOTICE:  drop cascades to materialized view mv_ivm_1
+DROP TABLE mv_base_a CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index f23fe8d870..63e743dddc 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -78,7 +78,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tidscan
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tidscan incremental_matview
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index ca200eb599..555c91d771 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -113,6 +113,7 @@ test: init_privs
 test: security_label
 test: collate
 test: matview
+test: incremental_matview
 test: lock
 test: replica_identity
 test: rowsecurity
diff --git a/src/test/regress/sql/incremental_matview.sql b/src/test/regress/sql/incremental_matview.sql
new file mode 100644
index 0000000000..94417093ce
--- /dev/null
+++ b/src/test/regress/sql/incremental_matview.sql
@@ -0,0 +1,77 @@
+
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i);
+
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+-- immediaite maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+
+-- result of materliazied view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ROLLBACK;
+
+-- support SUM() and COUNT() aggregation function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support COUNT(*) aggregation function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j),COUNT(*)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support having only aggregation function without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j)FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ROLLBACK;
+
+-- unsupport aggregation function except for SUM(),COUNT()
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min AS SELECT i, MIN(j)  FROM mv_base_a GROUP BY i;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_max AS SELECT i, MAX(j)  FROM mv_base_a GROUP BY i;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg AS SELECT i, AVG(j)  FROM mv_base_a GROUP BY i;
+
+DROP TABLE mv_base_b CASCADE;
+DROP TABLE mv_base_a CASCADE;
#18Yugo Nagata
nagata@sraoss.co.jp
In reply to: Greg Stark (#12)
Re: Implementing Incremental View Maintenance

Hi Greg,

On Wed, 3 Apr 2019 17:41:36 -0400
Greg Stark <stark@mit.edu> wrote:

On Sun, 31 Mar 2019 at 23:22, Yugo Nagata <nagata@sraoss.co.jp> wrote:

Firstly, this will handle simple definition views which includes only
selection, projection, and join. Standard aggregations (count, sum, avg,
min, max) are not planned to be implemented in the first patch, but these
are commonly used in materialized views, so I'll implement them later on.

It's fine to not have all the features from day 1 of course. But I
just picked up this comment and the followup talking about splitting
AVG into SUM and COUNT and I had a comment. When you do look at
tackling aggregates I don't think you should restrict yourself to
these specific standard aggregations. We have all the necessary
abstractions to handle all aggregations that are feasible, see
https://www.postgresql.org/docs/devel/xaggr.html#XAGGR-MOVING-AGGREGATES

What you need to do -- I think -- is store the "moving aggregate
state" before the final function. Then whenever a row is inserted or
deleted or updated (or whenever another column is updated which causes
the value to row to enter or leave the aggregation) apply either
aggtransfn or aggminvtransfn to the state. I'm not sure if you want to
apply the final function on every update or only lazily either may be
better in some usage.

Thank you for your suggestion! I submitted the latest patch just now supporting
some aggregate functions, but this supports only sum and count, and lacking a
kind of generalization. However, I would like to refine this to support more
general aggregate functions. I think your suggestions is helpful for me to do
this. Thank you!

Best regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

#19Thomas Munro
thomas.munro@gmail.com
In reply to: Yugo Nagata (#17)
Re: Implementing Incremental View Maintenance

On Fri, Jun 28, 2019 at 10:56 PM Yugo Nagata <nagata@sraoss.co.jp> wrote:

Attached is a WIP patch of IVM which supports some aggregate functions.

Hi Nagata-san and Hoshiai-san,

Thank you for working on this. I enjoyed your talk at PGCon. I've
added Kevin Grittner just in case he missed this thread; he has talked
often about implementing the counting algorithm, and he wrote the
"trigger transition tables" feature to support exactly this. While
integrating trigger transition tables with the new partition features,
we had to make a number of decisions about how that should work, and
we tried to come up with answers that would work for IMV, and I hope
we made the right choices!

I am quite interested to learn how IVM interacts with SERIALIZABLE.

A couple of superficial review comments:

+            const char *aggname = get_func_name(aggref->aggfnoid);
...
+            else if (!strcmp(aggname, "sum"))

I guess you need a more robust way to detect the supported aggregates
than their name, or I guess some way for aggregates themselves to
specify that they support this and somehow supply the extra logic.
Perhaps I just waid what Greg Stark already said, except not as well.

+ elog(ERROR, "Aggrege function %s is not
supported", aggname);

s/Aggrege/aggregate/

Of course it is not helpful to comment on typos at this early stage,
it's just that this one appears many times in the test output :-)

+static bool
+isIvmColumn(const char *s)
+{
+    char pre[7];
+
+     strlcpy(pre, s, sizeof(pre));
+    return (strcmp(pre, "__ivm_") == 0);
+}

What about strncmp(s, "__ivm_", 6) == 0)? As for the question of how
to reserve a namespace for system columns that won't clash with user
columns, according to our manual the SQL standard doesn't allow $ in
identifier names, and according to my copy SQL92 "intermediate SQL"
doesn't allow identifiers that end in an underscore. I don't know
what the best answer is but we should probably decide on a something
based the standard.

As for how to make internal columns invisible to SELECT *, previously
there have been discussions about doing that using a new flag in
pg_attribute:

/messages/by-id/CAEepm=3ZHh=p0nEEnVbs1Dig_UShPzHUcMNAqvDQUgYgcDo-pA@mail.gmail.com

+                            "WITH t AS ("
+                            "  SELECT diff.__ivm_count__,
(diff.__ivm_count__ = mv.__ivm_count__) AS for_dlt, mv.ctid"
+                            ", %s"
+                            "  FROM %s AS mv, %s AS diff WHERE (%s) = (%s)"
+                            "), updt AS ("
+                            "  UPDATE %s AS mv SET __ivm_count__ =
mv.__ivm_count__ - t.__ivm_count__"
+                            ", %s "
+                            "  FROM t WHERE mv.ctid = t.ctid AND NOT for_dlt"
+                            ") DELETE FROM %s AS mv USING t WHERE
mv.ctid = t.ctid AND for_dlt;",

I fully understand that this is POC code, but I am curious about one
thing. These queries that are executed by apply_delta() would need to
be converted to C, or at least used reusable plans, right? Hmm,
creating and dropping temporary tables every time is a clue that the
ultimate form of this should be tuplestores and C code, I think,
right?

Moreover, some regression test are added for aggregate functions support.
This is Hoshiai-san's work.

Great. Next time you post a WIP patch, could you please fix this
small compiler warning?

describe.c: In function ‘describeOneTableDetails’:
describe.c:3270:55: error: ‘*((void *)&tableinfo+48)’ may be used
uninitialized in this function [-Werror=maybe-uninitialized]
if (verbose && tableinfo.relkind == RELKIND_MATVIEW && tableinfo.isivm)
^
describe.c:1495:4: note: ‘*((void *)&tableinfo+48)’ was declared here
} tableinfo;
^

Then our unofficial automatic CI system[1]cfbot.cputube.org will run these tests every
day, which sometimes finds problems.

[1]: cfbot.cputube.org

--
Thomas Munro
https://enterprisedb.com

#20Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Thomas Munro (#19)
Re: Implementing Incremental View Maintenance

As for how to make internal columns invisible to SELECT *, previously
there have been discussions about doing that using a new flag in
pg_attribute:

/messages/by-id/CAEepm=3ZHh=p0nEEnVbs1Dig_UShPzHUcMNAqvDQUgYgcDo-pA@mail.gmail.com

Now that I realized that there are several use cases for invisible
columns, I think this is the way what we shoud go for.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#21Takuma Hoshiai
takuma.hoshiai@gmail.com
In reply to: Thomas Munro (#19)
Re: Implementing Incremental View Maintenance

Hi Thomas,

2019年7月8日(月) 15:32 Thomas Munro <thomas.munro@gmail.com>:

On Fri, Jun 28, 2019 at 10:56 PM Yugo Nagata <nagata@sraoss.co.jp> wrote:

Attached is a WIP patch of IVM which supports some aggregate functions.

Hi Nagata-san and Hoshiai-san,

Thank you for working on this. I enjoyed your talk at PGCon. I've
added Kevin Grittner just in case he missed this thread; he has talked
often about implementing the counting algorithm, and he wrote the
"trigger transition tables" feature to support exactly this. While
integrating trigger transition tables with the new partition features,
we had to make a number of decisions about how that should work, and
we tried to come up with answers that would work for IMV, and I hope
we made the right choices!

I am quite interested to learn how IVM interacts with SERIALIZABLE.

Nagata-san has been studying this. Nagata-san, any comment?

A couple of superficial review comments:

Thank you for your review comments.
Please find attached patches. The some of your review is reflected in patch
too.

We manage and update IVM on following github repository.
https://github.com/sraoss/pgsql-ivm
you also can found latest WIP patch here.

+            const char *aggname = get_func_name(aggref->aggfnoid);
...
+            else if (!strcmp(aggname, "sum"))

I guess you need a more robust way to detect the supported aggregates
than their name, or I guess some way for aggregates themselves to
specify that they support this and somehow supply the extra logic.
Perhaps I just waid what Greg Stark already said, except not as well.

We have recognized the issue and are welcome any input.

+ elog(ERROR, "Aggrege function %s is not

supported", aggname);

s/Aggrege/aggregate/

I fixed this typo.

Of course it is not helpful to comment on typos at this early stage,

it's just that this one appears many times in the test output :-)

+static bool
+isIvmColumn(const char *s)
+{
+    char pre[7];
+
+     strlcpy(pre, s, sizeof(pre));
+    return (strcmp(pre, "__ivm_") == 0);
+}

What about strncmp(s, "__ivm_", 6) == 0)?

I agree with you, I fixed it.

As for the question of how

to reserve a namespace for system columns that won't clash with user
columns, according to our manual the SQL standard doesn't allow $ in
identifier names, and according to my copy SQL92 "intermediate SQL"
doesn't allow identifiers that end in an underscore. I don't know
what the best answer is but we should probably decide on a something
based the standard.

As for how to make internal columns invisible to SELECT *, previously
there have been discussions about doing that using a new flag in
pg_attribute:

/messages/by-id/CAEepm=3ZHh=p0nEEnVbs1Dig_UShPzHUcMNAqvDQUgYgcDo-pA@mail.gmail.com

+                            "WITH t AS ("
+                            "  SELECT diff.__ivm_count__,
(diff.__ivm_count__ = mv.__ivm_count__) AS for_dlt, mv.ctid"
+                            ", %s"
+                            "  FROM %s AS mv, %s AS diff WHERE (%s) =
(%s)"
+                            "), updt AS ("
+                            "  UPDATE %s AS mv SET __ivm_count__ =
mv.__ivm_count__ - t.__ivm_count__"
+                            ", %s "
+                            "  FROM t WHERE mv.ctid = t.ctid AND NOT
for_dlt"
+                            ") DELETE FROM %s AS mv USING t WHERE
mv.ctid = t.ctid AND for_dlt;",

I fully understand that this is POC code, but I am curious about one
thing. These queries that are executed by apply_delta() would need to
be converted to C, or at least used reusable plans, right? Hmm,
creating and dropping temporary tables every time is a clue that the
ultimate form of this should be tuplestores and C code, I think,
right?

Nagata-san is investing the issue.

Moreover, some regression test are added for aggregate functions support.
This is Hoshiai-san's work.

Great. Next time you post a WIP patch, could you please fix this
small compiler warning?

describe.c: In function ‘describeOneTableDetails’:
describe.c:3270:55: error: ‘*((void *)&tableinfo+48)’ may be used
uninitialized in this function [-Werror=maybe-uninitialized]
if (verbose && tableinfo.relkind == RELKIND_MATVIEW && tableinfo.isivm)
^
describe.c:1495:4: note: ‘*((void *)&tableinfo+48)’ was declared here
} tableinfo;
^

It is fixed too.

Then our unofficial automatic CI system[1] will run these tests every

day, which sometimes finds problems.

[1] cfbot.cputube.org

--
Thomas Munro
https://enterprisedb.com

Best regards,

Takuma Hoshiai

#22Takuma Hoshiai
hoshiai@sraoss.co.jp
In reply to: Takuma Hoshiai (#21)
1 attachment(s)
Re: Implementing Incremental View Maintenance

On Wed, 10 Jul 2019 11:07:15 +0900
Takuma Hoshiai <takuma.hoshiai@gmail.com> wrote:

Hi Thomas,

2019年7月8日(月) 15:32 Thomas Munro <thomas.munro@gmail.com>:

On Fri, Jun 28, 2019 at 10:56 PM Yugo Nagata <nagata@sraoss.co.jp> wrote:

Attached is a WIP patch of IVM which supports some aggregate functions.

Hi Nagata-san and Hoshiai-san,

Thank you for working on this. I enjoyed your talk at PGCon. I've
added Kevin Grittner just in case he missed this thread; he has talked
often about implementing the counting algorithm, and he wrote the
"trigger transition tables" feature to support exactly this. While
integrating trigger transition tables with the new partition features,
we had to make a number of decisions about how that should work, and
we tried to come up with answers that would work for IMV, and I hope
we made the right choices!

I am quite interested to learn how IVM interacts with SERIALIZABLE.

Nagata-san has been studying this. Nagata-san, any comment?

A couple of superficial review comments:

Thank you for your review comments.
Please find attached patches. The some of your review is reflected in patch
too.

Sorry, I forgot attaching patch.
In addition, avg() function is supported newly. We found a issue
when use avg() with IVM, added its reproduction case in
regressio test. We are being to fix now.

We manage and update IVM on following github repository.
https://github.com/sraoss/pgsql-ivm
you also can found latest WIP patch here.

+            const char *aggname = get_func_name(aggref->aggfnoid);
...
+            else if (!strcmp(aggname, "sum"))

I guess you need a more robust way to detect the supported aggregates
than their name, or I guess some way for aggregates themselves to
specify that they support this and somehow supply the extra logic.
Perhaps I just waid what Greg Stark already said, except not as well.

We have recognized the issue and are welcome any input.

+ elog(ERROR, "Aggrege function %s is not
supported", aggname);

s/Aggrege/aggregate/

I fixed this typo.

Of course it is not helpful to comment on typos at this early stage,
it's just that this one appears many times in the test output :-)

+static bool
+isIvmColumn(const char *s)
+{
+    char pre[7];
+
+     strlcpy(pre, s, sizeof(pre));
+    return (strcmp(pre, "__ivm_") == 0);
+}

What about strncmp(s, "__ivm_", 6) == 0)?

I agree with you, I fixed it.

As for the question of how
to reserve a namespace for system columns that won't clash with user
columns, according to our manual the SQL standard doesn't allow $ in
identifier names, and according to my copy SQL92 "intermediate SQL"
doesn't allow identifiers that end in an underscore. I don't know
what the best answer is but we should probably decide on a something
based the standard.

As for how to make internal columns invisible to SELECT *, previously
there have been discussions about doing that using a new flag in
pg_attribute:

/messages/by-id/CAEepm=3ZHh=p0nEEnVbs1Dig_UShPzHUcMNAqvDQUgYgcDo-pA@mail.gmail.com

+                            "WITH t AS ("
+                            "  SELECT diff.__ivm_count__,
(diff.__ivm_count__ = mv.__ivm_count__) AS for_dlt, mv.ctid"
+                            ", %s"
+                            "  FROM %s AS mv, %s AS diff WHERE (%s) =
(%s)"
+                            "), updt AS ("
+                            "  UPDATE %s AS mv SET __ivm_count__ =
mv.__ivm_count__ - t.__ivm_count__"
+                            ", %s "
+                            "  FROM t WHERE mv.ctid = t.ctid AND NOT
for_dlt"
+                            ") DELETE FROM %s AS mv USING t WHERE
mv.ctid = t.ctid AND for_dlt;",

I fully understand that this is POC code, but I am curious about one
thing. These queries that are executed by apply_delta() would need to
be converted to C, or at least used reusable plans, right? Hmm,
creating and dropping temporary tables every time is a clue that the
ultimate form of this should be tuplestores and C code, I think,
right?

Nagata-san is investing the issue.

Moreover, some regression test are added for aggregate functions support.
This is Hoshiai-san's work.

Great. Next time you post a WIP patch, could you please fix this
small compiler warning?

describe.c: In function ‘describeOneTableDetails’:
describe.c:3270:55: error: ‘*((void *)&tableinfo+48)’ may be used
uninitialized in this function [-Werror=maybe-uninitialized]
if (verbose && tableinfo.relkind == RELKIND_MATVIEW && tableinfo.isivm)
^
describe.c:1495:4: note: ‘*((void *)&tableinfo+48)’ was declared here
} tableinfo;
^

It is fixed too.

Then our unofficial automatic CI system[1] will run these tests every
day, which sometimes finds problems.

[1] cfbot.cputube.org

--
Thomas Munro
https://enterprisedb.com

Best regards,

Takuma Hoshiai

--
Takuma Hoshiai <hoshiai@sraoss.co.jp>

Attachments:

WIP_immediate_IVM_v4.patchapplication/octet-stream; name=WIP_immediate_IVM_v4.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3428a7c..05cde06 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1953,6 +1953,13 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
      </row>
 
      <row>
+      <entry><structfield>relisivm</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if materialized view enables incremental view maintenance</entry>
+     </row>
+
+     <row>
       <entry><structfield>relrewrite</structfield></entry>
       <entry><type>oid</type></entry>
       <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml
index ec8847e..a23366a 100644
--- a/doc/src/sgml/ref/create_materialized_view.sgml
+++ b/doc/src/sgml/ref/create_materialized_view.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
+CREATE [ INCREMENTAL ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
     [ (<replaceable>column_name</replaceable> [, ...] ) ]
     [ USING <replaceable class="parameter">method</replaceable> ]
     [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
@@ -55,6 +55,16 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
 
   <variablelist>
    <varlistentry>
+    <term><literal>INCREMENTAL</literal></term>
+    <listitem>
+     <para>
+      If specified, a materialized view enables incremental view maintenance.
+      You can replace only the contents of a materialized view, which based rows are changed.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>IF NOT EXISTS</literal></term>
     <listitem>
      <para>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 3b8c8b1..a69a183 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -891,6 +891,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
+	values[Anum_pg_class_relisivm - 1] = BoolGetDatum(rd_rel->relisivm);
 	if (relacl != (Datum) 0)
 		values[Anum_pg_class_relacl - 1] = relacl;
 	else
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bb60b23..0e22a64 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -910,6 +910,7 @@ index_create(Relation heapRelation,
 	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
 	indexRelation->rd_rel->relam = accessMethodObjectId;
 	indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid);
+	indexRelation->rd_rel->relisivm = false;
 
 	/*
 	 * store index's pg_class entry
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 4c1d909..06073d0 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -51,6 +51,16 @@
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
 
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
+#include "commands/trigger.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
+#include "parser/parse_func.h"
+#include "nodes/print.h"
+#include "optimizer/optimizer.h"
+#include "commands/defrem.h"
+
 
 typedef struct
 {
@@ -74,6 +84,8 @@ static bool intorel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void intorel_shutdown(DestReceiver *self);
 static void intorel_destroy(DestReceiver *self);
 
+static void CreateIvmTrigger(Oid relOid, Oid viewOid, char *matviewname, int16 type);
+static void CreateIvmTriggersOnBaseTables(Query *qry, Node *jtnode, Oid matviewOid, char* matviewname);
 
 /*
  * create_ctas_internal
@@ -109,6 +121,8 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	create->oncommit = into->onCommit;
 	create->tablespacename = into->tableSpaceName;
 	create->if_not_exists = false;
+	/* Using Materialized view only */
+	create->ivm = into->ivm;
 	create->accessMethod = into->accessMethod;
 
 	/*
@@ -239,6 +253,7 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 	List	   *rewritten;
 	PlannedStmt *plan;
 	QueryDesc  *queryDesc;
+	Query	   *copied_query;
 
 	if (stmt->if_not_exists)
 	{
@@ -319,7 +334,100 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		 * and is executed repeatedly.  (See also the same hack in EXPLAIN and
 		 * PREPARE.)
 		 */
-		rewritten = QueryRewrite(copyObject(query));
+
+		copied_query = copyObject(query);
+		if (is_matview && into->ivm)
+		{
+			TargetEntry *tle;
+			Node *node;
+			ParseState *pstate = make_parsestate(NULL);
+			FuncCall *fn;
+
+			/* group keys must be in targetlist */
+			if (copied_query->groupClause)
+			{
+				ListCell *lc;
+				foreach(lc, copied_query->groupClause)
+				{
+					SortGroupClause *scl = (SortGroupClause *) lfirst(lc);
+					TargetEntry *tle = get_sortgroupclause_tle(scl, copied_query->targetList);
+
+					if (tle->resjunk)
+						elog(ERROR, "GROUP BY expression must appear in select list for incremental materialized views");
+				}
+			}
+			else if (!copied_query->hasAggs)
+				copied_query->groupClause = transformDistinctClause(NULL, &copied_query->targetList, copied_query->sortClause, false);
+
+			if (copied_query->hasAggs)
+			{
+				ListCell *lc;
+				List *agg_counts = NIL;
+				AttrNumber next_resno = list_length(copied_query->targetList) + 1;
+				Const	*dmy_arg = makeConst(INT4OID,
+											 -1,
+											 InvalidOid,
+											 sizeof(int32),
+											 Int32GetDatum(1),
+											 false,
+											 true); /* pass by value */
+
+				foreach(lc, copied_query->targetList)
+				{
+					TargetEntry *tle = (TargetEntry *) lfirst(lc);
+					TargetEntry *tle_count;
+
+
+					if (IsA(tle->expr, Aggref))
+					{
+						Aggref *aggref = (Aggref *) tle->expr;
+						const char *aggname = get_func_name(aggref->aggfnoid);
+
+						/* XXX: need some generalization */
+						if (strcmp(aggname, "sum") !=0
+							&& strcmp(aggname, "count") != 0
+							&& strcmp(aggname, "avg") != 0
+						)
+							elog(ERROR, "aggregate function %s is not supported", aggname);
+
+						/* For aggregate functions except to count, add count func with the same arg parameters. */
+						if (strcmp(aggname, "count") != 0)
+						{
+							fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+
+							/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+							node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+							((Aggref *)node)->args = aggref->args;
+
+							tle_count = makeTargetEntry((Expr *) node,
+														next_resno,
+														pstrdup(makeObjectName("__ivm_count",tle->resname, "_")),
+														false);
+							agg_counts = lappend(agg_counts, tle_count);
+							next_resno++;
+						}
+
+					}
+				}
+				copied_query->targetList = list_concat(copied_query->targetList, agg_counts);
+
+			}
+
+			/* Add count(*) for counting algorithm */
+			fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+			fn->agg_star = true;
+
+			node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+			tle = makeTargetEntry((Expr *) node,
+								  	list_length(copied_query->targetList) + 1,
+								  	pstrdup("__ivm_count__"),
+								  	false);
+			copied_query->targetList = lappend(copied_query->targetList, tle);
+			copied_query->hasAggs = true;
+		}
+
+		rewritten = QueryRewrite(copied_query);
 
 		/* SELECT should never rewrite to more or less than one SELECT query */
 		if (list_length(rewritten) != 1)
@@ -378,11 +486,65 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 
 		/* Restore userid and security context */
 		SetUserIdAndSecContext(save_userid, save_sec_context);
+
+
+		if (into->ivm)
+		{
+			char	   *matviewname;
+			Oid matviewOid = address.objectId;
+			Relation matviewRel = table_open(matviewOid, NoLock);
+			matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+													 RelationGetRelationName(matviewRel));
+			copied_query = copyObject(query);
+			AcquireRewriteLocks(copied_query, true, false);
+
+			CreateIvmTriggersOnBaseTables(copied_query, (Node *)copied_query->jointree, matviewOid, matviewname);
+
+			table_close(matviewRel, NoLock);
+		}
 	}
 
 	return address;
 }
 
+static void CreateIvmTriggersOnBaseTables(Query *qry, Node *jtnode, Oid matviewOid, char* matviewname)
+{
+
+	if (jtnode == NULL)
+		return;
+	if (IsA(jtnode, RangeTblRef))
+	{
+		int			rti = ((RangeTblRef *) jtnode)->rtindex;
+		RangeTblEntry *rte = rt_fetch(rti, qry->rtable);
+
+		if (rte->rtekind == RTE_RELATION)
+		{
+			CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_INSERT);
+			CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_DELETE);
+			CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_UPDATE);
+		}
+		else
+			elog(ERROR, "unsupported RTE kind: %d", (int) rte->rtekind);
+	}
+	else if (IsA(jtnode, FromExpr))
+	{
+		FromExpr   *f = (FromExpr *) jtnode;
+		ListCell   *l;
+
+		foreach(l, f->fromlist)
+			CreateIvmTriggersOnBaseTables(qry, lfirst(l), matviewOid, matviewname);
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+
+		CreateIvmTriggersOnBaseTables(qry, j->larg, matviewOid, matviewname);
+		CreateIvmTriggersOnBaseTables(qry, j->rarg, matviewOid, matviewname);
+	}
+	else
+		elog(ERROR, "unrecognized node type: %d", (int) nodeTag(jtnode));
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -548,6 +710,11 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 		SetMatViewPopulatedState(intoRelationDesc, true);
 
 	/*
+	 * Mark relisivm field, if it's a matview and into->ivm is true.
+	 */
+	if (is_matview && into->ivm)
+		SetMatViewIVMState(intoRelationDesc, true);
+	/*
 	 * Fill private fields of myState for use by later routines
 	 */
 	myState->rel = intoRelationDesc;
@@ -619,3 +786,74 @@ intorel_destroy(DestReceiver *self)
 {
 	pfree(self);
 }
+
+static void
+CreateIvmTrigger(Oid relOid, Oid viewOid, char *matviewname, int16 type)
+{
+	CreateTrigStmt *ivm_trigger;
+	List *transitionRels = NIL;
+	ObjectAddress address, refaddr;
+
+	refaddr.classId = RelationRelationId;
+	refaddr.objectId = viewOid;
+	refaddr.objectSubId = 0;
+
+
+	ivm_trigger = makeNode(CreateTrigStmt);
+	ivm_trigger->relation = NULL;
+	ivm_trigger->row = false;
+	ivm_trigger->timing = TRIGGER_TYPE_AFTER;
+
+	ivm_trigger->events = type;
+
+	switch (type)
+	{
+		case TRIGGER_TYPE_INSERT:
+			ivm_trigger->trigname = "IVM_trigger_ins";
+			break;
+		case TRIGGER_TYPE_DELETE:
+			ivm_trigger->trigname = "IVM_trigger_del";
+			break;
+		case TRIGGER_TYPE_UPDATE:
+			ivm_trigger->trigname = "IVM_trigger_upd";
+			break;
+	}
+
+	if (type == TRIGGER_TYPE_INSERT || type == TRIGGER_TYPE_UPDATE)
+	{
+		TriggerTransition *n = makeNode(TriggerTransition);
+		n->name = "ivm_newtable";
+		n->isNew = true;
+		n->isTable = true;
+
+		transitionRels = lappend(transitionRels, n);
+	}
+	if (type == TRIGGER_TYPE_DELETE || type == TRIGGER_TYPE_UPDATE)
+	{
+		TriggerTransition *n = makeNode(TriggerTransition);
+		n->name = "ivm_oldtable";
+		n->isNew = false;
+		n->isTable = true;
+
+		transitionRels = lappend(transitionRels, n);
+	}
+
+	ivm_trigger->funcname = SystemFuncName("IVM_immediate_maintenance");
+
+	ivm_trigger->columns = NIL;
+	ivm_trigger->transitionRels = transitionRels;
+	ivm_trigger->whenClause = NULL;
+	ivm_trigger->isconstraint = false;
+	ivm_trigger->deferrable = false;
+	ivm_trigger->initdeferred = false;
+	ivm_trigger->constrrel = NULL;
+	ivm_trigger->args = list_make1(makeString(matviewname));
+
+	address = CreateTrigger(ivm_trigger, NULL, relOid, InvalidOid, InvalidOid,
+						 InvalidOid, InvalidOid, InvalidOid, NULL, true, false);
+
+	recordDependencyOn(&address, &refaddr, DEPENDENCY_AUTO);
+
+	/* Make changes-so-far visible */
+	CommandCounterIncrement();
+}
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 537d0e8..edede5a 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -46,6 +46,15 @@
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 
+#include "utils/regproc.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_func.h"
+#include "nodes/print.h"
+#include "catalog/pg_type_d.h"
+#include "optimizer/optimizer.h"
+#include "commands/defrem.h"
+
 
 typedef struct
 {
@@ -65,7 +74,8 @@ static bool transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void transientrel_shutdown(DestReceiver *self);
 static void transientrel_destroy(DestReceiver *self);
 static uint64 refresh_matview_datafill(DestReceiver *dest, Query *query,
-									   const char *queryString);
+						 QueryEnvironment *queryEnv,
+						 const char *queryString);
 static char *make_temptable_name_n(char *tempname, int n);
 static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 								   int save_sec_context);
@@ -74,6 +84,9 @@ static bool is_usable_unique_index(Relation indexRel);
 static void OpenMatViewIncrementalMaintenance(void);
 static void CloseMatViewIncrementalMaintenance(void);
 
+static void apply_delta(Oid matviewOid, Oid tempOid_new, Oid tempOid_old,
+			Query *query, Oid relowner, int save_sec_context);
+
 /*
  * SetMatViewPopulatedState
  *		Mark a materialized view as populated, or not.
@@ -115,6 +128,46 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
 }
 
 /*
+ * SetMatViewIVMState
+ *		Mark a materialized view as IVM, or not.
+ *
+ * NOTE: caller must be holding an appropriate lock on the relation.
+ */
+void
+SetMatViewIVMState(Relation relation, bool newstate)
+{
+	Relation	pgrel;
+	HeapTuple	tuple;
+
+	Assert(relation->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Update relation's pg_class entry.  Crucial side-effect: other backends
+	 * (and this one too!) are sent SI message to make them rebuild relcache
+	 * entries.
+	 */
+	pgrel = table_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(relation)));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u",
+			 RelationGetRelid(relation));
+
+	((Form_pg_class) GETSTRUCT(tuple))->relisivm = newstate;
+
+	CatalogTupleUpdate(pgrel, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	table_close(pgrel, RowExclusiveLock);
+
+	/*
+	 * Advance command counter to make the updated pg_class row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+}
+
+/*
  * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command
  *
  * This refreshes the materialized view by creating a new table and swapping
@@ -311,7 +364,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 
 	/* Generate the data, if wanted. */
 	if (!stmt->skipData)
-		processed = refresh_matview_datafill(dest, dataQuery, queryString);
+		processed = refresh_matview_datafill(dest, dataQuery, NULL, queryString);
 
 	/* Make the matview match the newly generated data. */
 	if (concurrent)
@@ -369,6 +422,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  */
 static uint64
 refresh_matview_datafill(DestReceiver *dest, Query *query,
+						 QueryEnvironment *queryEnv,
 						 const char *queryString)
 {
 	List	   *rewritten;
@@ -405,7 +459,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 	/* Create a QueryDesc, redirecting output to our tuple receiver */
 	queryDesc = CreateQueryDesc(plan, queryString,
 								GetActiveSnapshot(), InvalidSnapshot,
-								dest, NULL, NULL, 0);
+								dest, NULL, queryEnv ? queryEnv: NULL, 0);
 
 	/* call ExecutorStart to prepare the plan for execution */
 	ExecutorStart(queryDesc, 0);
@@ -926,3 +980,664 @@ CloseMatViewIncrementalMaintenance(void)
 	matview_maintenance_depth--;
 	Assert(matview_maintenance_depth >= 0);
 }
+
+/*
+ * IVM trigger function
+ */
+
+Datum
+IVM_immediate_maintenance(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	Relation	rel;
+	Oid relid;
+	Oid matviewOid;
+	Query	   *query, *old_delta_qry, *new_delta_qry;
+	char*		matviewname = trigdata->tg_trigger->tgargs[0];
+	List	   *names;
+	Relation matviewRel;
+	int old_depth = matview_maintenance_depth;
+
+	Oid			tableSpace;
+	Oid			relowner;
+	Oid			OIDDelta_new = InvalidOid;
+	Oid			OIDDelta_old = InvalidOid;
+	DestReceiver *dest_new = NULL, *dest_old = NULL;
+	char		relpersistence;
+	Oid			save_userid;
+	int			save_sec_context;
+	int			save_nestlevel;
+
+	ParseState *pstate;
+	QueryEnvironment *queryEnv = create_queryEnv();
+
+	Const	*dmy_arg = makeConst(INT4OID,
+								 -1,
+								 InvalidOid,
+								 sizeof(int32),
+								 Int32GetDatum(1),
+								 false,
+								 true); /* pass by value */
+
+	/* Create a dummy ParseState for addRangeTableEntryForENR */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+
+	names = stringToQualifiedNameList(matviewname);
+
+	/*
+	 * Wait for concurrent transactions which update this materialized view at READ COMMITED.
+	 * This is needed to see changes commited in othre transactions. No wait and raise an error
+	 * at REPEATABLE READ or SERIALIZABLE to prevent anormal update of matviews.
+	 * XXX: dead-lock is possible here.
+	 */
+	if (!IsolationUsesXactSnapshot())
+		matviewOid = RangeVarGetRelid(makeRangeVarFromNameList(names), ExclusiveLock, true);
+	else
+		matviewOid = RangeVarGetRelidExtended(makeRangeVarFromNameList(names), ExclusiveLock, RVR_MISSING_OK | RVR_NOWAIT, NULL, NULL);
+
+	matviewRel = table_open(matviewOid, NoLock);
+
+	/*
+	 * Get and push the latast snapshot to see any changes which is commited during waiting in
+	 * other transactions at READ COMMITTED level.
+	 * XXX: Is this safe?
+	 */
+	PushActiveSnapshot(GetTransactionSnapshot());
+
+	/* Make sure it is a materialized view. */
+	if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("\"%s\" is not a materialized view",
+						RelationGetRelationName(matviewRel))));
+
+	rel = trigdata->tg_relation;
+	relid = rel->rd_id;
+
+	query = get_view_query(matviewRel);
+
+	new_delta_qry = copyObject(query);
+	old_delta_qry = copyObject(query);
+
+	if (trigdata->tg_newtable)
+	{
+		RangeTblEntry *rte;
+		ListCell   *lc;
+
+		TargetEntry *tle;
+		Node *node;
+		FuncCall *fn;
+
+		EphemeralNamedRelation enr =
+			palloc(sizeof(EphemeralNamedRelationData));
+
+		enr->md.name = trigdata->tg_trigger->tgnewtable;
+		enr->md.reliddesc = trigdata->tg_relation->rd_id;
+		enr->md.tupdesc = NULL;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(trigdata->tg_newtable);
+		enr->reldata = trigdata->tg_newtable;
+		register_ENR(queryEnv, enr);
+
+		rte = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+		new_delta_qry->rtable = lappend(new_delta_qry->rtable, rte);
+
+		foreach(lc, new_delta_qry->rtable)
+		{
+			RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+			if (r->relid == relid)
+			{
+				lfirst(lc) = rte;
+				break;
+			}
+		}
+
+		if (query->hasAggs)
+		{
+			ListCell *lc;
+			List *agg_counts = NIL;
+			AttrNumber next_resno = list_length(query->targetList) + 1;
+			Node *node;
+
+			foreach(lc, query->targetList)
+			{
+				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+				TargetEntry *tle_count;
+
+				if (IsA(tle->expr, Aggref))
+				{
+					Aggref *aggref = (Aggref *) tle->expr;
+					const char *aggname = get_func_name(aggref->aggfnoid);
+
+					if (strcmp(aggname, "count") != 0)
+					{
+						fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+
+						node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+						((Aggref *)node)->args = aggref->args;
+
+						tle_count = makeTargetEntry((Expr *) node,
+												next_resno,
+												pstrdup(makeObjectName("__ivm_count",tle->resname, "_")),
+												false);
+						agg_counts = lappend(agg_counts, tle_count);
+						next_resno++;
+					}
+				}
+
+			}
+			new_delta_qry->targetList = list_concat(new_delta_qry->targetList, agg_counts);
+		}
+
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+		fn->agg_star = true;
+		if (!new_delta_qry->groupClause && !new_delta_qry->hasAggs)
+			new_delta_qry->groupClause = transformDistinctClause(NULL, &new_delta_qry->targetList, new_delta_qry->sortClause, false);
+
+		node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+		tle = makeTargetEntry((Expr *) node,
+								  list_length(new_delta_qry->targetList) + 1,
+								  pstrdup("__ivm_count__"),
+								  false);
+		new_delta_qry->targetList = lappend(new_delta_qry->targetList, tle);
+		new_delta_qry->hasAggs = true;
+	}
+
+	if (trigdata->tg_oldtable)
+	{
+		RangeTblEntry *rte;
+		ListCell   *lc;
+
+		TargetEntry *tle;
+		Node *node;
+		FuncCall *fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+
+		EphemeralNamedRelation enr =
+			palloc(sizeof(EphemeralNamedRelationData));
+
+		enr->md.name = trigdata->tg_trigger->tgoldtable;
+		enr->md.reliddesc = trigdata->tg_relation->rd_id;
+		enr->md.tupdesc = NULL;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(trigdata->tg_oldtable);
+		enr->reldata = trigdata->tg_oldtable;
+		register_ENR(queryEnv, enr);
+
+		rte = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+		old_delta_qry->rtable = lappend(old_delta_qry->rtable, rte);
+
+		foreach(lc, old_delta_qry->rtable)
+		{
+			RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+			if (r->relid == relid)
+			{
+				lfirst(lc) = rte;
+				break;
+			}
+		}
+
+		if (query->hasAggs)
+		{
+			ListCell *lc;
+			List *agg_counts = NIL;
+			AttrNumber next_resno = list_length(query->targetList) + 1;
+			Node *node;
+
+			foreach(lc, query->targetList)
+			{
+				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+				TargetEntry *tle_count;
+
+				if (IsA(tle->expr, Aggref))
+				{
+					Aggref *aggref = (Aggref *) tle->expr;
+					const char *aggname = get_func_name(aggref->aggfnoid);
+
+					if (strcmp(aggname, "count") != 0)
+					{
+						fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+
+						node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+						((Aggref *)node)->args = aggref->args;
+
+						tle_count = makeTargetEntry((Expr *) node,
+												next_resno,
+												pstrdup(makeObjectName("__ivm_count",tle->resname, "_")),
+												false);
+						agg_counts = lappend(agg_counts, tle_count);
+						next_resno++;
+					}
+				}
+
+			}
+			old_delta_qry->targetList = list_concat(old_delta_qry->targetList, agg_counts);
+		}
+
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+		fn->agg_star = true;
+
+		if (!old_delta_qry->groupClause && !old_delta_qry->hasAggs)
+			old_delta_qry->groupClause = transformDistinctClause(NULL, &old_delta_qry->targetList, old_delta_qry->sortClause, false);
+
+		node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+		tle = makeTargetEntry((Expr *) node,
+								  list_length(old_delta_qry->targetList) + 1,
+								  pstrdup("__ivm_count__"),
+								  false);
+		old_delta_qry->targetList = lappend(old_delta_qry->targetList, tle);
+		old_delta_qry->hasAggs = true;
+	}
+
+
+	/*
+	 * Check for active uses of the relation in the current transaction, such
+	 * as open scans.
+	 *
+	 * NB: We count on this to protect us against problems with refreshing the
+	 * data using TABLE_INSERT_FROZEN.
+	 */
+	CheckTableNotInUse(matviewRel, "REFRESH MATERIALIZED VIEW");
+
+	relowner = matviewRel->rd_rel->relowner;
+
+	/*
+	 * Switch to the owner's userid, so that any functions are run as that
+	 * user.  Also arrange to make GUC variable changes local to this command.
+	 * Don't lock it down too tight to create a temporary table just yet.  We
+	 * will switch modes when we are about to execute user code.
+	 */
+	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+	save_nestlevel = NewGUCNestLevel();
+
+	tableSpace = GetDefaultTablespace(RELPERSISTENCE_TEMP, false);
+	relpersistence = RELPERSISTENCE_TEMP;
+
+	/*
+	 * Create the transient table that will receive the regenerated data. Lock
+	 * it against access by any other process until commit (by which time it
+	 * will be gone).
+	 */
+	if (trigdata->tg_newtable)
+	{
+		OIDDelta_new = make_new_heap(matviewOid, tableSpace, relpersistence,
+									 ExclusiveLock);
+		LockRelationOid(OIDDelta_new, AccessExclusiveLock);
+		dest_new = CreateTransientRelDestReceiver(OIDDelta_new);
+	}
+	if (trigdata->tg_oldtable)
+	{
+		if (trigdata->tg_newtable)
+			OIDDelta_old = make_new_heap(OIDDelta_new, tableSpace, relpersistence,
+										 ExclusiveLock);
+		else
+			OIDDelta_old = make_new_heap(matviewOid, tableSpace, relpersistence,
+										 ExclusiveLock);
+		LockRelationOid(OIDDelta_old, AccessExclusiveLock);
+		dest_old = CreateTransientRelDestReceiver(OIDDelta_old);
+	}
+
+	/*
+	 * Now lock down security-restricted operations.
+	 */
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+
+	/* Generate the data. */
+	if (trigdata->tg_newtable)
+		refresh_matview_datafill(dest_new, new_delta_qry, queryEnv, NULL);
+	if (trigdata->tg_oldtable)
+		refresh_matview_datafill(dest_old, old_delta_qry, queryEnv, NULL);
+
+	PG_TRY();
+	{
+		apply_delta(matviewOid, OIDDelta_new, OIDDelta_old,
+					query, relowner, save_sec_context);
+	}
+	PG_CATCH();
+	{
+		matview_maintenance_depth = old_depth;
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	/* Pop the original snapshot. */
+	PopActiveSnapshot();
+
+	table_close(matviewRel, NoLock);
+
+	/* Roll back any GUC changes */
+	AtEOXact_GUC(false, save_nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(save_userid, save_sec_context);
+
+	return PointerGetDatum(NULL);
+}
+
+static void
+apply_delta(Oid matviewOid, Oid tempOid_new, Oid tempOid_old,
+			Query *query, Oid relowner, int save_sec_context)
+{
+	StringInfoData querybuf;
+	StringInfoData mvatts_buf, diffatts_buf;
+	StringInfoData mv_gkeys_buf, diff_gkeys_buf, updt_gkeys_buf;
+	StringInfoData diff_aggs_buf, update_aggs_old, update_aggs_new;
+	Relation	matviewRel;
+	Relation	tempRel_new = NULL, tempRel_old = NULL;
+	char	   *matviewname;
+	char	   *tempname_new = NULL, *tempname_old = NULL;
+	ListCell	*lc;
+	char	   *sep, *sep_agg;
+	bool		with_group = query->groupClause != NULL;
+
+
+	initStringInfo(&querybuf);
+	matviewRel = table_open(matviewOid, NoLock);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	if (OidIsValid(tempOid_new))
+	{
+		tempRel_new = table_open(tempOid_new, NoLock);
+		tempname_new = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(tempRel_new)),
+												  RelationGetRelationName(tempRel_new));
+	}
+	if (OidIsValid(tempOid_old))
+	{
+		tempRel_old = table_open(tempOid_old, NoLock);
+		tempname_old = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(tempRel_old)),
+												  RelationGetRelationName(tempRel_old));
+	}
+
+	initStringInfo(&mvatts_buf);
+	initStringInfo(&diffatts_buf);
+	initStringInfo(&diff_aggs_buf);
+	initStringInfo(&update_aggs_old);
+	initStringInfo(&update_aggs_new);
+
+	sep = "";
+	sep_agg= "";
+	foreach (lc, query->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);;
+
+		if (tle->resjunk)
+			continue;
+
+		appendStringInfo(&mvatts_buf, "%s", sep);
+		appendStringInfo(&diffatts_buf, "%s", sep);
+		sep = ", ";
+
+		appendStringInfo(&mvatts_buf, "%s", quote_qualified_identifier("mv", tle->resname));
+		appendStringInfo(&diffatts_buf, "%s", quote_qualified_identifier("diff", tle->resname));
+		if (query->hasAggs && IsA(tle->expr, Aggref))
+		{
+			Aggref *aggref = (Aggref *) tle->expr;
+			const char *aggname = get_func_name(aggref->aggfnoid);
+
+			appendStringInfo(&update_aggs_old, "%s", sep_agg);
+			appendStringInfo(&update_aggs_new, "%s", sep_agg);
+			appendStringInfo(&diff_aggs_buf, "%s", sep_agg);
+
+			sep_agg = ", ";
+
+			if (!strcmp(aggname, "count"))
+			{
+				appendStringInfo(&update_aggs_old,
+					"%s = %s - %s",
+					quote_qualified_identifier(NULL, tle->resname),
+					quote_qualified_identifier("mv", tle->resname),
+					quote_qualified_identifier("t", tle->resname)
+				);
+				appendStringInfo(&update_aggs_new,
+					"%s = %s + %s",
+					quote_qualified_identifier(NULL, tle->resname),
+					quote_qualified_identifier("mv", tle->resname),
+					quote_qualified_identifier("diff", tle->resname)
+				);
+
+				appendStringInfo(&diff_aggs_buf, "%s",
+					quote_qualified_identifier("diff", tle->resname)
+				);
+			}
+			else if (!strcmp(aggname, "sum"))
+			{
+				appendStringInfo(&update_aggs_old,
+					"%s = CASE WHEN %s = %s THEN NULL ELSE COALESCE(%s,0) - COALESCE(%s, 0) END, "
+					"%s = %s - %s",
+					quote_qualified_identifier(NULL, tle->resname),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("t", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("mv", tle->resname),
+					quote_qualified_identifier("t", tle->resname),
+					quote_qualified_identifier(NULL, makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("t", makeObjectName("__ivm_count",tle->resname,"_"))
+				);
+				appendStringInfo(&update_aggs_new,
+					"%s = CASE WHEN %s = 0 AND %s = 0 THEN NULL ELSE COALESCE(%s,0) + COALESCE(%s, 0) END, "
+					"%s = %s + %s",
+					quote_qualified_identifier(NULL, tle->resname),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("mv", tle->resname),
+					quote_qualified_identifier("diff", tle->resname),
+					quote_qualified_identifier(NULL, makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",tle->resname,"_"))
+				);
+
+				appendStringInfo(&diff_aggs_buf, "%s, %s",
+					quote_qualified_identifier("diff", tle->resname),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",tle->resname,"_"))
+				);
+			}
+			else if (!strcmp(aggname, "avg"))
+			{
+				appendStringInfo(&update_aggs_old,
+					"%s = CASE WHEN %s = %s THEN NULL ELSE"
+						"(COALESCE(%s,0) * %s - COALESCE(%s, 0) * %s) / (%s - %s) END, "
+					"%s = %s - %s",
+					quote_qualified_identifier(NULL, tle->resname),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("t", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("mv", tle->resname),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("t", tle->resname),
+					quote_qualified_identifier("t", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("t", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier(NULL, makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("t", makeObjectName("__ivm_count",tle->resname,"_"))
+				);
+				appendStringInfo(&update_aggs_new,
+					"%s = CASE WHEN %s = 0 AND %s = 0 THEN NULL ELSE"
+						" (COALESCE(%s,0) * %s + COALESCE(%s, 0) * %s) / (%s + %s) END, "
+					"%s = %s + %s",
+					quote_qualified_identifier(NULL, tle->resname),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("mv", tle->resname),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("diff", tle->resname),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier(NULL, makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",tle->resname,"_")),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",tle->resname,"_"))
+				);
+
+				appendStringInfo(&diff_aggs_buf, "%s, %s",
+					quote_qualified_identifier("diff", tle->resname),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",tle->resname,"_"))
+				);
+			}
+			else
+				elog(ERROR, "unsupported aggregate function: %s", aggname);
+
+		}
+	}
+
+	if (query->hasAggs)
+	{
+		initStringInfo(&mv_gkeys_buf);
+		initStringInfo(&diff_gkeys_buf);
+		initStringInfo(&updt_gkeys_buf);
+
+		if (with_group)
+		{
+			sep_agg= "";
+			foreach (lc, query->groupClause)
+			{
+				SortGroupClause *sgcl = (SortGroupClause *) lfirst(lc);
+				TargetEntry *tle = get_sortgroupclause_tle(sgcl, query->targetList);
+
+				appendStringInfo(&mv_gkeys_buf, "%s", sep_agg);
+				appendStringInfo(&diff_gkeys_buf, "%s", sep_agg);
+				appendStringInfo(&updt_gkeys_buf, "%s", sep_agg);
+
+				sep_agg = ", ";
+
+				appendStringInfo(&mv_gkeys_buf, "%s", quote_qualified_identifier("mv", tle->resname));
+				appendStringInfo(&diff_gkeys_buf, "%s", quote_qualified_identifier("diff", tle->resname));
+				appendStringInfo(&updt_gkeys_buf, "%s", quote_qualified_identifier("updt", tle->resname));
+			}
+		}
+		else
+		{
+			appendStringInfo(&mv_gkeys_buf, "1");
+			appendStringInfo(&diff_gkeys_buf, "1");
+			appendStringInfo(&updt_gkeys_buf, "1");
+		}
+	}
+
+	/* Open SPI context. */
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	/* Analyze the temp table with the new contents. */
+	if (tempname_new)
+	{
+		appendStringInfo(&querybuf, "ANALYZE %s", tempname_new);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+	if (tempname_old)
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf, "ANALYZE %s", tempname_old);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+
+	OpenMatViewIncrementalMaintenance();
+
+	if (query->hasAggs)
+	{
+		if (tempname_old)
+		{
+			resetStringInfo(&querybuf);
+			appendStringInfo(&querybuf,
+							"WITH t AS ("
+							"  SELECT diff.__ivm_count__, (diff.__ivm_count__ = mv.__ivm_count__) AS for_dlt, mv.ctid"
+							", %s"
+							"  FROM %s AS mv, %s AS diff WHERE (%s) = (%s)"
+							"), updt AS ("
+							"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ - t.__ivm_count__"
+							", %s "
+							"  FROM t WHERE mv.ctid = t.ctid AND NOT for_dlt"
+							") DELETE FROM %s AS mv USING t WHERE mv.ctid = t.ctid AND for_dlt;",
+							diff_aggs_buf.data,
+							matviewname, tempname_old, mv_gkeys_buf.data, diff_gkeys_buf.data,
+							matviewname, update_aggs_old.data, matviewname);
+			if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+				elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+		}
+		if (tempname_new)
+		{
+			resetStringInfo(&querybuf);
+			appendStringInfo(&querybuf,
+							"WITH updt AS ("
+							"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ + diff.__ivm_count__"
+							", %s "
+							"  FROM %s AS diff WHERE (%s) = (%s)"
+							"  RETURNING %s"
+							") INSERT INTO %s (SELECT * FROM %s AS diff WHERE (%s) NOT IN (SELECT %s FROM updt));",
+							matviewname, update_aggs_new.data, tempname_new,
+							mv_gkeys_buf.data, diff_gkeys_buf.data, diff_gkeys_buf.data,
+							matviewname, tempname_new, diff_gkeys_buf.data, updt_gkeys_buf.data);
+			if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+				elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+		}
+	}
+	else
+	{
+		if (tempname_old)
+		{
+			resetStringInfo(&querybuf);
+			appendStringInfo(&querybuf,
+							"WITH t AS ("
+							"  SELECT diff.__ivm_count__, (diff.__ivm_count__ = mv.__ivm_count__) AS for_dlt, mv.ctid"
+							"  FROM %s AS mv, %s AS diff WHERE (%s) = (%s)"
+							"), updt AS ("
+							"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ - t.__ivm_count__"
+							"  FROM t WHERE mv.ctid = t.ctid AND NOT for_dlt"
+							") DELETE FROM %s AS mv USING t WHERE mv.ctid = t.ctid AND for_dlt;",
+							matviewname, tempname_old, mvatts_buf.data, diffatts_buf.data, matviewname, matviewname);
+			if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+				elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+		}
+		if (tempname_new)
+		{
+			resetStringInfo(&querybuf);
+			appendStringInfo(&querybuf,
+							"WITH updt AS ("
+							"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ + diff.__ivm_count__"
+							"  FROM %s AS diff WHERE (%s) = (%s)"
+							"  RETURNING %s"
+							") INSERT INTO %s (SELECT * FROM %s AS diff WHERE (%s) NOT IN (SELECT * FROM updt));",
+							matviewname, tempname_new, mvatts_buf.data, diffatts_buf.data, diffatts_buf.data, matviewname, tempname_new, diffatts_buf.data);
+			if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+				elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+		}
+	}
+
+	/* We're done maintaining the materialized view. */
+	CloseMatViewIncrementalMaintenance();
+
+	if (OidIsValid(tempOid_new))
+		table_close(tempRel_new, NoLock);
+	if (OidIsValid(tempOid_old))
+		table_close(tempRel_old, NoLock);
+
+	table_close(matviewRel, NoLock);
+
+	/* Clean up temp tables. */
+	if (OidIsValid(tempOid_new))
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf, "DROP TABLE %s", tempname_new);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+	if (OidIsValid(tempOid_old))
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf, "DROP TABLE %s", tempname_old);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+
+	/* Close SPI context. */
+	if (SPI_finish() != SPI_OK_FINISH)
+		elog(ERROR, "SPI_finish failed");
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 78deade..8262ac0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2361,6 +2361,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(relisivm);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4f2ebe5..608d477 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2641,6 +2641,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(relisivm);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8400dd3..7897dd9 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3042,6 +3042,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6c2626e..aa01e20 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1366,6 +1366,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 208b4a1..fc71280 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -418,6 +418,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target create_mv_target
+%type <boolean>	incremental
 
 %type <defelt>	createfunc_opt_item common_func_opt_item dostmt_opt_item
 %type <fun_param> func_arg func_arg_with_default table_func_column aggr_arg
@@ -645,7 +646,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
 	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
-	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+	INCLUDING INCREMENT INCREMENTAL INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
 	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
 	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -4054,30 +4055,32 @@ opt_with_data:
  *****************************************************************************/
 
 CreateMatViewStmt:
-		CREATE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
+		CREATE OptNoLog incremental MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $7;
-					ctas->into = $5;
+					ctas->query = $8;
+					ctas->into = $6;
 					ctas->relkind = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = false;
 					/* cram additional flags into the IntoClause */
-					$5->rel->relpersistence = $2;
-					$5->skipData = !($8);
+					$6->rel->relpersistence = $2;
+					$6->skipData = !($9);
+					$6->ivm = $3;
 					$$ = (Node *) ctas;
 				}
-		| CREATE OptNoLog MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
+		| CREATE OptNoLog incremental MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $10;
-					ctas->into = $8;
+					ctas->query = $11;
+					ctas->into = $9;
 					ctas->relkind = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = true;
 					/* cram additional flags into the IntoClause */
-					$8->rel->relpersistence = $2;
-					$8->skipData = !($11);
+					$9->rel->relpersistence = $2;
+					$9->skipData = !($12);
+					$9->ivm = $3;
 					$$ = (Node *) ctas;
 				}
 		;
@@ -4094,9 +4097,14 @@ create_mv_target:
 					$$->tableSpaceName = $5;
 					$$->viewQuery = NULL;		/* filled at analysis time */
 					$$->skipData = false;		/* might get changed later */
+					$$->ivm = false;
 				}
 		;
 
+incremental:	INCREMENTAL				{ $$ = true; }
+				| /*EMPTY*/				{ $$ = false; }
+		;
+
 OptNoLog:	UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
 		;
@@ -15128,6 +15136,7 @@ unreserved_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 77a48b0..f62013d 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -37,6 +37,7 @@
 #include "utils/syscache.h"
 #include "utils/varlena.h"
 
+#include "commands/matview.h"
 
 #define MAX_FUZZY_DISTANCE				3
 
@@ -56,9 +57,10 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							int count, int offset,
 							int rtindex, int sublevels_up,
 							int location, bool include_dropped,
-							List **colnames, List **colvars);
+							List **colnames, List **colvars, bool is_ivm);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool isIvmColumn(const char *s);
 
 
 /*
@@ -1238,6 +1240,7 @@ addRangeTableEntry(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -1317,6 +1320,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -2315,7 +2319,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 						expandTupleDesc(tupdesc, rte->eref,
 										rtfunc->funccolcount, atts_done,
 										rtindex, sublevels_up, location,
-										include_dropped, colnames, colvars);
+										include_dropped, colnames, colvars, false);
 					}
 					else if (functypclass == TYPEFUNC_SCALAR)
 					{
@@ -2567,10 +2571,16 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
 	expandTupleDesc(rel->rd_att, eref, rel->rd_att->natts, 0,
 					rtindex, sublevels_up,
 					location, include_dropped,
-					colnames, colvars);
+					colnames, colvars, RelationIsIVM(rel));
 	relation_close(rel, AccessShareLock);
 }
 
+static bool
+isIvmColumn(const char *s)
+{
+	return (strncmp(s, "__ivm_", 6) == 0); 
+}
+
 /*
  * expandTupleDesc -- expandRTE subroutine
  *
@@ -2584,7 +2594,7 @@ static void
 expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 				int rtindex, int sublevels_up,
 				int location, bool include_dropped,
-				List **colnames, List **colvars)
+				List **colnames, List **colvars, bool is_ivm)
 {
 	ListCell   *aliascell = list_head(eref->colnames);
 	int			varattno;
@@ -2605,6 +2615,9 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 	{
 		Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
 
+		if (is_ivm && isIvmColumn(NameStr(attr->attname)) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		if (attr->attisdropped)
 		{
 			if (include_dropped)
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 7df2b61..5385a03 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -765,7 +765,8 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 														attr->atttypmod))));
 	}
 
-	if (i != resultDesc->natts)
+	/* No check for materialized views since this could have special columns for IVM */
+	if ((!isSelect || requireColumnNameMatch) && i != resultDesc->natts)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 isSelect ?
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 5b047d1..0bba2a6 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -41,6 +41,8 @@
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 
+#include "parser/parser.h"
+#include "commands/matview.h"
 
 /* We use a list of these to detect recursion in RewriteQuery */
 typedef struct rewrite_event
@@ -1597,6 +1599,50 @@ ApplyRetrieveRule(Query *parsetree,
 	if (rule->qual != NULL)
 		elog(ERROR, "cannot handle qualified ON SELECT rule");
 
+	if (RelationIsIVM(relation))
+	{
+		rule_action = copyObject(linitial(rule->actions));
+
+		if (!rule_action->distinctClause && !rule_action->groupClause && !rule_action->hasAggs)
+		{
+			StringInfoData str;
+			RawStmt *raw;
+			Query *sub;
+
+			if (rule_action->hasDistinctOn)
+				elog(ERROR, "DISTINCT ON is not supported in IVM");
+
+			initStringInfo(&str);
+			appendStringInfo(&str, "SELECT mv.*, __ivm_count__ FROM %s mv, generate_series(1, mv.__ivm_count__)",
+						quote_qualified_identifier(get_namespace_name(RelationGetNamespace(relation)),
+													RelationGetRelationName(relation)));
+
+			raw = (RawStmt*)linitial(raw_parser(str.data));
+			sub = transformStmt(make_parsestate(NULL),raw->stmt);
+
+			rte = rt_fetch(rt_index, parsetree->rtable);
+
+			rte->rtekind = RTE_SUBQUERY;
+			rte->subquery = sub;
+			rte->security_barrier = RelationIsSecurityView(relation);
+			/* Clear fields that should not be set in a subquery RTE */
+			rte->relid = InvalidOid;
+			rte->relkind = 0;
+			rte->rellockmode = 0;
+			rte->tablesample = NULL;
+			rte->inh = false;			/* must not be set for a subquery */
+
+			rte->requiredPerms = 0;		/* no permission check on subquery itself */
+			rte->checkAsUser = InvalidOid;
+			rte->selectedCols = NULL;
+			rte->insertedCols = NULL;
+			rte->updatedCols = NULL;
+			rte->extraUpdatedCols = NULL;
+		}
+
+		return parsetree;
+	}
+
 	if (rt_index == parsetree->resultRelation)
 	{
 		/*
@@ -1906,7 +1952,8 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
 		 * In that case this test would need to be postponed till after we've
 		 * opened the rel, so that we could check its state.
 		 */
-		if (rte->relkind == RELKIND_MATVIEW)
+		if (rte->relkind == RELKIND_MATVIEW &&
+			(!rte->relisivm || MatViewIncrementalMaintenanceIsEnabled() || parsetree->commandType != CMD_SELECT))
 			continue;
 
 		/*
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index c13c08a..296cc53 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1845,6 +1845,30 @@ get_rel_relispartition(Oid relid)
 }
 
 /*
+ * get_rel_relisivm
+ *
+ *		Returns the relisivm flag associated with a given relation.
+ */
+bool
+get_rel_relisivm(Oid relid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp);
+		bool		result;
+
+		result = reltup->relisivm;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return false;
+}
+
+/*
  * get_rel_tablespace
  *
  *		Returns the pg_tablespace OID associated with a given relation.
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2b992d7..d80bb30 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1861,6 +1861,8 @@ formrdesc(const char *relationName, Oid relationReltype,
 
 	/* ... and they're always populated, too */
 	relation->rd_rel->relispopulated = true;
+	/* ... and they're always no ivm, too */
+	relation->rd_rel->relisivm = false;
 
 	relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
 	relation->rd_rel->relpages = 0;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 8b4cd53..926ef24 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1491,6 +1491,7 @@ describeOneTableDetails(const char *schemaname,
 		char		relpersistence;
 		char		relreplident;
 		char	   *relam;
+		bool		isivm;
 	}			tableinfo;
 	bool		show_column_details = false;
 
@@ -1511,6 +1512,7 @@ describeOneTableDetails(const char *schemaname,
 						  "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
 						  "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
 						  "c.relpersistence, c.relreplident, am.amname\n"
+						  ",c.relisivm\n"
 						  "FROM pg_catalog.pg_class c\n "
 						  "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
 						  "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
@@ -1687,6 +1689,11 @@ describeOneTableDetails(const char *schemaname,
 			(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
 	else
 		tableinfo.relam = NULL;
+	/* TODO: This will supported when sversion >= 130000 (or later). */
+	if (pset.sversion >= 120000)
+		tableinfo.isivm = strcmp(PQgetvalue(res, 0, 15), "t") == 0;
+	else
+		tableinfo.isivm = false;
 	PQclear(res);
 	res = NULL;
 
@@ -3260,6 +3267,12 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
 			printTableAddFooter(&cont, buf.data);
 		}
+
+		/* Incremental view maintance info */
+		if (verbose && tableinfo.relkind == RELKIND_MATVIEW && tableinfo.isivm)
+		{
+			printTableAddFooter(&cont, _("Incremental view maintenance: yes"));
+		}
 	}
 
 	/* reloptions, if verbose */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 9009b05..7e21dca 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1001,6 +1001,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"INCREMENTAL MATERIALIZED VIEW", NULL, NULL, &Query_for_list_of_matviews},
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2486,7 +2487,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
-		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
+		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW", "INCREMENTAL MATERIALIZED VIEW");
 	/* Complete PARTITION BY with RANGE ( or LIST ( or ... */
 	else if (TailMatches("PARTITION", "BY"))
 		COMPLETE_WITH("RANGE (", "LIST (", "HASH (");
@@ -2695,13 +2696,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches("CREATE", "MATERIALIZED"))
+	else if (Matches("CREATE", "MATERIALIZED") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED"))
 		COMPLETE_WITH("VIEW");
-	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	/* Complete CREATE MATERIALIZED VIEW <name> with AS  */
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny) ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH("SELECT");
 
 /* CREATE EVENT TRIGGER */
diff --git a/src/include/catalog/pg_class.dat b/src/include/catalog/pg_class.dat
index 9bcf286..7deda40 100644
--- a/src/include/catalog/pg_class.dat
+++ b/src/include/catalog/pg_class.dat
@@ -27,7 +27,7 @@
   relpersistence => 'p', relkind => 'r', relnatts => '31', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 { oid => '1249',
@@ -37,7 +37,7 @@
   relpersistence => 'p', relkind => 'r', relnatts => '25', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 { oid => '1255',
@@ -47,17 +47,17 @@
   relpersistence => 'p', relkind => 'r', relnatts => '29', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 { oid => '1259',
   relname => 'pg_class', reltype => 'pg_class', relam => 'heap',
   relfilenode => '0', relpages => '0', reltuples => '0', relallvisible => '0',
   reltoastrelid => '0', relhasindex => 'f', relisshared => 'f',
-  relpersistence => 'p', relkind => 'r', relnatts => '33', relchecks => '0',
+  relpersistence => 'p', relkind => 'r', relnatts => '34', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 090b6ba..ff535f5 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
 	/* is relation a partition? */
 	bool		relispartition;
 
+	/* is relation a matview with ivm? */
+	bool		relisivm;
+
 	/* heap for rewrite during DDL, link to original rel */
 	Oid			relrewrite BKI_DEFAULT(0);
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 604470c..141966c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10669,4 +10669,9 @@
   proname => 'pg_partition_root', prorettype => 'regclass',
   proargtypes => 'regclass', prosrc => 'pg_partition_root' },
 
+# IVM
+{ oid => '784', descr => 'ivm trigger',
+  proname => 'IVM_immediate_maintenance', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_maintenance' },
+
 ]
diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h
index edf04bf..8dd8193 100644
--- a/src/include/commands/matview.h
+++ b/src/include/commands/matview.h
@@ -23,6 +23,8 @@
 
 extern void SetMatViewPopulatedState(Relation relation, bool newstate);
 
+extern void SetMatViewIVMState(Relation relation, bool newstate);
+
 extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										ParamListInfo params, char *completionTag);
 
@@ -30,4 +32,6 @@ extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
 
 extern bool MatViewIncrementalMaintenanceIsEnabled(void);
 
+extern Datum IVM_immediate_maintenance(PG_FUNCTION_ARGS);
+
 #endif							/* MATVIEW_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 94ded3c..5c8a5ae 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1002,6 +1002,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		relisivm;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -2059,6 +2060,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		ivm;			/* incremental view maintenance is used by materialized view */
 } CreateStmt;
 
 /* ----------
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 860a84d..6df58e2 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -117,6 +117,7 @@ typedef struct IntoClause
 	char	   *tableSpaceName; /* table space to use, or NULL */
 	Node	   *viewQuery;		/* materialized view's SELECT query */
 	bool		skipData;		/* true for WITH NO DATA */
+	bool		ivm;			/* true for WITH IVM */
 } IntoClause;
 
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 00ace84..d682ee1 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -198,6 +198,7 @@ PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
 PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
+PG_KEYWORD("incremental", INCREMENTAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
 PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index c8df5bf..8fd9193 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -129,6 +129,7 @@ extern Oid	get_rel_namespace(Oid relid);
 extern Oid	get_rel_type_id(Oid relid);
 extern char get_rel_relkind(Oid relid);
 extern bool get_rel_relispartition(Oid relid);
+extern bool get_rel_relisivm(Oid relid);
 extern Oid	get_rel_tablespace(Oid relid);
 extern char get_rel_persistence(Oid relid);
 extern Oid	get_transform_fromsql(Oid typid, Oid langid, List *trftypes);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d35b4a5..5e5856b 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -562,6 +562,8 @@ typedef struct ViewOptions
  */
 #define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated)
 
+#define RelationIsIVM(relation) ((relation)->rd_rel->relisivm)
+
 /*
  * RelationIsAccessibleInLogicalDecoding
  *		True if we need to log enough information to have access via
diff --git a/src/test/regress/expected/incremental_matview.out b/src/test/regress/expected/incremental_matview.out
new file mode 100644
index 0000000..c4c1e8f
--- /dev/null
+++ b/src/test/regress/expected/incremental_matview.out
@@ -0,0 +1,254 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- immediaite maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- result of materliazied view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 20
+ 30
+ 40
+ 50
+(6 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+ROLLBACK;
+-- support SUM(), COUNT() and AVG() aggregation function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i),AVG(j)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 | 120 |     2 | 60.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg          
+---+-----+-------+----------------------
+ 1 |  10 |     1 |  10.0000000000000000
+ 2 | 220 |     2 | 110.0000000000000000
+ 3 |  30 |     1 |  30.0000000000000000
+ 4 |  40 |     1 |  40.0000000000000000
+ 5 |  50 |     1 |  50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support COUNT(*) aggregation function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j),COUNT(*)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 |  20 |     1
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 | 120 |     2
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+ROLLBACK;
+-- support having only aggregation function without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j)FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum 
+-----
+ 150
+(1 row)
+
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum 
+-----
+ 170
+(1 row)
+
+ROLLBACK;
+-- unsupport aggregation function except for SUM(),COUNT(),AVG()
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min AS SELECT i, MIN(j)  FROM mv_base_a GROUP BY i;
+ERROR:  aggregate function min is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_max AS SELECT i, MAX(j)  FROM mv_base_a GROUP BY i;
+ERROR:  aggregate function max is not supported
+-- known issues: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |          avg           
+---+-----+-------+------------------------
+ 1 |  10 |     3 | 3.33333333333333333333
+ 2 |  80 |     3 |    26.6666666666666667
+ 3 |  30 |     1 |    30.0000000000000000
+ 4 |  40 |     1 |    40.0000000000000000
+ 5 |  50 |     1 |    50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |          avg           
+---+-----+-------+------------------------
+ 1 |  10 |     1 | 9.99999999999999999999
+ 2 |  20 |     1 |    20.0000000000000001
+ 3 |  30 |     1 |    30.0000000000000000
+ 4 |  40 |     1 |    40.0000000000000000
+ 5 |  50 |     1 |    50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+DROP TABLE mv_base_b CASCADE;
+NOTICE:  drop cascades to materialized view mv_ivm_1
+DROP TABLE mv_base_a CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8fb55f0..39e79d8 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -78,7 +78,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tidscan
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tidscan incremental_matview
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index a39ca10..552c80c8 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -112,6 +112,7 @@ test: init_privs
 test: security_label
 test: collate
 test: matview
+test: incremental_matview
 test: lock
 test: replica_identity
 test: rowsecurity
diff --git a/src/test/regress/sql/incremental_matview.sql b/src/test/regress/sql/incremental_matview.sql
new file mode 100644
index 0000000..a05f607
--- /dev/null
+++ b/src/test/regress/sql/incremental_matview.sql
@@ -0,0 +1,91 @@
+
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i);
+
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+-- immediaite maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+
+-- result of materliazied view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ROLLBACK;
+
+-- support SUM(), COUNT() and AVG() aggregation function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i),AVG(j)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ROLLBACK;
+
+-- support COUNT(*) aggregation function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j),COUNT(*)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support having only aggregation function without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j)FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ROLLBACK;
+
+-- unsupport aggregation function except for SUM(),COUNT(),AVG()
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min AS SELECT i, MIN(j)  FROM mv_base_a GROUP BY i;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_max AS SELECT i, MAX(j)  FROM mv_base_a GROUP BY i;
+
+-- known issues: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ROLLBACK;
+
+DROP TABLE mv_base_b CASCADE;
+DROP TABLE mv_base_a CASCADE;
#23Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Thomas Munro (#19)
Re: Implementing Incremental View Maintenance

I am quite interested to learn how IVM interacts with SERIALIZABLE.

Just for a fun, I have added:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

right after every BEGIN; in incremental_matview.sql in regression test
and it seems it works.

A couple of superficial review comments:

+            const char *aggname = get_func_name(aggref->aggfnoid);
...
+            else if (!strcmp(aggname, "sum"))

I guess you need a more robust way to detect the supported aggregates
than their name, or I guess some way for aggregates themselves to
specify that they support this and somehow supply the extra logic.
Perhaps I just waid what Greg Stark already said, except not as well.

I guess we could use moving aggregate (or partial aggregate?)
functions for this purpose, but then we need to run executor directly
rather using SPI. It needs more codes...

+                            "WITH t AS ("
+                            "  SELECT diff.__ivm_count__,
(diff.__ivm_count__ = mv.__ivm_count__) AS for_dlt, mv.ctid"
+                            ", %s"
+                            "  FROM %s AS mv, %s AS diff WHERE (%s) = (%s)"
+                            "), updt AS ("
+                            "  UPDATE %s AS mv SET __ivm_count__ =
mv.__ivm_count__ - t.__ivm_count__"
+                            ", %s "
+                            "  FROM t WHERE mv.ctid = t.ctid AND NOT for_dlt"
+                            ") DELETE FROM %s AS mv USING t WHERE
mv.ctid = t.ctid AND for_dlt;",

I fully understand that this is POC code, but I am curious about one
thing. These queries that are executed by apply_delta() would need to
be converted to C, or at least used reusable plans, right? Hmm,
creating and dropping temporary tables every time is a clue that the
ultimate form of this should be tuplestores and C code, I think,
right?

Yes, we could reuse the temp tables and plans.

Then our unofficial automatic CI system[1] will run these tests every
day, which sometimes finds problems.

[1] cfbot.cputube.org

I appreciate that you provide the system.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#24Yugo Nagata
nagata@sraoss.co.jp
In reply to: Takuma Hoshiai (#22)
Re: Implementing Incremental View Maintenance

Hi Thomas,

Thank you for your review and discussion on this patch!

2019年7月8日(月) 15:32 Thomas Munro <thomas.munro@gmail.com>:

On Fri, Jun 28, 2019 at 10:56 PM Yugo Nagata <nagata@sraoss.co.jp> wrote:

Attached is a WIP patch of IVM which supports some aggregate functions.

Hi Nagata-san and Hoshiai-san,

Thank you for working on this. I enjoyed your talk at PGCon. I've
added Kevin Grittner just in case he missed this thread; he has talked
often about implementing the counting algorithm, and he wrote the
"trigger transition tables" feature to support exactly this. While
integrating trigger transition tables with the new partition features,
we had to make a number of decisions about how that should work, and
we tried to come up with answers that would work for IMV, and I hope
we made the right choices!

Transition tables is a great feature. I am now using this in my implementation
of IVM, but the first time I used this feature was when I implemented a PoC
for extending view updatability of PostgreSQL[1]https://www.pgcon.org/2017/schedule/events/1074.en.html. At that time, I didn't know
that this feature is made originally aiming to support IVM.

[1]: https://www.pgcon.org/2017/schedule/events/1074.en.html

I think transition tables is a good choice to implement a statement level
immediate view maintenance where materialized views are refreshed in a statement
level after trigger. However, when implementing a transaction level immediate
view maintenance where views are refreshed per transaction, or deferred view
maintenance, we can't update views in a after trigger, and we will need an
infrastructure to manage change logs of base tables. Transition tables can be
used to collect these logs, but using logical decoding of WAL is another candidate.
In any way, if these logs can be collected in a tuplestore, we might able to
convert this to "ephemeral named relation (ENR)" and use this to calculate diff
sets for views.

I am quite interested to learn how IVM interacts with SERIALIZABLE.

Nagata-san has been studying this. Nagata-san, any comment?

In SERIALIZABLE or REPEATABLE READ level, table changes occurred in other
ransactions are not visible, so views can not be maintained correctly in AFTER
triggers. If a view is defined on two tables and each table is modified in
different concurrent transactions respectively, the result of view maintenance
done in trigger functions can be incorrect due to the race condition. This is the
reason why such transactions are aborted immediately in that case in my current
implementation.

One idea to resolve this is performing view maintenance in two phases. Firstly,
views are updated using only table changes visible in this transaction. Then,
just after this transaction is committed, views have to be updated additionally
using changes happened in other transactions to keep consistency. This is a just
idea, but to implement this idea, I think, we will need keep to keep and
maintain change logs.

A couple of superficial review comments:

+            const char *aggname = get_func_name(aggref->aggfnoid);
...
+            else if (!strcmp(aggname, "sum"))

I guess you need a more robust way to detect the supported aggregates
than their name, or I guess some way for aggregates themselves to
specify that they support this and somehow supply the extra logic.
Perhaps I just waid what Greg Stark already said, except not as well.

Yes. Using name is not robust because users can make same name aggregates like
sum(text) (although I am not sure this makes sense). We can use oids instead
of names, but it would be nice to extend pg_aggregate and add new attributes
for informing that this supports IVM and for providing functions for IVM logic.

As for the question of how
to reserve a namespace for system columns that won't clash with user
columns, according to our manual the SQL standard doesn't allow $ in
identifier names, and according to my copy SQL92 "intermediate SQL"
doesn't allow identifiers that end in an underscore. I don't know
what the best answer is but we should probably decide on a something
based the standard.

Ok, so we should use "__ivm_count__" since this ends in "_" at least.

Another idea is that users specify the name of this special column when
defining materialized views with IVM support. This way can avoid the conflict
because users will specify a name which does not appear in the target list.

As for aggregates supports, it may be also possible to make it a restriction
that count(expr) must be in the target list explicitly when sum(expr) or
avg(expr) is included, instead of making hidden column like __ivm_count_sum__,
like Oracle does.

As for how to make internal columns invisible to SELECT *, previously
there have been discussions about doing that using a new flag in
pg_attribute:

/messages/by-id/CAEepm=3ZHh=p0nEEnVbs1Dig_UShPzHUcMNAqvDQUgYgcDo-pA@mail.gmail.com

I agree implementing this feature in PostgreSQL since there are at least a few
use cases, IVM and temporal database.

+                            "WITH t AS ("
+                            "  SELECT diff.__ivm_count__,
(diff.__ivm_count__ = mv.__ivm_count__) AS for_dlt, mv.ctid"
+                            ", %s"
+                            "  FROM %s AS mv, %s AS diff WHERE (%s) =
(%s)"
+                            "), updt AS ("
+                            "  UPDATE %s AS mv SET __ivm_count__ =
mv.__ivm_count__ - t.__ivm_count__"
+                            ", %s "
+                            "  FROM t WHERE mv.ctid = t.ctid AND NOT
for_dlt"
+                            ") DELETE FROM %s AS mv USING t WHERE
mv.ctid = t.ctid AND for_dlt;",

I fully understand that this is POC code, but I am curious about one
thing. These queries that are executed by apply_delta() would need to
be converted to C, or at least used reusable plans, right? Hmm,
creating and dropping temporary tables every time is a clue that the
ultimate form of this should be tuplestores and C code, I think,
right?

I used SPI just because REFRESH CONCURRENTLY uses this, but, as you said,
it is inefficient to create/drop temp tables and perform parse/plan every times.
It seems to be enough to perform this once when creating materialized views or
at the first maintenance time.

Best regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

#25Yugo Nagata
nagata@sraoss.co.jp
In reply to: Yugo Nagata (#24)
Re: Implementing Incremental View Maintenance

Hi,

I've updated the wiki page of Incremental View Maintenance.

https://wiki.postgresql.org/wiki/Incremental_View_Maintenance

On Thu, 11 Jul 2019 13:28:04 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

Hi Thomas,

Thank you for your review and discussion on this patch!

2019年7月8日(月) 15:32 Thomas Munro <thomas.munro@gmail.com>:

On Fri, Jun 28, 2019 at 10:56 PM Yugo Nagata <nagata@sraoss.co.jp> wrote:

Attached is a WIP patch of IVM which supports some aggregate functions.

Hi Nagata-san and Hoshiai-san,

Thank you for working on this. I enjoyed your talk at PGCon. I've
added Kevin Grittner just in case he missed this thread; he has talked
often about implementing the counting algorithm, and he wrote the
"trigger transition tables" feature to support exactly this. While
integrating trigger transition tables with the new partition features,
we had to make a number of decisions about how that should work, and
we tried to come up with answers that would work for IMV, and I hope
we made the right choices!

Transition tables is a great feature. I am now using this in my implementation
of IVM, but the first time I used this feature was when I implemented a PoC
for extending view updatability of PostgreSQL[1]. At that time, I didn't know
that this feature is made originally aiming to support IVM.

[1] https://www.pgcon.org/2017/schedule/events/1074.en.html

I think transition tables is a good choice to implement a statement level
immediate view maintenance where materialized views are refreshed in a statement
level after trigger. However, when implementing a transaction level immediate
view maintenance where views are refreshed per transaction, or deferred view
maintenance, we can't update views in a after trigger, and we will need an
infrastructure to manage change logs of base tables. Transition tables can be
used to collect these logs, but using logical decoding of WAL is another candidate.
In any way, if these logs can be collected in a tuplestore, we might able to
convert this to "ephemeral named relation (ENR)" and use this to calculate diff
sets for views.

I am quite interested to learn how IVM interacts with SERIALIZABLE.

Nagata-san has been studying this. Nagata-san, any comment?

In SERIALIZABLE or REPEATABLE READ level, table changes occurred in other
ransactions are not visible, so views can not be maintained correctly in AFTER
triggers. If a view is defined on two tables and each table is modified in
different concurrent transactions respectively, the result of view maintenance
done in trigger functions can be incorrect due to the race condition. This is the
reason why such transactions are aborted immediately in that case in my current
implementation.

One idea to resolve this is performing view maintenance in two phases. Firstly,
views are updated using only table changes visible in this transaction. Then,
just after this transaction is committed, views have to be updated additionally
using changes happened in other transactions to keep consistency. This is a just
idea, but to implement this idea, I think, we will need keep to keep and
maintain change logs.

A couple of superficial review comments:

+            const char *aggname = get_func_name(aggref->aggfnoid);
...
+            else if (!strcmp(aggname, "sum"))

I guess you need a more robust way to detect the supported aggregates
than their name, or I guess some way for aggregates themselves to
specify that they support this and somehow supply the extra logic.
Perhaps I just waid what Greg Stark already said, except not as well.

Yes. Using name is not robust because users can make same name aggregates like
sum(text) (although I am not sure this makes sense). We can use oids instead
of names, but it would be nice to extend pg_aggregate and add new attributes
for informing that this supports IVM and for providing functions for IVM logic.

As for the question of how
to reserve a namespace for system columns that won't clash with user
columns, according to our manual the SQL standard doesn't allow $ in
identifier names, and according to my copy SQL92 "intermediate SQL"
doesn't allow identifiers that end in an underscore. I don't know
what the best answer is but we should probably decide on a something
based the standard.

Ok, so we should use "__ivm_count__" since this ends in "_" at least.

Another idea is that users specify the name of this special column when
defining materialized views with IVM support. This way can avoid the conflict
because users will specify a name which does not appear in the target list.

As for aggregates supports, it may be also possible to make it a restriction
that count(expr) must be in the target list explicitly when sum(expr) or
avg(expr) is included, instead of making hidden column like __ivm_count_sum__,
like Oracle does.

As for how to make internal columns invisible to SELECT *, previously
there have been discussions about doing that using a new flag in
pg_attribute:

/messages/by-id/CAEepm=3ZHh=p0nEEnVbs1Dig_UShPzHUcMNAqvDQUgYgcDo-pA@mail.gmail.com

I agree implementing this feature in PostgreSQL since there are at least a few
use cases, IVM and temporal database.

+                            "WITH t AS ("
+                            "  SELECT diff.__ivm_count__,
(diff.__ivm_count__ = mv.__ivm_count__) AS for_dlt, mv.ctid"
+                            ", %s"
+                            "  FROM %s AS mv, %s AS diff WHERE (%s) =
(%s)"
+                            "), updt AS ("
+                            "  UPDATE %s AS mv SET __ivm_count__ =
mv.__ivm_count__ - t.__ivm_count__"
+                            ", %s "
+                            "  FROM t WHERE mv.ctid = t.ctid AND NOT
for_dlt"
+                            ") DELETE FROM %s AS mv USING t WHERE
mv.ctid = t.ctid AND for_dlt;",

I fully understand that this is POC code, but I am curious about one
thing. These queries that are executed by apply_delta() would need to
be converted to C, or at least used reusable plans, right? Hmm,
creating and dropping temporary tables every time is a clue that the
ultimate form of this should be tuplestores and C code, I think,
right?

I used SPI just because REFRESH CONCURRENTLY uses this, but, as you said,
it is inefficient to create/drop temp tables and perform parse/plan every times.
It seems to be enough to perform this once when creating materialized views or
at the first maintenance time.

Best regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo Nagata <nagata@sraoss.co.jp>

#26Yugo Nagata
nagata@sraoss.co.jp
In reply to: Yugo Nagata (#25)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

Attached is the latest patch for supporting min and max aggregate functions.

When new tuples are inserted into base tables, if new values are small
(for min) or large (for max), matview just have to be updated with these
new values. Otherwise, old values just remains.

However, in the case of deletion, this is more complicated. If deleted
values exists in matview as current min or max, we have to recomputate
new min or max values from base tables for affected groups, and matview
should be updated with these recomputated values.

Also, regression tests for min/max are also added.

In addition, incremental update algorithm of avg aggregate values is a bit
improved. If an avg result in materialized views is updated incrementally
y using the old avg value, numerical errors in avg values are accumulated
and the values get wrong eventually. To prevent this, both of sum and count
values are contained in views as hidden columns and use them to calculate
new avg value instead of using old avg values.

Regards,

On Fri, 26 Jul 2019 11:35:53 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

Hi,

I've updated the wiki page of Incremental View Maintenance.

https://wiki.postgresql.org/wiki/Incremental_View_Maintenance

On Thu, 11 Jul 2019 13:28:04 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

Hi Thomas,

Thank you for your review and discussion on this patch!

2019年7月8日(月) 15:32 Thomas Munro <thomas.munro@gmail.com>:

On Fri, Jun 28, 2019 at 10:56 PM Yugo Nagata <nagata@sraoss.co.jp> wrote:

Attached is a WIP patch of IVM which supports some aggregate functions.

Hi Nagata-san and Hoshiai-san,

Thank you for working on this. I enjoyed your talk at PGCon. I've
added Kevin Grittner just in case he missed this thread; he has talked
often about implementing the counting algorithm, and he wrote the
"trigger transition tables" feature to support exactly this. While
integrating trigger transition tables with the new partition features,
we had to make a number of decisions about how that should work, and
we tried to come up with answers that would work for IMV, and I hope
we made the right choices!

Transition tables is a great feature. I am now using this in my implementation
of IVM, but the first time I used this feature was when I implemented a PoC
for extending view updatability of PostgreSQL[1]. At that time, I didn't know
that this feature is made originally aiming to support IVM.

[1] https://www.pgcon.org/2017/schedule/events/1074.en.html

I think transition tables is a good choice to implement a statement level
immediate view maintenance where materialized views are refreshed in a statement
level after trigger. However, when implementing a transaction level immediate
view maintenance where views are refreshed per transaction, or deferred view
maintenance, we can't update views in a after trigger, and we will need an
infrastructure to manage change logs of base tables. Transition tables can be
used to collect these logs, but using logical decoding of WAL is another candidate.
In any way, if these logs can be collected in a tuplestore, we might able to
convert this to "ephemeral named relation (ENR)" and use this to calculate diff
sets for views.

I am quite interested to learn how IVM interacts with SERIALIZABLE.

Nagata-san has been studying this. Nagata-san, any comment?

In SERIALIZABLE or REPEATABLE READ level, table changes occurred in other
ransactions are not visible, so views can not be maintained correctly in AFTER
triggers. If a view is defined on two tables and each table is modified in
different concurrent transactions respectively, the result of view maintenance
done in trigger functions can be incorrect due to the race condition. This is the
reason why such transactions are aborted immediately in that case in my current
implementation.

One idea to resolve this is performing view maintenance in two phases. Firstly,
views are updated using only table changes visible in this transaction. Then,
just after this transaction is committed, views have to be updated additionally
using changes happened in other transactions to keep consistency. This is a just
idea, but to implement this idea, I think, we will need keep to keep and
maintain change logs.

A couple of superficial review comments:

+            const char *aggname = get_func_name(aggref->aggfnoid);
...
+            else if (!strcmp(aggname, "sum"))

I guess you need a more robust way to detect the supported aggregates
than their name, or I guess some way for aggregates themselves to
specify that they support this and somehow supply the extra logic.
Perhaps I just waid what Greg Stark already said, except not as well.

Yes. Using name is not robust because users can make same name aggregates like
sum(text) (although I am not sure this makes sense). We can use oids instead
of names, but it would be nice to extend pg_aggregate and add new attributes
for informing that this supports IVM and for providing functions for IVM logic.

As for the question of how
to reserve a namespace for system columns that won't clash with user
columns, according to our manual the SQL standard doesn't allow $ in
identifier names, and according to my copy SQL92 "intermediate SQL"
doesn't allow identifiers that end in an underscore. I don't know
what the best answer is but we should probably decide on a something
based the standard.

Ok, so we should use "__ivm_count__" since this ends in "_" at least.

Another idea is that users specify the name of this special column when
defining materialized views with IVM support. This way can avoid the conflict
because users will specify a name which does not appear in the target list.

As for aggregates supports, it may be also possible to make it a restriction
that count(expr) must be in the target list explicitly when sum(expr) or
avg(expr) is included, instead of making hidden column like __ivm_count_sum__,
like Oracle does.

As for how to make internal columns invisible to SELECT *, previously
there have been discussions about doing that using a new flag in
pg_attribute:

/messages/by-id/CAEepm=3ZHh=p0nEEnVbs1Dig_UShPzHUcMNAqvDQUgYgcDo-pA@mail.gmail.com

I agree implementing this feature in PostgreSQL since there are at least a few
use cases, IVM and temporal database.

+                            "WITH t AS ("
+                            "  SELECT diff.__ivm_count__,
(diff.__ivm_count__ = mv.__ivm_count__) AS for_dlt, mv.ctid"
+                            ", %s"
+                            "  FROM %s AS mv, %s AS diff WHERE (%s) =
(%s)"
+                            "), updt AS ("
+                            "  UPDATE %s AS mv SET __ivm_count__ =
mv.__ivm_count__ - t.__ivm_count__"
+                            ", %s "
+                            "  FROM t WHERE mv.ctid = t.ctid AND NOT
for_dlt"
+                            ") DELETE FROM %s AS mv USING t WHERE
mv.ctid = t.ctid AND for_dlt;",

I fully understand that this is POC code, but I am curious about one
thing. These queries that are executed by apply_delta() would need to
be converted to C, or at least used reusable plans, right? Hmm,
creating and dropping temporary tables every time is a clue that the
ultimate form of this should be tuplestores and C code, I think,
right?

I used SPI just because REFRESH CONCURRENTLY uses this, but, as you said,
it is inefficient to create/drop temp tables and perform parse/plan every times.
It seems to be enough to perform this once when creating materialized views or
at the first maintenance time.

Best regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

WIP_immediate_IVM_v5.patchtext/x-diff; name=WIP_immediate_IVM_v5.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 68ad5071ca..cd1fff8409 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1952,6 +1952,13 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       <entry>True if table or index is a partition</entry>
      </row>
 
+     <row>
+      <entry><structfield>relisivm</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if materialized view enables incremental view maintenance</entry>
+     </row>
+
      <row>
       <entry><structfield>relrewrite</structfield></entry>
       <entry><type>oid</type></entry>
diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml
index ec8847ed40..a23366a342 100644
--- a/doc/src/sgml/ref/create_materialized_view.sgml
+++ b/doc/src/sgml/ref/create_materialized_view.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
+CREATE [ INCREMENTAL ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
     [ (<replaceable>column_name</replaceable> [, ...] ) ]
     [ USING <replaceable class="parameter">method</replaceable> ]
     [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
@@ -54,6 +54,16 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
   <title>Parameters</title>
 
   <variablelist>
+   <varlistentry>
+    <term><literal>INCREMENTAL</literal></term>
+    <listitem>
+     <para>
+      If specified, a materialized view enables incremental view maintenance.
+      You can replace only the contents of a materialized view, which based rows are changed.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>IF NOT EXISTS</literal></term>
     <listitem>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index b7bcdd9d0f..cb7e8be84a 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -890,6 +890,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
+	values[Anum_pg_class_relisivm - 1] = BoolGetDatum(rd_rel->relisivm);
 	if (relacl != (Datum) 0)
 		values[Anum_pg_class_relacl - 1] = relacl;
 	else
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 99ae159f98..c3cbe32ac7 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -910,6 +910,7 @@ index_create(Relation heapRelation,
 	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
 	indexRelation->rd_rel->relam = accessMethodObjectId;
 	indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid);
+	indexRelation->rd_rel->relisivm = false;
 
 	/*
 	 * store index's pg_class entry
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index b7d220699f..1ff5d03d59 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -51,6 +51,17 @@
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
 
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
+#include "commands/trigger.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
+#include "parser/parse_func.h"
+#include "nodes/print.h"
+#include "nodes/primnodes.h"
+#include "optimizer/optimizer.h"
+#include "commands/defrem.h"
+
 
 typedef struct
 {
@@ -74,6 +85,9 @@ static bool intorel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void intorel_shutdown(DestReceiver *self);
 static void intorel_destroy(DestReceiver *self);
 
+static void CreateIvmTrigger(Oid relOid, Oid viewOid, char *matviewname, int16 type);
+static void CreateIvmTriggersOnBaseTables(Query *qry, Node *jtnode, Oid matviewOid, char* matviewname);
+static void check_ivm_restriction_walker(Node *node);
 
 /*
  * create_ctas_internal
@@ -109,6 +123,8 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	create->oncommit = into->onCommit;
 	create->tablespacename = into->tableSpaceName;
 	create->if_not_exists = false;
+	/* Using Materialized view only */
+	create->ivm = into->ivm;
 	create->accessMethod = into->accessMethod;
 
 	/*
@@ -239,6 +255,7 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 	List	   *rewritten;
 	PlannedStmt *plan;
 	QueryDesc  *queryDesc;
+	Query	   *copied_query;
 
 	if (stmt->if_not_exists)
 	{
@@ -256,6 +273,9 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		}
 	}
 
+	if (is_matview && into->ivm)
+		check_ivm_restriction_walker((Node *) query);
+
 	/*
 	 * Create the tuple receiver object and insert info it will need
 	 */
@@ -319,7 +339,130 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		 * and is executed repeatedly.  (See also the same hack in EXPLAIN and
 		 * PREPARE.)
 		 */
-		rewritten = QueryRewrite(copyObject(query));
+
+		copied_query = copyObject(query);
+		if (is_matview && into->ivm)
+		{
+			TargetEntry *tle;
+			Node *node;
+			ParseState *pstate = make_parsestate(NULL);
+			FuncCall *fn;
+
+			pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+			/* group keys must be in targetlist */
+			if (copied_query->groupClause)
+			{
+				ListCell *lc;
+				foreach(lc, copied_query->groupClause)
+				{
+					SortGroupClause *scl = (SortGroupClause *) lfirst(lc);
+					TargetEntry *tle = get_sortgroupclause_tle(scl, copied_query->targetList);
+
+					if (tle->resjunk)
+						elog(ERROR, "GROUP BY expression must appear in select list for incremental materialized views");
+				}
+			}
+			else if (!copied_query->hasAggs)
+				copied_query->groupClause = transformDistinctClause(NULL, &copied_query->targetList, copied_query->sortClause, false);
+
+			if (copied_query->hasAggs)
+			{
+				ListCell *lc;
+				List *agg_counts = NIL;
+				AttrNumber next_resno = list_length(copied_query->targetList) + 1;
+				Const	*dmy_arg = makeConst(INT4OID,
+											 -1,
+											 InvalidOid,
+											 sizeof(int32),
+											 Int32GetDatum(1),
+											 false,
+											 true); /* pass by value */
+
+				foreach(lc, copied_query->targetList)
+				{
+					TargetEntry *tle = (TargetEntry *) lfirst(lc);
+					TargetEntry *tle_count;
+					char *resname = (into->colNames == NIL ? tle->resname : strVal(list_nth(into->colNames, tle->resno-1)));
+
+
+					if (IsA(tle->expr, Aggref))
+					{
+						Aggref *aggref = (Aggref *) tle->expr;
+						const char *aggname = get_func_name(aggref->aggfnoid);
+
+						/* XXX: need some generalization
+						 *
+						 * Specifically, Using func names is not robust.  We can use oids instead
+						 * of names, but it would be nice to add some information to pg_aggregate.
+						 */
+						if (strcmp(aggname, "sum") !=0
+							&& strcmp(aggname, "count") != 0
+							&& strcmp(aggname, "avg") != 0
+							&& strcmp(aggname, "min") != 0
+							&& strcmp(aggname, "max") != 0
+						)
+							elog(ERROR, "aggregate function %s is not supported", aggname);
+
+						/*
+						 * For aggregate functions except to count, add count func with the same arg parameters.
+						 * Also, add sum func for agv.
+						 *
+						 * XXX: If there are same expressions explicitly in the target list, we can use this instead
+						 * of adding new duplicated one.
+						 */
+						if (strcmp(aggname, "count") != 0)
+						{
+							fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+
+							/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+							node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+							((Aggref *)node)->args = aggref->args;
+
+							tle_count = makeTargetEntry((Expr *) node,
+														next_resno,
+														pstrdup(makeObjectName("__ivm_count",resname, "_")),
+														false);
+							agg_counts = lappend(agg_counts, tle_count);
+							next_resno++;
+						}
+						if (strcmp(aggname, "avg") == 0)
+						{
+							fn = makeFuncCall(list_make1(makeString("sum")), NIL, -1);
+
+							/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+							node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+							((Aggref *)node)->args = aggref->args;
+
+							tle_count = makeTargetEntry((Expr *) node,
+														next_resno,
+														pstrdup(makeObjectName("__ivm_sum",resname, "_")),
+														false);
+							agg_counts = lappend(agg_counts, tle_count);
+							next_resno++;
+						}
+
+					}
+				}
+				copied_query->targetList = list_concat(copied_query->targetList, agg_counts);
+
+			}
+
+			/* Add count(*) for counting algorithm */
+			fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+			fn->agg_star = true;
+
+			node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+			tle = makeTargetEntry((Expr *) node,
+								  	list_length(copied_query->targetList) + 1,
+								  	pstrdup("__ivm_count__"),
+								  	false);
+			copied_query->targetList = lappend(copied_query->targetList, tle);
+			copied_query->hasAggs = true;
+		}
+
+		rewritten = QueryRewrite(copied_query);
 
 		/* SELECT should never rewrite to more or less than one SELECT query */
 		if (list_length(rewritten) != 1)
@@ -378,11 +521,65 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 
 		/* Restore userid and security context */
 		SetUserIdAndSecContext(save_userid, save_sec_context);
+
+
+		if (into->ivm)
+		{
+			char	   *matviewname;
+			Oid matviewOid = address.objectId;
+			Relation matviewRel = table_open(matviewOid, NoLock);
+			matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+													 RelationGetRelationName(matviewRel));
+			copied_query = copyObject(query);
+			AcquireRewriteLocks(copied_query, true, false);
+
+			CreateIvmTriggersOnBaseTables(copied_query, (Node *)copied_query->jointree, matviewOid, matviewname);
+
+			table_close(matviewRel, NoLock);
+		}
 	}
 
 	return address;
 }
 
+static void CreateIvmTriggersOnBaseTables(Query *qry, Node *jtnode, Oid matviewOid, char* matviewname)
+{
+
+	if (jtnode == NULL)
+		return;
+	if (IsA(jtnode, RangeTblRef))
+	{
+		int			rti = ((RangeTblRef *) jtnode)->rtindex;
+		RangeTblEntry *rte = rt_fetch(rti, qry->rtable);
+
+		if (rte->rtekind == RTE_RELATION)
+		{
+			CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_INSERT);
+			CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_DELETE);
+			CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_UPDATE);
+		}
+		else
+			elog(ERROR, "unsupported RTE kind: %d", (int) rte->rtekind);
+	}
+	else if (IsA(jtnode, FromExpr))
+	{
+		FromExpr   *f = (FromExpr *) jtnode;
+		ListCell   *l;
+
+		foreach(l, f->fromlist)
+			CreateIvmTriggersOnBaseTables(qry, lfirst(l), matviewOid, matviewname);
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+
+		CreateIvmTriggersOnBaseTables(qry, j->larg, matviewOid, matviewname);
+		CreateIvmTriggersOnBaseTables(qry, j->rarg, matviewOid, matviewname);
+	}
+	else
+		elog(ERROR, "unrecognized node type: %d", (int) nodeTag(jtnode));
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -547,6 +744,11 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	if (is_matview && !into->skipData)
 		SetMatViewPopulatedState(intoRelationDesc, true);
 
+	/*
+	 * Mark relisivm field, if it's a matview and into->ivm is true.
+	 */
+	if (is_matview && into->ivm)
+		SetMatViewIVMState(intoRelationDesc, true);
 	/*
 	 * Fill private fields of myState for use by later routines
 	 */
@@ -619,3 +821,229 @@ intorel_destroy(DestReceiver *self)
 {
 	pfree(self);
 }
+
+static void
+CreateIvmTrigger(Oid relOid, Oid viewOid, char *matviewname, int16 type)
+{
+	CreateTrigStmt *ivm_trigger;
+	List *transitionRels = NIL;
+	ObjectAddress address, refaddr;
+
+	refaddr.classId = RelationRelationId;
+	refaddr.objectId = viewOid;
+	refaddr.objectSubId = 0;
+
+
+	ivm_trigger = makeNode(CreateTrigStmt);
+	ivm_trigger->relation = NULL;
+	ivm_trigger->row = false;
+	ivm_trigger->timing = TRIGGER_TYPE_AFTER;
+
+	ivm_trigger->events = type;
+
+	switch (type)
+	{
+		case TRIGGER_TYPE_INSERT:
+			ivm_trigger->trigname = "IVM_trigger_ins";
+			break;
+		case TRIGGER_TYPE_DELETE:
+			ivm_trigger->trigname = "IVM_trigger_del";
+			break;
+		case TRIGGER_TYPE_UPDATE:
+			ivm_trigger->trigname = "IVM_trigger_upd";
+			break;
+	}
+
+	if (type == TRIGGER_TYPE_INSERT || type == TRIGGER_TYPE_UPDATE)
+	{
+		TriggerTransition *n = makeNode(TriggerTransition);
+		n->name = "ivm_newtable";
+		n->isNew = true;
+		n->isTable = true;
+
+		transitionRels = lappend(transitionRels, n);
+	}
+	if (type == TRIGGER_TYPE_DELETE || type == TRIGGER_TYPE_UPDATE)
+	{
+		TriggerTransition *n = makeNode(TriggerTransition);
+		n->name = "ivm_oldtable";
+		n->isNew = false;
+		n->isTable = true;
+
+		transitionRels = lappend(transitionRels, n);
+	}
+
+	ivm_trigger->funcname = SystemFuncName("IVM_immediate_maintenance");
+
+	ivm_trigger->columns = NIL;
+	ivm_trigger->transitionRels = transitionRels;
+	ivm_trigger->whenClause = NULL;
+	ivm_trigger->isconstraint = false;
+	ivm_trigger->deferrable = false;
+	ivm_trigger->initdeferred = false;
+	ivm_trigger->constrrel = NULL;
+	ivm_trigger->args = list_make1(makeString(matviewname));
+
+	address = CreateTrigger(ivm_trigger, NULL, relOid, InvalidOid, InvalidOid,
+						 InvalidOid, InvalidOid, InvalidOid, NULL, true, false);
+
+	recordDependencyOn(&address, &refaddr, DEPENDENCY_AUTO);
+
+	/* Make changes-so-far visible */
+	CommandCounterIncrement();
+}
+
+/*
+ * check_ivm_restriction_walker --- look for specify nodes in the query tree
+ */
+static void
+check_ivm_restriction_walker(Node *node)
+{
+	/* This can recurse, so check for excessive recursion */
+	check_stack_depth();
+
+	if (node == NULL)
+		return;
+	/*
+	 * We currently don't support Sub-Query.
+	 */
+	if (IsA(node, SubPlan) || IsA(node, SubLink))
+		ereport(ERROR, (errmsg("subquery is not supported with IVM")));
+
+	switch (nodeTag(node))
+	{
+		case T_Query:
+			{
+				Query *qry = (Query *)node;
+				ListCell   *lc;
+				/* if contained CTE, return error */
+				if (qry->cteList != NIL)
+					ereport(ERROR, (errmsg("CTE is not supported with IVM")));
+
+				/* if contained VIEW or subquery into RTE, return error */
+				foreach(lc, qry->rtable)
+				{
+					RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+					if (rte->relkind == RELKIND_VIEW ||
+							rte->relkind == RELKIND_MATVIEW)
+						ereport(ERROR, (errmsg("VIEW or MATERIALIZED VIEW is not supported with IVM")));
+					if (rte->rtekind ==  RTE_SUBQUERY)
+						ereport(ERROR, (errmsg("subquery is not supported with IVM")));
+				}
+
+				/* search in jointree */
+				check_ivm_restriction_walker((Node *) qry->jointree);
+
+				/* search in target lists */
+				foreach(lc, qry->targetList)
+				{
+					TargetEntry *tle = (TargetEntry *) lfirst(lc);
+					check_ivm_restriction_walker((Node *) tle->expr);
+				}
+
+				break;
+			}
+		case T_JoinExpr:
+			{
+				JoinExpr *joinexpr = (JoinExpr *)node;
+				if (joinexpr->jointype > JOIN_INNER)
+					ereport(ERROR, (errmsg("OUTER JOIN is not supported with IVM")));
+				/* left side */
+				check_ivm_restriction_walker((Node *) joinexpr->larg);
+				/* right side */
+				check_ivm_restriction_walker((Node *) joinexpr->rarg);
+				check_ivm_restriction_walker((Node *) joinexpr->quals);
+			}
+			break;
+		case T_FromExpr:
+			{
+				ListCell *lc;
+				FromExpr *fromexpr = (FromExpr *)node;
+				foreach(lc, fromexpr->fromlist)
+				{
+					check_ivm_restriction_walker((Node *) lfirst(lc));
+				}
+				check_ivm_restriction_walker((Node *) fromexpr->quals);
+			}
+			break;
+		case T_Var:
+			{
+				/* if system column, return error */
+				Var	*variable = (Var *) node;
+				if (variable->varattno < 0)
+					ereport(ERROR, (errmsg("system column is not supported with IVM")));
+			}
+			break;
+		case T_BoolExpr:
+			{
+				BoolExpr   *boolexpr = (BoolExpr *) node;
+				ListCell   *lc;
+
+				foreach(lc, boolexpr->args)
+				{
+					Node	   *arg = (Node *) lfirst(lc);
+					check_ivm_restriction_walker(arg);
+				}
+				break;
+			}
+		case T_NullIfExpr: /* same as OpExpr */
+		case T_DistinctExpr: /* same as OpExpr */
+		case T_OpExpr:
+			{
+				OpExpr	   *op = (OpExpr *) node;
+				ListCell   *lc;
+				foreach(lc, op->args)
+				{
+					Node	   *arg = (Node *) lfirst(lc);
+					check_ivm_restriction_walker(arg);
+				}
+				break;
+			}
+		case T_CaseExpr:
+			{
+				CaseExpr *caseexpr = (CaseExpr *) node;
+				ListCell *lc;
+				/* result for ELSE clause */
+				check_ivm_restriction_walker((Node *) caseexpr->defresult);
+				/* expr for WHEN clauses */
+				foreach(lc, caseexpr->args)
+				{
+					CaseWhen *when = (CaseWhen *) lfirst(lc);
+					Node *w_expr = (Node *) when->expr;
+					/* result for WHEN clause */
+					check_ivm_restriction_walker((Node *) when->result);
+					/* expr clause*/
+					check_ivm_restriction_walker((Node *) w_expr);
+				}
+				break;
+			}
+		case T_SubLink:
+			{
+				/* Now, not supported */
+/*
+				SubLink	*sublink = (SubLink *) node;
+				Query	*qry =(Query *) sublink->subselect;
+				if (qry != NULL && qry->jointree != NULL)
+					check_ivm_restriction_walker((Node *) qry->jointree->quals);
+*/
+				break;
+			}
+		case T_SubPlan:
+			{
+				/* Now, not supported */
+				break;
+			}
+		case T_Aggref:
+		case T_GroupingFunc:
+		case T_WindowFunc:
+		case T_FuncExpr:
+		case T_SQLValueFunction:
+		case T_Const:
+		case T_Param:
+		default:
+			/* do nothing */
+			break;
+	}
+
+	return;
+}
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 537d0e8cef..be235d28ca 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -46,6 +46,15 @@
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 
+#include "utils/regproc.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_func.h"
+#include "nodes/print.h"
+#include "catalog/pg_type_d.h"
+#include "optimizer/optimizer.h"
+#include "commands/defrem.h"
+
 
 typedef struct
 {
@@ -60,12 +69,16 @@ typedef struct
 
 static int	matview_maintenance_depth = 0;
 
+static SPIPlanPtr	plan_for_recalc_min_max = NULL;
+static SPIPlanPtr	plan_for_set_min_max = NULL;
+
 static void transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
 static bool transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void transientrel_shutdown(DestReceiver *self);
 static void transientrel_destroy(DestReceiver *self);
 static uint64 refresh_matview_datafill(DestReceiver *dest, Query *query,
-									   const char *queryString);
+						 QueryEnvironment *queryEnv,
+						 const char *queryString);
 static char *make_temptable_name_n(char *tempname, int n);
 static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 								   int save_sec_context);
@@ -74,6 +87,16 @@ static bool is_usable_unique_index(Relation indexRel);
 static void OpenMatViewIncrementalMaintenance(void);
 static void CloseMatViewIncrementalMaintenance(void);
 
+static void apply_delta(Oid matviewOid, Oid tempOid_new, Oid tempOid_old,
+			Query *query, Oid relowner, int save_sec_context);
+static SPIPlanPtr get_plan_for_recalc_min_max(Oid matviewOid, const char *min_max_list,
+						  const char *group_keys, int nkeys, Oid *keyTypes);
+static SPIPlanPtr get_plan_for_set_min_max(char *matviewname, const char *min_max_list,
+						  int num_min_max, Oid *valTypes);
+
+static Query *get_matview_query(Relation matviewRel);
+
+
 /*
  * SetMatViewPopulatedState
  *		Mark a materialized view as populated, or not.
@@ -114,6 +137,46 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
 	CommandCounterIncrement();
 }
 
+/*
+ * SetMatViewIVMState
+ *		Mark a materialized view as IVM, or not.
+ *
+ * NOTE: caller must be holding an appropriate lock on the relation.
+ */
+void
+SetMatViewIVMState(Relation relation, bool newstate)
+{
+	Relation	pgrel;
+	HeapTuple	tuple;
+
+	Assert(relation->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Update relation's pg_class entry.  Crucial side-effect: other backends
+	 * (and this one too!) are sent SI message to make them rebuild relcache
+	 * entries.
+	 */
+	pgrel = table_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(relation)));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u",
+			 RelationGetRelid(relation));
+
+	((Form_pg_class) GETSTRUCT(tuple))->relisivm = newstate;
+
+	CatalogTupleUpdate(pgrel, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	table_close(pgrel, RowExclusiveLock);
+
+	/*
+	 * Advance command counter to make the updated pg_class row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+}
+
 /*
  * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command
  *
@@ -140,8 +203,6 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 {
 	Oid			matviewOid;
 	Relation	matviewRel;
-	RewriteRule *rule;
-	List	   *actions;
 	Query	   *dataQuery;
 	Oid			tableSpace;
 	Oid			relowner;
@@ -187,32 +248,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("CONCURRENTLY and WITH NO DATA options cannot be used together")));
 
-	/*
-	 * Check that everything is correct for a refresh. Problems at this point
-	 * are internal errors, so elog is sufficient.
-	 */
-	if (matviewRel->rd_rel->relhasrules == false ||
-		matviewRel->rd_rules->numLocks < 1)
-		elog(ERROR,
-			 "materialized view \"%s\" is missing rewrite information",
-			 RelationGetRelationName(matviewRel));
-
-	if (matviewRel->rd_rules->numLocks > 1)
-		elog(ERROR,
-			 "materialized view \"%s\" has too many rules",
-			 RelationGetRelationName(matviewRel));
-
-	rule = matviewRel->rd_rules->rules[0];
-	if (rule->event != CMD_SELECT || !(rule->isInstead))
-		elog(ERROR,
-			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
-			 RelationGetRelationName(matviewRel));
 
-	actions = rule->actions;
-	if (list_length(actions) != 1)
-		elog(ERROR,
-			 "the rule for materialized view \"%s\" is not a single action",
-			 RelationGetRelationName(matviewRel));
+	dataQuery = get_matview_query(matviewRel);
 
 	/*
 	 * Check that there is a unique index with no WHERE clause on one or more
@@ -247,12 +284,6 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 					 errhint("Create a unique index with no WHERE clause on one or more columns of the materialized view.")));
 	}
 
-	/*
-	 * The stored query was rewritten at the time of the MV definition, but
-	 * has not been scribbled on by the planner.
-	 */
-	dataQuery = linitial_node(Query, actions);
-
 	/*
 	 * Check for active uses of the relation in the current transaction, such
 	 * as open scans.
@@ -311,7 +342,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 
 	/* Generate the data, if wanted. */
 	if (!stmt->skipData)
-		processed = refresh_matview_datafill(dest, dataQuery, queryString);
+		processed = refresh_matview_datafill(dest, dataQuery, NULL, queryString);
 
 	/* Make the matview match the newly generated data. */
 	if (concurrent)
@@ -369,6 +400,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  */
 static uint64
 refresh_matview_datafill(DestReceiver *dest, Query *query,
+						 QueryEnvironment *queryEnv,
 						 const char *queryString)
 {
 	List	   *rewritten;
@@ -405,7 +437,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 	/* Create a QueryDesc, redirecting output to our tuple receiver */
 	queryDesc = CreateQueryDesc(plan, queryString,
 								GetActiveSnapshot(), InvalidSnapshot,
-								dest, NULL, NULL, 0);
+								dest, NULL, queryEnv ? queryEnv: NULL, 0);
 
 	/* call ExecutorStart to prepare the plan for execution */
 	ExecutorStart(queryDesc, 0);
@@ -926,3 +958,1024 @@ CloseMatViewIncrementalMaintenance(void)
 	matview_maintenance_depth--;
 	Assert(matview_maintenance_depth >= 0);
 }
+
+/*
+ * IVM trigger function
+ */
+
+Datum
+IVM_immediate_maintenance(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	Relation	rel;
+	Oid relid;
+	Oid matviewOid;
+	Query	   *query, *old_delta_qry, *new_delta_qry;
+	char*		matviewname = trigdata->tg_trigger->tgargs[0];
+	List	   *names;
+	Relation matviewRel;
+	int old_depth = matview_maintenance_depth;
+
+	Oid			tableSpace;
+	Oid			relowner;
+	Oid			OIDDelta_new = InvalidOid;
+	Oid			OIDDelta_old = InvalidOid;
+	DestReceiver *dest_new = NULL, *dest_old = NULL;
+	char		relpersistence;
+	Oid			save_userid;
+	int			save_sec_context;
+	int			save_nestlevel;
+
+	ParseState *pstate;
+	QueryEnvironment *queryEnv = create_queryEnv();
+
+	Const	*dmy_arg = makeConst(INT4OID,
+								 -1,
+								 InvalidOid,
+								 sizeof(int32),
+								 Int32GetDatum(1),
+								 false,
+								 true); /* pass by value */
+
+	/* Create a dummy ParseState for addRangeTableEntryForENR */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	names = stringToQualifiedNameList(matviewname);
+
+	/*
+	 * Wait for concurrent transactions which update this materialized view at READ COMMITED.
+	 * This is needed to see changes commited in othre transactions. No wait and raise an error
+	 * at REPEATABLE READ or SERIALIZABLE to prevent anormal update of matviews.
+	 * XXX: dead-lock is possible here.
+	 */
+	if (!IsolationUsesXactSnapshot())
+		matviewOid = RangeVarGetRelid(makeRangeVarFromNameList(names), ExclusiveLock, true);
+	else
+		matviewOid = RangeVarGetRelidExtended(makeRangeVarFromNameList(names), ExclusiveLock, RVR_MISSING_OK | RVR_NOWAIT, NULL, NULL);
+
+	matviewRel = table_open(matviewOid, NoLock);
+
+	/*
+	 * Get and push the latast snapshot to see any changes which is commited during waiting in
+	 * other transactions at READ COMMITTED level.
+	 * XXX: Is this safe?
+	 */
+	PushActiveSnapshot(GetTransactionSnapshot());
+
+	/* Make sure it is a materialized view. */
+	if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("\"%s\" is not a materialized view",
+						RelationGetRelationName(matviewRel))));
+
+	rel = trigdata->tg_relation;
+	relid = rel->rd_id;
+
+	/* get view query*/
+	query = get_matview_query(matviewRel);
+
+	new_delta_qry = copyObject(query);
+	old_delta_qry = copyObject(query);
+
+	if (trigdata->tg_newtable)
+	{
+		RangeTblEntry *rte;
+		ListCell   *lc;
+
+		TargetEntry *tle;
+		Node *node;
+		FuncCall *fn;
+
+		EphemeralNamedRelation enr =
+			palloc(sizeof(EphemeralNamedRelationData));
+
+		enr->md.name = trigdata->tg_trigger->tgnewtable;
+		enr->md.reliddesc = trigdata->tg_relation->rd_id;
+		enr->md.tupdesc = NULL;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(trigdata->tg_newtable);
+		enr->reldata = trigdata->tg_newtable;
+		register_ENR(queryEnv, enr);
+
+		rte = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+		new_delta_qry->rtable = lappend(new_delta_qry->rtable, rte);
+
+		foreach(lc, new_delta_qry->rtable)
+		{
+			RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+			if (r->relid == relid)
+			{
+				lfirst(lc) = rte;
+				break;
+			}
+		}
+
+		if (query->hasAggs)
+		{
+			ListCell *lc;
+			List *agg_counts = NIL;
+			AttrNumber next_resno = list_length(query->targetList) + 1;
+			Node *node;
+
+			foreach(lc, query->targetList)
+			{
+				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+				TargetEntry *tle_count;
+
+				if (IsA(tle->expr, Aggref))
+				{
+					Aggref *aggref = (Aggref *) tle->expr;
+					const char *aggname = get_func_name(aggref->aggfnoid);
+
+					/*
+					 * For aggregate functions except to count, add count func with the same arg parameters.
+					 * Also, add sum func for agv.
+					 *
+					 * XXX: need some generalization
+					 * XXX: If there are same expressions explicitly in the target list, we can use this instead
+					 * of adding new duplicated one.
+					 */
+					if (strcmp(aggname, "count") != 0)
+					{
+						fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+
+						/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+						node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+						((Aggref *)node)->args = aggref->args;
+
+						tle_count = makeTargetEntry((Expr *) node,
+												next_resno,
+												NULL,
+												false);
+						agg_counts = lappend(agg_counts, tle_count);
+						next_resno++;
+					}
+					if (strcmp(aggname, "avg") == 0)
+					{
+						fn = makeFuncCall(list_make1(makeString("sum")), NIL, -1);
+
+						/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+						node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+						((Aggref *)node)->args = aggref->args;
+
+						tle_count = makeTargetEntry((Expr *) node,
+													next_resno,
+													NULL,
+													false);
+						agg_counts = lappend(agg_counts, tle_count);
+						next_resno++;
+					}
+				}
+
+			}
+			new_delta_qry->targetList = list_concat(new_delta_qry->targetList, agg_counts);
+		}
+
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+		fn->agg_star = true;
+		if (!new_delta_qry->groupClause && !new_delta_qry->hasAggs)
+			new_delta_qry->groupClause = transformDistinctClause(NULL, &new_delta_qry->targetList, new_delta_qry->sortClause, false);
+
+		node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+		tle = makeTargetEntry((Expr *) node,
+								  list_length(new_delta_qry->targetList) + 1,
+								  NULL,
+								  false);
+		new_delta_qry->targetList = lappend(new_delta_qry->targetList, tle);
+		new_delta_qry->hasAggs = true;
+	}
+
+	if (trigdata->tg_oldtable)
+	{
+		RangeTblEntry *rte;
+		ListCell   *lc;
+
+		TargetEntry *tle;
+		Node *node;
+		FuncCall *fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+
+		EphemeralNamedRelation enr =
+			palloc(sizeof(EphemeralNamedRelationData));
+
+		enr->md.name = trigdata->tg_trigger->tgoldtable;
+		enr->md.reliddesc = trigdata->tg_relation->rd_id;
+		enr->md.tupdesc = NULL;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(trigdata->tg_oldtable);
+		enr->reldata = trigdata->tg_oldtable;
+		register_ENR(queryEnv, enr);
+
+		rte = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+		old_delta_qry->rtable = lappend(old_delta_qry->rtable, rte);
+
+		foreach(lc, old_delta_qry->rtable)
+		{
+			RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+			if (r->relid == relid)
+			{
+				lfirst(lc) = rte;
+				break;
+			}
+		}
+
+		if (query->hasAggs)
+		{
+			ListCell *lc;
+			List *agg_counts = NIL;
+			AttrNumber next_resno = list_length(query->targetList) + 1;
+			Node *node;
+
+			foreach(lc, query->targetList)
+			{
+				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+				TargetEntry *tle_count;
+
+				if (IsA(tle->expr, Aggref))
+				{
+					Aggref *aggref = (Aggref *) tle->expr;
+					const char *aggname = get_func_name(aggref->aggfnoid);
+
+					/*
+					 * For aggregate functions except to count, add count func with the same arg parameters.
+					 * Also, add sum func for agv.
+					 *
+					 * XXX: need some generalization
+					 * XXX: If there are same expressions explicitly in the target list, we can use this instead
+					 * of adding new duplicated one.
+					 */
+
+					if (strcmp(aggname, "count") != 0)
+					{
+						fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+
+						/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+						node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+						((Aggref *)node)->args = aggref->args;
+
+						tle_count = makeTargetEntry((Expr *) node,
+												next_resno,
+												NULL,
+												false);
+						agg_counts = lappend(agg_counts, tle_count);
+						next_resno++;
+					}
+					if (strcmp(aggname, "avg") == 0)
+					{
+						fn = makeFuncCall(list_make1(makeString("sum")), NIL, -1);
+
+						/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+						node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+						((Aggref *)node)->args = aggref->args;
+
+						tle_count = makeTargetEntry((Expr *) node,
+													next_resno,
+													NULL,
+													false);
+						agg_counts = lappend(agg_counts, tle_count);
+						next_resno++;
+					}
+				}
+
+			}
+			old_delta_qry->targetList = list_concat(old_delta_qry->targetList, agg_counts);
+		}
+
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+		fn->agg_star = true;
+
+		if (!old_delta_qry->groupClause && !old_delta_qry->hasAggs)
+			old_delta_qry->groupClause = transformDistinctClause(NULL, &old_delta_qry->targetList, old_delta_qry->sortClause, false);
+
+		node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+		tle = makeTargetEntry((Expr *) node,
+								  list_length(old_delta_qry->targetList) + 1,
+								  NULL,
+								  false);
+		old_delta_qry->targetList = lappend(old_delta_qry->targetList, tle);
+		old_delta_qry->hasAggs = true;
+	}
+
+
+	/*
+	 * Check for active uses of the relation in the current transaction, such
+	 * as open scans.
+	 *
+	 * NB: We count on this to protect us against problems with refreshing the
+	 * data using TABLE_INSERT_FROZEN.
+	 */
+	CheckTableNotInUse(matviewRel, "REFRESH MATERIALIZED VIEW");
+
+	relowner = matviewRel->rd_rel->relowner;
+
+	/*
+	 * Switch to the owner's userid, so that any functions are run as that
+	 * user.  Also arrange to make GUC variable changes local to this command.
+	 * Don't lock it down too tight to create a temporary table just yet.  We
+	 * will switch modes when we are about to execute user code.
+	 */
+	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+	save_nestlevel = NewGUCNestLevel();
+
+	tableSpace = GetDefaultTablespace(RELPERSISTENCE_TEMP, false);
+	relpersistence = RELPERSISTENCE_TEMP;
+
+	/*
+	 * Create the transient table that will receive the regenerated data. Lock
+	 * it against access by any other process until commit (by which time it
+	 * will be gone).
+	 */
+	if (trigdata->tg_newtable)
+	{
+		OIDDelta_new = make_new_heap(matviewOid, tableSpace, relpersistence,
+									 ExclusiveLock);
+		LockRelationOid(OIDDelta_new, AccessExclusiveLock);
+		dest_new = CreateTransientRelDestReceiver(OIDDelta_new);
+	}
+	if (trigdata->tg_oldtable)
+	{
+		if (trigdata->tg_newtable)
+			OIDDelta_old = make_new_heap(OIDDelta_new, tableSpace, relpersistence,
+										 ExclusiveLock);
+		else
+			OIDDelta_old = make_new_heap(matviewOid, tableSpace, relpersistence,
+										 ExclusiveLock);
+		LockRelationOid(OIDDelta_old, AccessExclusiveLock);
+		dest_old = CreateTransientRelDestReceiver(OIDDelta_old);
+	}
+
+	/*
+	 * Now lock down security-restricted operations.
+	 */
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+
+	/* Generate the data. */
+	if (trigdata->tg_newtable)
+		refresh_matview_datafill(dest_new, new_delta_qry, queryEnv, NULL);
+	if (trigdata->tg_oldtable)
+		refresh_matview_datafill(dest_old, old_delta_qry, queryEnv, NULL);
+
+	PG_TRY();
+	{
+		apply_delta(matviewOid, OIDDelta_new, OIDDelta_old,
+					query, relowner, save_sec_context);
+	}
+	PG_CATCH();
+	{
+		matview_maintenance_depth = old_depth;
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	/* Pop the original snapshot. */
+	PopActiveSnapshot();
+
+	table_close(matviewRel, NoLock);
+
+	/* Roll back any GUC changes */
+	AtEOXact_GUC(false, save_nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(save_userid, save_sec_context);
+
+	return PointerGetDatum(NULL);
+}
+
+static void
+apply_delta(Oid matviewOid, Oid tempOid_new, Oid tempOid_old,
+			Query *query, Oid relowner, int save_sec_context)
+{
+	StringInfoData querybuf;
+	StringInfoData mvatts_buf, diffatts_buf;
+	StringInfoData mv_gkeys_buf, diff_gkeys_buf, updt_gkeys_buf;
+	StringInfoData diff_aggs_buf, update_aggs_old, update_aggs_new;
+	StringInfoData returning_buf, result_buf;
+	StringInfoData min_or_max_buf;
+	Relation	matviewRel;
+	Relation	tempRel_new = NULL, tempRel_old = NULL;
+	char	   *matviewname;
+	char	   *tempname_new = NULL, *tempname_old = NULL;
+	ListCell	*lc;
+	char	   *sep, *sep_agg;
+	bool		with_group = query->groupClause != NULL;
+	int			i;
+	bool 		has_min_or_max = false;
+	int			num_group_keys = 0;
+	int			num_min_or_max = 0;
+
+
+	initStringInfo(&querybuf);
+	matviewRel = table_open(matviewOid, NoLock);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	if (OidIsValid(tempOid_new))
+	{
+		tempRel_new = table_open(tempOid_new, NoLock);
+		tempname_new = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(tempRel_new)),
+												  RelationGetRelationName(tempRel_new));
+	}
+	if (OidIsValid(tempOid_old))
+	{
+		tempRel_old = table_open(tempOid_old, NoLock);
+		tempname_old = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(tempRel_old)),
+												  RelationGetRelationName(tempRel_old));
+	}
+
+	initStringInfo(&mvatts_buf);
+	initStringInfo(&diffatts_buf);
+	initStringInfo(&diff_aggs_buf);
+	initStringInfo(&update_aggs_old);
+	initStringInfo(&update_aggs_new);
+	initStringInfo(&returning_buf);
+	initStringInfo(&result_buf);
+	initStringInfo(&min_or_max_buf);
+
+	sep = "";
+	sep_agg= "";
+	i = 0;
+	foreach (lc, query->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char *resname = NameStr(attr->attname);
+
+		i++;
+
+
+		if (tle->resjunk)
+			continue;
+
+		appendStringInfo(&mvatts_buf, "%s", sep);
+		appendStringInfo(&diffatts_buf, "%s", sep);
+		sep = ", ";
+
+		appendStringInfo(&mvatts_buf, "%s", quote_qualified_identifier("mv", resname));
+		appendStringInfo(&diffatts_buf, "%s", quote_qualified_identifier("diff", resname));
+		if (query->hasAggs && IsA(tle->expr, Aggref))
+		{
+			Aggref *aggref = (Aggref *) tle->expr;
+			const char *aggname = get_func_name(aggref->aggfnoid);
+			const char *aggtype = format_type_be(aggref->aggtype); /* XXX: should be add_cast_to ? */
+
+			appendStringInfo(&update_aggs_old, "%s", sep_agg);
+			appendStringInfo(&update_aggs_new, "%s", sep_agg);
+			appendStringInfo(&diff_aggs_buf, "%s", sep_agg);
+
+			sep_agg = ", ";
+
+			/* XXX: need some generalization
+			 *
+			 * Specifically, Using func names is not robust.  We can use oids instead
+			 * of names, but it would be nice to add some information to pg_aggregate
+			 * and handler functions.
+			 */
+
+			if (!strcmp(aggname, "count"))
+			{
+				appendStringInfo(&update_aggs_old,
+					"%s = %s - %s",
+					quote_qualified_identifier(NULL, resname),
+					quote_qualified_identifier("mv", resname),
+					quote_qualified_identifier("t", resname)
+				);
+				appendStringInfo(&update_aggs_new,
+					"%s = %s + %s",
+					quote_qualified_identifier(NULL, resname),
+					quote_qualified_identifier("mv", resname),
+					quote_qualified_identifier("diff", resname)
+				);
+
+				appendStringInfo(&diff_aggs_buf, "%s",
+					quote_qualified_identifier("diff", resname)
+				);
+			}
+			else if (!strcmp(aggname, "sum"))
+			{
+				appendStringInfo(&update_aggs_old,
+					"%s = CASE WHEN %s = %s THEN NULL ELSE "
+						"COALESCE(%s,0) - COALESCE(%s, 0) END, "
+					"%s = %s - %s",
+					quote_qualified_identifier(NULL, resname),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("t", makeObjectName("__ivm_count",resname,"_")),
+
+					quote_qualified_identifier("mv", resname),
+					quote_qualified_identifier("t", resname),
+
+					quote_qualified_identifier(NULL, makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("t", makeObjectName("__ivm_count",resname,"_"))
+				);
+				appendStringInfo(&update_aggs_new,
+					"%s = CASE WHEN %s = 0 AND %s = 0 THEN NULL ELSE "
+						"COALESCE(%s,0) + COALESCE(%s, 0) END, "
+					"%s = %s + %s",
+					quote_qualified_identifier(NULL, resname),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",resname,"_")),
+
+					quote_qualified_identifier("mv", resname),
+					quote_qualified_identifier("diff", resname),
+
+					quote_qualified_identifier(NULL, makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",resname,"_"))
+				);
+
+				appendStringInfo(&diff_aggs_buf, "%s, %s",
+					quote_qualified_identifier("diff", resname),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",resname,"_"))
+				);
+			}
+			else if (!strcmp(aggname, "avg"))
+			{
+				appendStringInfo(&update_aggs_old,
+					"%s = CASE WHEN %s = %s THEN NULL ELSE "
+						"(COALESCE(%s,0) - COALESCE(%s, 0))::%s / (%s - %s) END, "
+					"%s = COALESCE(%s,0) - COALESCE(%s, 0), "
+					"%s = %s - %s",
+
+					quote_qualified_identifier(NULL, resname),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("t", makeObjectName("__ivm_count",resname,"_")),
+
+					quote_qualified_identifier("mv", makeObjectName("__ivm_sum",resname,"_")),
+					quote_qualified_identifier("t", makeObjectName("__ivm_sum",resname,"_")),
+					aggtype,
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("t", makeObjectName("__ivm_count",resname,"_")),
+
+					quote_qualified_identifier(NULL, makeObjectName("__ivm_sum",resname,"_")),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_sum",resname,"_")),
+					quote_qualified_identifier("t", makeObjectName("__ivm_sum",resname,"_")),
+
+					quote_qualified_identifier(NULL, makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("t", makeObjectName("__ivm_count",resname,"_"))
+				);
+				appendStringInfo(&update_aggs_new,
+					"%s = CASE WHEN %s = 0 AND %s = 0 THEN NULL ELSE "
+						"(COALESCE(%s,0)+ COALESCE(%s, 0))::%s / (%s + %s) END, "
+					"%s = COALESCE(%s,0) + COALESCE(%s, 0), "
+					"%s = %s + %s",
+
+					quote_qualified_identifier(NULL, resname),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",resname,"_")),
+
+					quote_qualified_identifier("mv", makeObjectName("__ivm_sum",resname,"_")),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_sum",resname,"_")),
+					aggtype,
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",resname,"_")),
+
+					quote_qualified_identifier(NULL, makeObjectName("__ivm_sum",resname,"_")),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_sum",resname,"_")),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_sum",resname,"_")),
+
+					quote_qualified_identifier(NULL, makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",resname,"_"))
+				);
+
+				appendStringInfo(&diff_aggs_buf, "%s, %s, %s",
+					quote_qualified_identifier("diff", resname),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_sum",resname,"_")),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",resname,"_"))
+				);
+			}
+			else if (!strcmp(aggname, "min") || !strcmp(aggname, "max"))
+			{
+				bool is_min = !strcmp(aggname, "min");
+
+				if (!has_min_or_max)
+				{
+					has_min_or_max = true;
+					appendStringInfo(&returning_buf, "RETURNING mv.ctid, (");
+				}
+				else
+				{
+					appendStringInfo(&returning_buf, " OR ");
+					appendStringInfo(&min_or_max_buf, ",");
+				}
+
+				appendStringInfo(&returning_buf, "%s %s %s",
+					quote_qualified_identifier("mv", resname),
+					is_min ? ">=" : "<=",
+					quote_qualified_identifier("t", resname)
+				);
+				appendStringInfo(&min_or_max_buf, "%s", quote_qualified_identifier(NULL, resname));
+
+				appendStringInfo(&update_aggs_old,
+					"%s = CASE WHEN %s = %s THEN NULL ELSE "
+						"%s END, "
+					"%s = %s - %s",
+					quote_qualified_identifier(NULL, resname),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("t", makeObjectName("__ivm_count",resname,"_")),
+
+					quote_qualified_identifier("mv", resname),
+
+					quote_qualified_identifier(NULL, makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("t", makeObjectName("__ivm_count",resname,"_"))
+				);
+				appendStringInfo(&update_aggs_new,
+					"%s = CASE WHEN %s = 0 AND %s = 0 THEN NULL ELSE "
+						"%s(%s,%s) END, "
+					"%s = %s + %s",
+					quote_qualified_identifier(NULL, resname),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",resname,"_")),
+
+					is_min ? "least" : "greatest",
+					quote_qualified_identifier("mv", resname),
+					quote_qualified_identifier("diff", resname),
+
+					quote_qualified_identifier(NULL, makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("mv", makeObjectName("__ivm_count",resname,"_")),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",resname,"_"))
+				);
+
+				appendStringInfo(&diff_aggs_buf, "%s, %s",
+					quote_qualified_identifier("diff", resname),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",resname,"_"))
+				);
+
+				num_min_or_max++;
+			}
+			else
+				elog(ERROR, "unsupported aggregate function: %s", aggname);
+
+		}
+	}
+	if (has_min_or_max)
+		appendStringInfo(&returning_buf, ") AS recalc");
+
+	if (query->hasAggs)
+	{
+		initStringInfo(&mv_gkeys_buf);
+		initStringInfo(&diff_gkeys_buf);
+		initStringInfo(&updt_gkeys_buf);
+
+		if (with_group)
+		{
+			sep_agg= "";
+			foreach (lc, query->groupClause)
+			{
+				SortGroupClause *sgcl = (SortGroupClause *) lfirst(lc);
+				TargetEntry *tle = get_sortgroupclause_tle(sgcl, query->targetList);
+
+				Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno-1);
+				char *resname = NameStr(attr->attname);
+
+				appendStringInfo(&mv_gkeys_buf, "%s", sep_agg);
+				appendStringInfo(&diff_gkeys_buf, "%s", sep_agg);
+				appendStringInfo(&updt_gkeys_buf, "%s", sep_agg);
+
+				sep_agg = ", ";
+
+				appendStringInfo(&mv_gkeys_buf, "%s", quote_qualified_identifier("mv", resname));
+				appendStringInfo(&diff_gkeys_buf, "%s", quote_qualified_identifier("diff", resname));
+				appendStringInfo(&updt_gkeys_buf, "%s", quote_qualified_identifier("updt", resname));
+
+				num_group_keys++;
+			}
+
+			if (has_min_or_max)
+			{
+				appendStringInfo(&returning_buf, ", %s", mv_gkeys_buf.data);
+				appendStringInfo(&result_buf, "SELECT ctid AS tid, %s FROM updt WHERE recalc", updt_gkeys_buf.data);
+			}
+
+		}
+		else
+		{
+			appendStringInfo(&mv_gkeys_buf, "1");
+			appendStringInfo(&diff_gkeys_buf, "1");
+			appendStringInfo(&updt_gkeys_buf, "1");
+		}
+	}
+
+
+	/* Open SPI context. */
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	/* Analyze the temp table with the new contents. */
+	if (tempname_new)
+	{
+		appendStringInfo(&querybuf, "ANALYZE %s", tempname_new);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+	if (tempname_old)
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf, "ANALYZE %s", tempname_old);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+
+	OpenMatViewIncrementalMaintenance();
+
+	if (query->hasAggs)
+	{
+		if (tempname_old)
+		{
+			resetStringInfo(&querybuf);
+			appendStringInfo(&querybuf,
+							"WITH t AS ("
+							"  SELECT diff.__ivm_count__, "
+							"         %s,"
+							"         (diff.__ivm_count__ = mv.__ivm_count__) AS for_dlt, "
+							"         mv.ctid"
+							"  FROM %s AS mv, %s AS diff WHERE (%s) = (%s)"
+							"), updt AS ("
+							"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ - t.__ivm_count__,"
+							"                      %s"
+							"  FROM t WHERE mv.ctid = t.ctid AND NOT for_dlt"
+							"   %s"
+							"), dlt AS ("
+							"  DELETE FROM %s AS mv USING t WHERE mv.ctid = t.ctid AND for_dlt "
+							") %s",
+							diff_aggs_buf.data,
+							matviewname, tempname_old, mv_gkeys_buf.data, diff_gkeys_buf.data,
+							matviewname, update_aggs_old.data,
+							returning_buf.data,
+							matviewname,
+							has_min_or_max ? result_buf.data : "SELECT 1"
+							);
+			if (SPI_exec(querybuf.data, 0) != SPI_OK_SELECT)
+				elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+
+			if (has_min_or_max && SPI_processed > 0)
+			{
+				SPITupleTable *tuptable_recalc = SPI_tuptable;
+				TupleDesc   tupdesc_recalc = tuptable_recalc->tupdesc;
+				int64		processed = SPI_processed;
+				uint64      i;
+				Oid			*keyTypes, *minmaxTypes;
+				char		*keyNulls, *minmaxNulls;
+				Datum		*keyVals, *minmaxVals;
+
+				keyTypes = palloc(sizeof(Oid) * num_group_keys);
+				keyNulls = palloc(sizeof(char) * num_group_keys);
+				keyVals = palloc(sizeof(Datum) * num_group_keys);
+
+				minmaxTypes = palloc(sizeof(Oid) * (num_min_or_max + 1));
+				minmaxNulls = palloc(sizeof(char) * (num_min_or_max + 1));
+				minmaxVals = palloc(sizeof(Datum) * (num_min_or_max + 1));
+
+				Assert(tupdesc_recalc->natts == num_group_keys + 1);
+
+				for (i = 0; i < num_group_keys; i++)
+					keyTypes[i] = TupleDescAttr(tupdesc_recalc, i+1)->atttypid;
+
+				for (i=0; i< processed; i++)
+				{
+					int j;
+					bool isnull;
+					SPIPlanPtr plan;
+					SPITupleTable *tuptable_minmax;
+					TupleDesc   tupdesc_minmax;
+
+
+					for (j = 0; j < num_group_keys; j++)
+					{
+						keyVals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, j+2, &isnull);
+						if (isnull)
+							keyNulls[j] = 'n';
+						else
+							keyNulls[j] = ' ';
+					}
+
+					plan = get_plan_for_recalc_min_max(matviewOid, min_or_max_buf.data,
+													   mv_gkeys_buf.data, num_group_keys, keyTypes);
+
+					if (SPI_execute_plan(plan, keyVals, keyNulls, false, 0) != SPI_OK_SELECT)
+						elog(ERROR, "SPI_execcute_plan");
+					if (SPI_processed != 1)
+						elog(ERROR, "SPI_execcute_plan returned zere or more than one rows");
+
+					tuptable_minmax = SPI_tuptable;
+					tupdesc_minmax = tuptable_minmax->tupdesc;
+
+					Assert(tupdesc_minmax->natts == num_min_or_max);
+
+					for (j = 0; j < tupdesc_minmax->natts; j++)
+					{
+						if (i == 0)
+							minmaxTypes[j] = TupleDescAttr(tupdesc_minmax, j)->atttypid;
+
+						minmaxVals[j] = SPI_getbinval(tuptable_minmax->vals[0], tupdesc_minmax, j+1, &isnull);
+						if (isnull)
+							minmaxNulls[j] = 'n';
+						else
+							minmaxNulls[j] = ' ';
+					}
+					minmaxTypes[j] = TIDOID;
+					minmaxVals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, 1, &isnull);
+					minmaxNulls[j] = ' ';
+
+					plan = get_plan_for_set_min_max(matviewname, min_or_max_buf.data,
+													num_min_or_max, minmaxTypes);
+
+					if (SPI_execute_plan(plan, minmaxVals, minmaxNulls, false, 0) != SPI_OK_UPDATE)
+						elog(ERROR, "SPI_execcute_plan");
+
+				}
+			}
+
+		}
+		if (tempname_new)
+		{
+			resetStringInfo(&querybuf);
+			appendStringInfo(&querybuf,
+							"WITH updt AS ("
+							"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ + diff.__ivm_count__"
+							", %s "
+							"  FROM %s AS diff WHERE (%s) = (%s)"
+							"  RETURNING %s"
+							") INSERT INTO %s (SELECT * FROM %s AS diff WHERE (%s) NOT IN (SELECT %s FROM updt));",
+							matviewname, update_aggs_new.data, tempname_new,
+							mv_gkeys_buf.data, diff_gkeys_buf.data, diff_gkeys_buf.data,
+							matviewname, tempname_new, diff_gkeys_buf.data, updt_gkeys_buf.data);
+			if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+				elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+		}
+	}
+	else
+	{
+		if (tempname_old)
+		{
+			resetStringInfo(&querybuf);
+			appendStringInfo(&querybuf,
+							"WITH t AS ("
+							"  SELECT diff.__ivm_count__, (diff.__ivm_count__ = mv.__ivm_count__) AS for_dlt, mv.ctid"
+							"  FROM %s AS mv, %s AS diff WHERE (%s) = (%s)"
+							"), updt AS ("
+							"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ - t.__ivm_count__"
+							"  FROM t WHERE mv.ctid = t.ctid AND NOT for_dlt"
+							") DELETE FROM %s AS mv USING t WHERE mv.ctid = t.ctid AND for_dlt;",
+							matviewname, tempname_old, mvatts_buf.data, diffatts_buf.data, matviewname, matviewname);
+			if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+				elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+		}
+		if (tempname_new)
+		{
+			resetStringInfo(&querybuf);
+			appendStringInfo(&querybuf,
+							"WITH updt AS ("
+							"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ + diff.__ivm_count__"
+							"  FROM %s AS diff WHERE (%s) = (%s)"
+							"  RETURNING %s"
+							") INSERT INTO %s (SELECT * FROM %s AS diff WHERE (%s) NOT IN (SELECT * FROM updt));",
+							matviewname, tempname_new, mvatts_buf.data, diffatts_buf.data, diffatts_buf.data, matviewname, tempname_new, diffatts_buf.data);
+			if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+				elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+		}
+	}
+
+	/* We're done maintaining the materialized view. */
+	CloseMatViewIncrementalMaintenance();
+
+	if (OidIsValid(tempOid_new))
+		table_close(tempRel_new, NoLock);
+	if (OidIsValid(tempOid_old))
+		table_close(tempRel_old, NoLock);
+
+	table_close(matviewRel, NoLock);
+
+	/* Clean up temp tables. */
+	if (OidIsValid(tempOid_new))
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf, "DROP TABLE %s", tempname_new);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+	if (OidIsValid(tempOid_old))
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf, "DROP TABLE %s", tempname_old);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+
+	/* Close SPI context. */
+	if (SPI_finish() != SPI_OK_FINISH)
+		elog(ERROR, "SPI_finish failed");
+}
+
+
+static SPIPlanPtr
+get_plan_for_recalc_min_max(Oid matviewOid, const char *min_max_list,
+							const char *group_keys, int nkeys, Oid *keyTypes)
+{
+	StringInfoData	str;
+	char	*viewdef;
+	int 	i;
+
+	if (plan_for_recalc_min_max)
+		return plan_for_recalc_min_max;
+
+	/* get view definition of matview */
+	viewdef = text_to_cstring((text *) DatumGetPointer(
+				DirectFunctionCall1(pg_get_viewdef, ObjectIdGetDatum(matviewOid))));
+	/* get rid of tailling semi-collon */
+	viewdef[strlen(viewdef)-1] = '\0';
+
+	initStringInfo(&str);
+	appendStringInfo(&str, "SELECT %s FROM (%s) mv WHERE (%s) = (",
+					 min_max_list, viewdef, group_keys);
+
+	for (i = 1; i <= nkeys; i++)
+		appendStringInfo(&str, "%s$%d", (i==1 ? "" : ", "),i );
+
+	appendStringInfo(&str, ")");
+
+	plan_for_recalc_min_max = SPI_prepare(str.data, nkeys, keyTypes);
+	SPI_keepplan(plan_for_recalc_min_max);
+
+	return plan_for_recalc_min_max;
+}
+
+static SPIPlanPtr
+get_plan_for_set_min_max(char *matviewname, const char *min_max_list,
+						  int num_min_max, Oid *valTypes)
+{
+	StringInfoData str;
+	int 	i;
+
+	if (plan_for_set_min_max)
+		return plan_for_set_min_max;
+
+	initStringInfo(&str);
+	appendStringInfo(&str, "UPDATE %s AS mv SET (%s) = (",
+		matviewname, min_max_list);
+
+	for (i = 1; i <= num_min_max; i++)
+		appendStringInfo(&str, "%s$%d", (i==1 ? "" : ", "), i);
+
+	appendStringInfo(&str, ") WHERE ctid = $%d", i);
+
+	plan_for_set_min_max = SPI_prepare(str.data, num_min_max + 1, valTypes);
+	SPI_keepplan(plan_for_set_min_max);
+
+	return plan_for_set_min_max;
+}
+
+
+/*
+ * get_matview_query - get the Query from a matview's _RETURN rule.
+ */
+static Query *
+get_matview_query(Relation matviewRel)
+{
+	RewriteRule *rule;
+	List * actions;
+
+	/*
+	 * Check that everything is correct for a refresh. Problems at this point
+	 * are internal errors, so elog is sufficient.
+	 */
+	if (matviewRel->rd_rel->relhasrules == false ||
+		matviewRel->rd_rules->numLocks < 1)
+		elog(ERROR,
+			 "materialized view \"%s\" is missing rewrite information",
+			 RelationGetRelationName(matviewRel));
+
+	if (matviewRel->rd_rules->numLocks > 1)
+		elog(ERROR,
+			 "materialized view \"%s\" has too many rules",
+			 RelationGetRelationName(matviewRel));
+
+	rule = matviewRel->rd_rules->rules[0];
+	if (rule->event != CMD_SELECT || !(rule->isInstead))
+		elog(ERROR,
+			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
+			 RelationGetRelationName(matviewRel));
+
+	actions = rule->actions;
+	if (list_length(actions) != 1)
+		elog(ERROR,
+			 "the rule for materialized view \"%s\" is not a single action",
+			 RelationGetRelationName(matviewRel));
+
+	/*
+	 * The stored query was rewritten at the time of the MV definition, but
+	 * has not been scribbled on by the planner.
+	 */
+	return linitial_node(Query, actions);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 6414aded0e..4f54258005 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2361,6 +2361,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(relisivm);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4f2ebe5118..608d477bd5 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2641,6 +2641,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(relisivm);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 86c31a48c9..be07641e82 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3044,6 +3044,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6c2626ee62..aa01e205c3 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1366,6 +1366,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c97bb367f8..29040126e0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -418,6 +418,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target create_mv_target
+%type <boolean>	incremental
 
 %type <defelt>	createfunc_opt_item common_func_opt_item dostmt_opt_item
 %type <fun_param> func_arg func_arg_with_default table_func_column aggr_arg
@@ -645,7 +646,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
 	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
-	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+	INCLUDING INCREMENT INCREMENTAL INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
 	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
 	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -4054,30 +4055,32 @@ opt_with_data:
  *****************************************************************************/
 
 CreateMatViewStmt:
-		CREATE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
+		CREATE OptNoLog incremental MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $7;
-					ctas->into = $5;
+					ctas->query = $8;
+					ctas->into = $6;
 					ctas->relkind = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = false;
 					/* cram additional flags into the IntoClause */
-					$5->rel->relpersistence = $2;
-					$5->skipData = !($8);
+					$6->rel->relpersistence = $2;
+					$6->skipData = !($9);
+					$6->ivm = $3;
 					$$ = (Node *) ctas;
 				}
-		| CREATE OptNoLog MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
+		| CREATE OptNoLog incremental MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $10;
-					ctas->into = $8;
+					ctas->query = $11;
+					ctas->into = $9;
 					ctas->relkind = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = true;
 					/* cram additional flags into the IntoClause */
-					$8->rel->relpersistence = $2;
-					$8->skipData = !($11);
+					$9->rel->relpersistence = $2;
+					$9->skipData = !($12);
+					$9->ivm = $3;
 					$$ = (Node *) ctas;
 				}
 		;
@@ -4094,9 +4097,14 @@ create_mv_target:
 					$$->tableSpaceName = $5;
 					$$->viewQuery = NULL;		/* filled at analysis time */
 					$$->skipData = false;		/* might get changed later */
+					$$->ivm = false;
 				}
 		;
 
+incremental:	INCREMENTAL				{ $$ = true; }
+				| /*EMPTY*/				{ $$ = false; }
+		;
+
 OptNoLog:	UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
 		;
@@ -15128,6 +15136,7 @@ unreserved_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 4dd81507a7..a6c8c3fd4d 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -37,6 +37,7 @@
 #include "utils/syscache.h"
 #include "utils/varlena.h"
 
+#include "commands/matview.h"
 
 #define MAX_FUZZY_DISTANCE				3
 
@@ -56,9 +57,10 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							int count, int offset,
 							int rtindex, int sublevels_up,
 							int location, bool include_dropped,
-							List **colnames, List **colvars);
+							List **colnames, List **colvars, bool is_ivm);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool isIvmColumn(const char *s);
 
 
 /*
@@ -1241,6 +1243,7 @@ addRangeTableEntry(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -1320,6 +1323,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -2318,7 +2322,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 						expandTupleDesc(tupdesc, rte->eref,
 										rtfunc->funccolcount, atts_done,
 										rtindex, sublevels_up, location,
-										include_dropped, colnames, colvars);
+										include_dropped, colnames, colvars, false);
 					}
 					else if (functypclass == TYPEFUNC_SCALAR)
 					{
@@ -2570,10 +2574,16 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
 	expandTupleDesc(rel->rd_att, eref, rel->rd_att->natts, 0,
 					rtindex, sublevels_up,
 					location, include_dropped,
-					colnames, colvars);
+					colnames, colvars, RelationIsIVM(rel));
 	relation_close(rel, AccessShareLock);
 }
 
+static bool
+isIvmColumn(const char *s)
+{
+	return (strncmp(s, "__ivm_", 6) == 0); 
+}
+
 /*
  * expandTupleDesc -- expandRTE subroutine
  *
@@ -2587,7 +2597,7 @@ static void
 expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 				int rtindex, int sublevels_up,
 				int location, bool include_dropped,
-				List **colnames, List **colvars)
+				List **colnames, List **colvars, bool is_ivm)
 {
 	ListCell   *aliascell;
 	int			varattno;
@@ -2600,6 +2610,9 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 	{
 		Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
 
+		if (is_ivm && isIvmColumn(NameStr(attr->attname)) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		if (attr->attisdropped)
 		{
 			if (include_dropped)
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 7df2b6154c..5385a038b1 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -765,7 +765,8 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 														attr->atttypmod))));
 	}
 
-	if (i != resultDesc->natts)
+	/* No check for materialized views since this could have special columns for IVM */
+	if ((!isSelect || requireColumnNameMatch) && i != resultDesc->natts)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 isSelect ?
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index c96ef8595a..5f2cb21b3f 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -41,6 +41,8 @@
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 
+#include "parser/parser.h"
+#include "commands/matview.h"
 
 /* We use a list of these to detect recursion in RewriteQuery */
 typedef struct rewrite_event
@@ -1597,6 +1599,50 @@ ApplyRetrieveRule(Query *parsetree,
 	if (rule->qual != NULL)
 		elog(ERROR, "cannot handle qualified ON SELECT rule");
 
+	if (RelationIsIVM(relation))
+	{
+		rule_action = copyObject(linitial(rule->actions));
+
+		if (!rule_action->distinctClause && !rule_action->groupClause && !rule_action->hasAggs)
+		{
+			StringInfoData str;
+			RawStmt *raw;
+			Query *sub;
+
+			if (rule_action->hasDistinctOn)
+				elog(ERROR, "DISTINCT ON is not supported in IVM");
+
+			initStringInfo(&str);
+			appendStringInfo(&str, "SELECT mv.*, __ivm_count__ FROM %s mv, generate_series(1, mv.__ivm_count__)",
+						quote_qualified_identifier(get_namespace_name(RelationGetNamespace(relation)),
+													RelationGetRelationName(relation)));
+
+			raw = (RawStmt*)linitial(raw_parser(str.data));
+			sub = transformStmt(make_parsestate(NULL),raw->stmt);
+
+			rte = rt_fetch(rt_index, parsetree->rtable);
+
+			rte->rtekind = RTE_SUBQUERY;
+			rte->subquery = sub;
+			rte->security_barrier = RelationIsSecurityView(relation);
+			/* Clear fields that should not be set in a subquery RTE */
+			rte->relid = InvalidOid;
+			rte->relkind = 0;
+			rte->rellockmode = 0;
+			rte->tablesample = NULL;
+			rte->inh = false;			/* must not be set for a subquery */
+
+			rte->requiredPerms = 0;		/* no permission check on subquery itself */
+			rte->checkAsUser = InvalidOid;
+			rte->selectedCols = NULL;
+			rte->insertedCols = NULL;
+			rte->updatedCols = NULL;
+			rte->extraUpdatedCols = NULL;
+		}
+
+		return parsetree;
+	}
+
 	if (rt_index == parsetree->resultRelation)
 	{
 		/*
@@ -1906,7 +1952,8 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
 		 * In that case this test would need to be postponed till after we've
 		 * opened the rel, so that we could check its state.
 		 */
-		if (rte->relkind == RELKIND_MATVIEW)
+		if (rte->relkind == RELKIND_MATVIEW &&
+			(!rte->relisivm || MatViewIncrementalMaintenanceIsEnabled() || parsetree->commandType != CMD_SELECT))
 			continue;
 
 		/*
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index c13c08a97b..296cc53ffd 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1844,6 +1844,30 @@ get_rel_relispartition(Oid relid)
 		return false;
 }
 
+/*
+ * get_rel_relisivm
+ *
+ *		Returns the relisivm flag associated with a given relation.
+ */
+bool
+get_rel_relisivm(Oid relid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp);
+		bool		result;
+
+		result = reltup->relisivm;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return false;
+}
+
 /*
  * get_rel_tablespace
  *
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 248860758c..531da6f879 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1860,6 +1860,8 @@ formrdesc(const char *relationName, Oid relationReltype,
 
 	/* ... and they're always populated, too */
 	relation->rd_rel->relispopulated = true;
+	/* ... and they're always no ivm, too */
+	relation->rd_rel->relisivm = false;
 
 	relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
 	relation->rd_rel->relpages = 0;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 774cc764ff..cc7388d412 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1491,6 +1491,7 @@ describeOneTableDetails(const char *schemaname,
 		char		relpersistence;
 		char		relreplident;
 		char	   *relam;
+		bool		isivm;
 	}			tableinfo;
 	bool		show_column_details = false;
 
@@ -1511,6 +1512,7 @@ describeOneTableDetails(const char *schemaname,
 						  "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
 						  "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
 						  "c.relpersistence, c.relreplident, am.amname\n"
+						  ",c.relisivm\n"
 						  "FROM pg_catalog.pg_class c\n "
 						  "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
 						  "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
@@ -1687,6 +1689,10 @@ describeOneTableDetails(const char *schemaname,
 			(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
 	else
 		tableinfo.relam = NULL;
+	if (pset.sversion >= 130000)
+		tableinfo.isivm = strcmp(PQgetvalue(res, 0, 15), "t") == 0;
+	else
+		tableinfo.isivm = false;
 	PQclear(res);
 	res = NULL;
 
@@ -3291,6 +3297,12 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
 			printTableAddFooter(&cont, buf.data);
 		}
+
+		/* Incremental view maintance info */
+		if (verbose && tableinfo.relkind == RELKIND_MATVIEW && tableinfo.isivm)
+		{
+			printTableAddFooter(&cont, _("Incremental view maintenance: yes"));
+		}
 	}
 
 	/* reloptions, if verbose */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 3f7001fb69..c481b3a2e6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1001,6 +1001,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"INCREMENTAL MATERIALIZED VIEW", NULL, NULL, &Query_for_list_of_matviews},
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2486,7 +2487,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
-		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
+		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW", "INCREMENTAL MATERIALIZED VIEW");
 	/* Complete PARTITION BY with RANGE ( or LIST ( or ... */
 	else if (TailMatches("PARTITION", "BY"))
 		COMPLETE_WITH("RANGE (", "LIST (", "HASH (");
@@ -2734,13 +2735,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches("CREATE", "MATERIALIZED"))
+	else if (Matches("CREATE", "MATERIALIZED") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED"))
 		COMPLETE_WITH("VIEW");
-	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	/* Complete CREATE MATERIALIZED VIEW <name> with AS  */
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny) ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH("SELECT");
 
 /* CREATE EVENT TRIGGER */
diff --git a/src/include/catalog/pg_class.dat b/src/include/catalog/pg_class.dat
index 9bcf28676d..7deda405af 100644
--- a/src/include/catalog/pg_class.dat
+++ b/src/include/catalog/pg_class.dat
@@ -27,7 +27,7 @@
   relpersistence => 'p', relkind => 'r', relnatts => '31', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 { oid => '1249',
@@ -37,7 +37,7 @@
   relpersistence => 'p', relkind => 'r', relnatts => '25', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 { oid => '1255',
@@ -47,17 +47,17 @@
   relpersistence => 'p', relkind => 'r', relnatts => '29', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 { oid => '1259',
   relname => 'pg_class', reltype => 'pg_class', relam => 'heap',
   relfilenode => '0', relpages => '0', reltuples => '0', relallvisible => '0',
   reltoastrelid => '0', relhasindex => 'f', relisshared => 'f',
-  relpersistence => 'p', relkind => 'r', relnatts => '33', relchecks => '0',
+  relpersistence => 'p', relkind => 'r', relnatts => '34', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 090b6ba907..ff535f5504 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
 	/* is relation a partition? */
 	bool		relispartition;
 
+	/* is relation a matview with ivm? */
+	bool		relisivm;
+
 	/* heap for rewrite during DDL, link to original rel */
 	Oid			relrewrite BKI_DEFAULT(0);
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0902dce5f1..6e1057c5b6 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10701,4 +10701,9 @@
   proname => 'pg_partition_root', prorettype => 'regclass',
   proargtypes => 'regclass', prosrc => 'pg_partition_root' },
 
+# IVM
+{ oid => '786', descr => 'ivm trigger',
+  proname => 'IVM_immediate_maintenance', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_maintenance' },
+
 ]
diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h
index edf04bf415..8dd8193d9c 100644
--- a/src/include/commands/matview.h
+++ b/src/include/commands/matview.h
@@ -23,6 +23,8 @@
 
 extern void SetMatViewPopulatedState(Relation relation, bool newstate);
 
+extern void SetMatViewIVMState(Relation relation, bool newstate);
+
 extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										ParamListInfo params, char *completionTag);
 
@@ -30,4 +32,6 @@ extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
 
 extern bool MatViewIncrementalMaintenanceIsEnabled(void);
 
+extern Datum IVM_immediate_maintenance(PG_FUNCTION_ARGS);
+
 #endif							/* MATVIEW_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 94ded3c135..5c8a5ae3ca 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1002,6 +1002,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		relisivm;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -2059,6 +2060,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		ivm;			/* incremental view maintenance is used by materialized view */
 } CreateStmt;
 
 /* ----------
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 860a84de7c..6df58e2ff9 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -117,6 +117,7 @@ typedef struct IntoClause
 	char	   *tableSpaceName; /* table space to use, or NULL */
 	Node	   *viewQuery;		/* materialized view's SELECT query */
 	bool		skipData;		/* true for WITH NO DATA */
+	bool		ivm;			/* true for WITH IVM */
 } IntoClause;
 
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 00ace8425e..d682ee11cc 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -198,6 +198,7 @@ PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
 PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
+PG_KEYWORD("incremental", INCREMENTAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
 PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index c8df5bff9f..8fd919349e 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -129,6 +129,7 @@ extern Oid	get_rel_namespace(Oid relid);
 extern Oid	get_rel_type_id(Oid relid);
 extern char get_rel_relkind(Oid relid);
 extern bool get_rel_relispartition(Oid relid);
+extern bool get_rel_relisivm(Oid relid);
 extern Oid	get_rel_tablespace(Oid relid);
 extern char get_rel_persistence(Oid relid);
 extern Oid	get_transform_fromsql(Oid typid, Oid langid, List *trftypes);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b0fe19ebc5..9da0a4a9f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -565,6 +565,8 @@ typedef struct ViewOptions
  */
 #define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated)
 
+#define RelationIsIVM(relation) ((relation)->rd_rel->relisivm)
+
 /*
  * RelationIsAccessibleInLogicalDecoding
  *		True if we need to log enough information to have access via
diff --git a/src/test/regress/expected/incremental_matview.out b/src/test/regress/expected/incremental_matview.out
new file mode 100644
index 0000000000..68060cc179
--- /dev/null
+++ b/src/test/regress/expected/incremental_matview.out
@@ -0,0 +1,306 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- immediaite maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- result of materliazied view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 20
+ 30
+ 40
+ 50
+(6 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+ROLLBACK;
+-- support SUM(), COUNT() and AVG() aggregation function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i),AVG(j)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 | 120 |     2 | 60.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg          
+---+-----+-------+----------------------
+ 1 |  10 |     1 |  10.0000000000000000
+ 2 | 220 |     2 | 110.0000000000000000
+ 3 |  30 |     1 |  30.0000000000000000
+ 4 |  40 |     1 |  40.0000000000000000
+ 5 |  50 |     1 |  50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support COUNT(*) aggregation function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j),COUNT(*)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 |  20 |     1
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 | 120 |     2
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+ROLLBACK;
+-- support having only aggregation function without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j)FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum 
+-----
+ 150
+(1 row)
+
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum 
+-----
+ 170
+(1 row)
+
+ROLLBACK;
+-- resolved issue: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     3 |  3.3333333333333333
+ 2 |  80 |     3 | 26.6666666666666667
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support MIN(), MAX() aggregation functions 
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT i, MIN(j), MAX(j)  FROM mv_base_a GROUP BY i;
+INSERT INTO mv_base_a VALUES
+  (1,11), (1,12),
+  (2,21), (2,22),
+  (3,31), (3,32),
+  (4,41), (4,42),
+  (5,51), (5,52); 
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  10 |  12
+ 2 |  20 |  22
+ 3 |  30 |  32
+ 4 |  40 |  42
+ 5 |  50 |  52
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  11 |  12
+ 2 |  20 |  22
+ 3 |  30 |  31
+ 4 |  40 |  42
+ 5 |  50 |  52
+(5 rows)
+
+ROLLBACK;
+-- restriction of incremental view maintenance
+-- contain system column
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm01 AS SELECT i,j,xmin FROM mv_base_a;
+ERROR:  system column is not supported with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+ERROR:  system column is not supported with IVM
+-- contain subquery
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm03 AS SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );
+ERROR:  subquery is not supported with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT a.i,a.j FROM mv_base_a a,( SELECT * FROM mv_base_b) b WHERE a.i = b.i;
+ERROR:  subquery is not supported with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm05 AS SELECT i,j, (SELECT k FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+ERROR:  subquery is not supported with IVM
+-- contain CTE
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm06 AS WITH b AS (SELECT i,k FROM mv_base_b WHERE k < 103) SELECT a.i,a.j FROM mv_base_a a,b WHERE a.i = b.i;
+ERROR:  CTE is not supported with IVM
+-- contain view or materialized view
+CREATE VIEW b_view AS SELECT i,k FROM mv_base_b;
+CREATE MATERIALIZED VIEW b_mview AS SELECT i,k FROM mv_base_b;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm07 AS SELECT a.i,a.j FROM mv_base_a a,b_view b WHERE a.i = b.i;
+ERROR:  VIEW or MATERIALIZED VIEW is not supported with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm08 AS SELECT a.i,a.j FROM mv_base_a a,b_mview b WHERE a.i = b.i;
+ERROR:  VIEW or MATERIALIZED VIEW is not supported with IVM
+DROP TABLE mv_base_b CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to materialized view mv_ivm_1
+drop cascades to view b_view
+drop cascades to materialized view b_mview
+DROP TABLE mv_base_a CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8fb55f045e..39e79d8d10 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -78,7 +78,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tidscan
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tidscan incremental_matview
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index a39ca1012a..552c80c851 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -112,6 +112,7 @@ test: init_privs
 test: security_label
 test: collate
 test: matview
+test: incremental_matview
 test: lock
 test: replica_identity
 test: rowsecurity
diff --git a/src/test/regress/sql/incremental_matview.sql b/src/test/regress/sql/incremental_matview.sql
new file mode 100644
index 0000000000..2dd968f709
--- /dev/null
+++ b/src/test/regress/sql/incremental_matview.sql
@@ -0,0 +1,117 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i);
+
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+-- immediaite maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+
+-- result of materliazied view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ROLLBACK;
+
+-- support SUM(), COUNT() and AVG() aggregation function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i),AVG(j)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ROLLBACK;
+
+-- support COUNT(*) aggregation function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j),COUNT(*)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support having only aggregation function without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j)FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ROLLBACK;
+
+-- resolved issue: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support MIN(), MAX() aggregation functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT i, MIN(j), MAX(j)  FROM mv_base_a GROUP BY i;
+INSERT INTO mv_base_a VALUES
+  (1,11), (1,12),
+  (2,21), (2,22),
+  (3,31), (3,32),
+  (4,41), (4,42),
+  (5,51), (5,52);
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ROLLBACK;
+
+-- restriction of incremental view maintenance
+
+-- contain system column
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm01 AS SELECT i,j,xmin FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+-- contain subquery
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm03 AS SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT a.i,a.j FROM mv_base_a a,( SELECT * FROM mv_base_b) b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm05 AS SELECT i,j, (SELECT k FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+-- contain CTE
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm06 AS WITH b AS (SELECT i,k FROM mv_base_b WHERE k < 103) SELECT a.i,a.j FROM mv_base_a a,b WHERE a.i = b.i;
+-- contain view or materialized view
+CREATE VIEW b_view AS SELECT i,k FROM mv_base_b;
+CREATE MATERIALIZED VIEW b_mview AS SELECT i,k FROM mv_base_b;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm07 AS SELECT a.i,a.j FROM mv_base_a a,b_view b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm08 AS SELECT a.i,a.j FROM mv_base_a a,b_mview b WHERE a.i = b.i;
+
+DROP TABLE mv_base_b CASCADE;
+DROP TABLE mv_base_a CASCADE;
#27Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo Nagata (#26)
Re: Implementing Incremental View Maintenance

It's not mentioned below but some bugs including seg fault when
--enable-casser is enabled was also fixed in this patch.

BTW, I found a bug with min/max support in this patch and I believe
Yugo is working on it. Details:
https://github.com/sraoss/pgsql-ivm/issues/20

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

From: Yugo Nagata <nagata@sraoss.co.jp>
Subject: Re: Implementing Incremental View Maintenance
Date: Wed, 31 Jul 2019 18:08:51 +0900
Message-ID: <20190731180851.73856441d8abb494bf5e68e7@sraoss.co.jp>

Show quoted text

Hi,

Attached is the latest patch for supporting min and max aggregate functions.

When new tuples are inserted into base tables, if new values are small
(for min) or large (for max), matview just have to be updated with these
new values. Otherwise, old values just remains.

However, in the case of deletion, this is more complicated. If deleted
values exists in matview as current min or max, we have to recomputate
new min or max values from base tables for affected groups, and matview
should be updated with these recomputated values.

Also, regression tests for min/max are also added.

In addition, incremental update algorithm of avg aggregate values is a bit
improved. If an avg result in materialized views is updated incrementally
y using the old avg value, numerical errors in avg values are accumulated
and the values get wrong eventually. To prevent this, both of sum and count
values are contained in views as hidden columns and use them to calculate
new avg value instead of using old avg values.

Regards,

On Fri, 26 Jul 2019 11:35:53 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

Hi,

I've updated the wiki page of Incremental View Maintenance.

https://wiki.postgresql.org/wiki/Incremental_View_Maintenance

On Thu, 11 Jul 2019 13:28:04 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

Hi Thomas,

Thank you for your review and discussion on this patch!

2019年7月8日(月) 15:32 Thomas Munro <thomas.munro@gmail.com>:

On Fri, Jun 28, 2019 at 10:56 PM Yugo Nagata <nagata@sraoss.co.jp> wrote:

Attached is a WIP patch of IVM which supports some aggregate functions.

Hi Nagata-san and Hoshiai-san,

Thank you for working on this. I enjoyed your talk at PGCon. I've
added Kevin Grittner just in case he missed this thread; he has talked
often about implementing the counting algorithm, and he wrote the
"trigger transition tables" feature to support exactly this. While
integrating trigger transition tables with the new partition features,
we had to make a number of decisions about how that should work, and
we tried to come up with answers that would work for IMV, and I hope
we made the right choices!

Transition tables is a great feature. I am now using this in my implementation
of IVM, but the first time I used this feature was when I implemented a PoC
for extending view updatability of PostgreSQL[1]. At that time, I didn't know
that this feature is made originally aiming to support IVM.

[1] https://www.pgcon.org/2017/schedule/events/1074.en.html

I think transition tables is a good choice to implement a statement level
immediate view maintenance where materialized views are refreshed in a statement
level after trigger. However, when implementing a transaction level immediate
view maintenance where views are refreshed per transaction, or deferred view
maintenance, we can't update views in a after trigger, and we will need an
infrastructure to manage change logs of base tables. Transition tables can be
used to collect these logs, but using logical decoding of WAL is another candidate.
In any way, if these logs can be collected in a tuplestore, we might able to
convert this to "ephemeral named relation (ENR)" and use this to calculate diff
sets for views.

I am quite interested to learn how IVM interacts with SERIALIZABLE.

Nagata-san has been studying this. Nagata-san, any comment?

In SERIALIZABLE or REPEATABLE READ level, table changes occurred in other
ransactions are not visible, so views can not be maintained correctly in AFTER
triggers. If a view is defined on two tables and each table is modified in
different concurrent transactions respectively, the result of view maintenance
done in trigger functions can be incorrect due to the race condition. This is the
reason why such transactions are aborted immediately in that case in my current
implementation.

One idea to resolve this is performing view maintenance in two phases. Firstly,
views are updated using only table changes visible in this transaction. Then,
just after this transaction is committed, views have to be updated additionally
using changes happened in other transactions to keep consistency. This is a just
idea, but to implement this idea, I think, we will need keep to keep and
maintain change logs.

A couple of superficial review comments:

+            const char *aggname = get_func_name(aggref->aggfnoid);
...
+            else if (!strcmp(aggname, "sum"))

I guess you need a more robust way to detect the supported aggregates
than their name, or I guess some way for aggregates themselves to
specify that they support this and somehow supply the extra logic.
Perhaps I just waid what Greg Stark already said, except not as well.

Yes. Using name is not robust because users can make same name aggregates like
sum(text) (although I am not sure this makes sense). We can use oids instead
of names, but it would be nice to extend pg_aggregate and add new attributes
for informing that this supports IVM and for providing functions for IVM logic.

As for the question of how
to reserve a namespace for system columns that won't clash with user
columns, according to our manual the SQL standard doesn't allow $ in
identifier names, and according to my copy SQL92 "intermediate SQL"
doesn't allow identifiers that end in an underscore. I don't know
what the best answer is but we should probably decide on a something
based the standard.

Ok, so we should use "__ivm_count__" since this ends in "_" at least.

Another idea is that users specify the name of this special column when
defining materialized views with IVM support. This way can avoid the conflict
because users will specify a name which does not appear in the target list.

As for aggregates supports, it may be also possible to make it a restriction
that count(expr) must be in the target list explicitly when sum(expr) or
avg(expr) is included, instead of making hidden column like __ivm_count_sum__,
like Oracle does.

As for how to make internal columns invisible to SELECT *, previously
there have been discussions about doing that using a new flag in
pg_attribute:

/messages/by-id/CAEepm=3ZHh=p0nEEnVbs1Dig_UShPzHUcMNAqvDQUgYgcDo-pA@mail.gmail.com

I agree implementing this feature in PostgreSQL since there are at least a few
use cases, IVM and temporal database.

+                            "WITH t AS ("
+                            "  SELECT diff.__ivm_count__,
(diff.__ivm_count__ = mv.__ivm_count__) AS for_dlt, mv.ctid"
+                            ", %s"
+                            "  FROM %s AS mv, %s AS diff WHERE (%s) =
(%s)"
+                            "), updt AS ("
+                            "  UPDATE %s AS mv SET __ivm_count__ =
mv.__ivm_count__ - t.__ivm_count__"
+                            ", %s "
+                            "  FROM t WHERE mv.ctid = t.ctid AND NOT
for_dlt"
+                            ") DELETE FROM %s AS mv USING t WHERE
mv.ctid = t.ctid AND for_dlt;",

I fully understand that this is POC code, but I am curious about one
thing. These queries that are executed by apply_delta() would need to
be converted to C, or at least used reusable plans, right? Hmm,
creating and dropping temporary tables every time is a clue that the
ultimate form of this should be tuplestores and C code, I think,
right?

I used SPI just because REFRESH CONCURRENTLY uses this, but, as you said,
it is inefficient to create/drop temp tables and perform parse/plan every times.
It seems to be enough to perform this once when creating materialized views or
at the first maintenance time.

Best regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo Nagata <nagata@sraoss.co.jp>

#28Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Tatsuo Ishii (#27)
Re: Implementing Incremental View Maintenance

On 2019-Aug-06, Tatsuo Ishii wrote:

It's not mentioned below but some bugs including seg fault when
--enable-casser is enabled was also fixed in this patch.

BTW, I found a bug with min/max support in this patch and I believe
Yugo is working on it. Details:
https://github.com/sraoss/pgsql-ivm/issues/20

So is he posting an updated patch soon?

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#29Paul Draper
paulddraper@gmail.com
In reply to: Alvaro Herrera (#28)
Re: Implementing Incremental View Maintenance

As I understand it, the current patch performs immediate IVM using AFTER
STATEMENT trigger transition tables.

However, multiple tables can be modified *before* AFTER STATEMENT triggers
are fired.

CREATE TABLE example1 (a int);
CREATE TABLE example2 (a int);

CREATE INCREMENTAL MATERIALIZED VIEW mv AS
SELECT example1.a, example2.a
FROM example1 JOIN example2 ON a;

WITH
insert1 AS (INSERT INTO example1 VALUES (1)),
insert2 AS (INSERT INTO example2 VALUES (1))
SELECT NULL;

Changes to example1 are visible in an AFTER STATEMENT trigger on example2,
and vice versa. Would this not result in the (1, 1) tuple being
"double-counted"?

IVM needs to either:

(1) Evaluate deltas "serially' (e.g. EACH ROW triggers)

(2) Have simultaneous access to multiple deltas:
delta_mv = example1 x delta_example2 + example2 x delta_example1 -
delta_example1 x delta_example2

This latter method is the "logged" approach that has been discussed for
deferred evaluation.

tl;dr It seems that AFTER STATEMENT triggers required a deferred-like
implementation anyway.

#30Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Alvaro Herrera (#28)
Re: Implementing Incremental View Maintenance

On 2019-Aug-06, Tatsuo Ishii wrote:

It's not mentioned below but some bugs including seg fault when
--enable-casser is enabled was also fixed in this patch.

BTW, I found a bug with min/max support in this patch and I believe
Yugo is working on it. Details:
https://github.com/sraoss/pgsql-ivm/issues/20

So is he posting an updated patch soon?

I think he is going to post an updated patch by the end of this month.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#31Yugo Nagata
nagata@sraoss.co.jp
In reply to: Paul Draper (#29)
Re: Implementing Incremental View Maintenance

Hi Paul,

Thank you for your suggestion.

On Sun, 15 Sep 2019 11:52:22 -0600
Paul Draper <paulddraper@gmail.com> wrote:

As I understand it, the current patch performs immediate IVM using AFTER
STATEMENT trigger transition tables.

However, multiple tables can be modified *before* AFTER STATEMENT triggers
are fired.

CREATE TABLE example1 (a int);
CREATE TABLE example2 (a int);

CREATE INCREMENTAL MATERIALIZED VIEW mv AS
SELECT example1.a, example2.a
FROM example1 JOIN example2 ON a;

WITH
insert1 AS (INSERT INTO example1 VALUES (1)),
insert2 AS (INSERT INTO example2 VALUES (1))
SELECT NULL;

Changes to example1 are visible in an AFTER STATEMENT trigger on example2,
and vice versa. Would this not result in the (1, 1) tuple being
"double-counted"?

IVM needs to either:

(1) Evaluate deltas "serially' (e.g. EACH ROW triggers)

(2) Have simultaneous access to multiple deltas:
delta_mv = example1 x delta_example2 + example2 x delta_example1 -
delta_example1 x delta_example2

This latter method is the "logged" approach that has been discussed for
deferred evaluation.

tl;dr It seems that AFTER STATEMENT triggers required a deferred-like
implementation anyway.

You are right, the latest patch doesn't support the situation where
multiple tables are modified in a query. I noticed this when working
on self-join, which also virtually need to handle multiple table
modification.

I am now working on this issue and the next patch will enable to handle
this situation. I plan to submit the patch during this month. Roughly
speaking, in the new implementation, AFTER STATEMENT triggers are used to
collect information of modified table and its changes (= transition tables),
and then the only last trigger updates the view. This will avoid the
double-counting. I think this implementation also would be a base of
deferred approach implementation in future where "logs" are used instead
of transition tables.

Regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

#32Paul Draper
paulddraper@gmail.com
In reply to: Yugo Nagata (#31)
Re: Implementing Incremental View Maintenance

Have you had any thoughts for more than two joined tables?

Either there needs to be an quadratic number of joins, or intermediate join
results need to be stored and reused.

On Tue, Sep 17, 2019 at 8:50 AM Yugo Nagata <nagata@sraoss.co.jp> wrote:

Show quoted text

Hi Paul,

Thank you for your suggestion.

On Sun, 15 Sep 2019 11:52:22 -0600
Paul Draper <paulddraper@gmail.com> wrote:

As I understand it, the current patch performs immediate IVM using AFTER
STATEMENT trigger transition tables.

However, multiple tables can be modified *before* AFTER STATEMENT

triggers

are fired.

CREATE TABLE example1 (a int);
CREATE TABLE example2 (a int);

CREATE INCREMENTAL MATERIALIZED VIEW mv AS
SELECT example1.a, example2.a
FROM example1 JOIN example2 ON a;

WITH
insert1 AS (INSERT INTO example1 VALUES (1)),
insert2 AS (INSERT INTO example2 VALUES (1))
SELECT NULL;

Changes to example1 are visible in an AFTER STATEMENT trigger on

example2,

and vice versa. Would this not result in the (1, 1) tuple being
"double-counted"?

IVM needs to either:

(1) Evaluate deltas "serially' (e.g. EACH ROW triggers)

(2) Have simultaneous access to multiple deltas:
delta_mv = example1 x delta_example2 + example2 x delta_example1 -
delta_example1 x delta_example2

This latter method is the "logged" approach that has been discussed for
deferred evaluation.

tl;dr It seems that AFTER STATEMENT triggers required a deferred-like
implementation anyway.

You are right, the latest patch doesn't support the situation where
multiple tables are modified in a query. I noticed this when working
on self-join, which also virtually need to handle multiple table
modification.

I am now working on this issue and the next patch will enable to handle
this situation. I plan to submit the patch during this month. Roughly
speaking, in the new implementation, AFTER STATEMENT triggers are used to
collect information of modified table and its changes (= transition
tables),
and then the only last trigger updates the view. This will avoid the
double-counting. I think this implementation also would be a base of
deferred approach implementation in future where "logs" are used instead
of transition tables.

Regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

#33Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Paul Draper (#32)
Re: Implementing Incremental View Maintenance

Have you had any thoughts for more than two joined tables?

I am not sure what you are asking here but if you are asking if IVM
supports two or more tables involved in a join, we already support it:

DROP MATERIALIZED VIEW mv1;
DROP MATERIALIZED VIEW
DROP TABLE t1;
DROP TABLE
DROP TABLE t2;
DROP TABLE
DROP TABLE t3;
DROP TABLE
CREATE TABLE t1(i int, j int);
CREATE TABLE
CREATE TABLE t2(k int, l int);
CREATE TABLE
CREATE TABLE t3(m int, n int);
CREATE TABLE
INSERT INTO t1 VALUES(1,10),(2,11);
INSERT 0 2
INSERT INTO t2 VALUES(1,20),(2,21);
INSERT 0 2
INSERT INTO t3 VALUES(1,30),(2,31);
INSERT 0 2
CREATE INCREMENTAL MATERIALIZED VIEW mv1 AS SELECT * FROM t1 INNER JOIN t2 ON t1.i = t2.k INNER JOIN t3 ON t1.i = t3.m;
SELECT 2
SELECT * FROM mv1;
i | j | k | l | m | n
---+----+---+----+---+----
1 | 10 | 1 | 20 | 1 | 30
2 | 11 | 2 | 21 | 2 | 31
(2 rows)

UPDATE t1 SET j = 15 WHERE i = 1;
UPDATE 1
SELECT * FROM mv1;
i | j | k | l | m | n
---+----+---+----+---+----
2 | 11 | 2 | 21 | 2 | 31
1 | 15 | 1 | 20 | 1 | 30
(2 rows)

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

Show quoted text

Either there needs to be an quadratic number of joins, or intermediate join
results need to be stored and reused.

On Tue, Sep 17, 2019 at 8:50 AM Yugo Nagata <nagata@sraoss.co.jp> wrote:

Hi Paul,

Thank you for your suggestion.

On Sun, 15 Sep 2019 11:52:22 -0600
Paul Draper <paulddraper@gmail.com> wrote:

As I understand it, the current patch performs immediate IVM using AFTER
STATEMENT trigger transition tables.

However, multiple tables can be modified *before* AFTER STATEMENT

triggers

are fired.

CREATE TABLE example1 (a int);
CREATE TABLE example2 (a int);

CREATE INCREMENTAL MATERIALIZED VIEW mv AS
SELECT example1.a, example2.a
FROM example1 JOIN example2 ON a;

WITH
insert1 AS (INSERT INTO example1 VALUES (1)),
insert2 AS (INSERT INTO example2 VALUES (1))
SELECT NULL;

Changes to example1 are visible in an AFTER STATEMENT trigger on

example2,

and vice versa. Would this not result in the (1, 1) tuple being
"double-counted"?

IVM needs to either:

(1) Evaluate deltas "serially' (e.g. EACH ROW triggers)

(2) Have simultaneous access to multiple deltas:
delta_mv = example1 x delta_example2 + example2 x delta_example1 -
delta_example1 x delta_example2

This latter method is the "logged" approach that has been discussed for
deferred evaluation.

tl;dr It seems that AFTER STATEMENT triggers required a deferred-like
implementation anyway.

You are right, the latest patch doesn't support the situation where
multiple tables are modified in a query. I noticed this when working
on self-join, which also virtually need to handle multiple table
modification.

I am now working on this issue and the next patch will enable to handle
this situation. I plan to submit the patch during this month. Roughly
speaking, in the new implementation, AFTER STATEMENT triggers are used to
collect information of modified table and its changes (= transition
tables),
and then the only last trigger updates the view. This will avoid the
double-counting. I think this implementation also would be a base of
deferred approach implementation in future where "logs" are used instead
of transition tables.

Regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

#34Yugo Nagata
nagata@sraoss.co.jp
In reply to: Paul Draper (#32)
Re: Implementing Incremental View Maintenance

On Tue, 17 Sep 2019 12:03:20 -0600
Paul Draper <paulddraper@gmail.com> wrote:

Have you had any thoughts for more than two joined tables?

Either there needs to be an quadratic number of joins, or intermediate join
results need to be stored and reused.

I don't think that we need to store intermediate join results.

Suppose that we have a view V joining table R,S, and new tuples are inserted
to each table, dR,dS, and dT respectively.

V = R*S*T
R_new = R + dR
S_new = S + dS
T_new = T + dT

In this situation, we can calculate the new view state as bellow.

V_new
= R_new * S_new * T_new
= (R + dR) * (S + dS) * (T + dT)
= R*S*T + dR*(S + dS)*(T + dT) + R*dS*(T + dT) + R*S*dT
= V + dR*(S + dS)*(T + dT) + R*dS*(T + dT) + R*S*dT

Although the number of terms is 2^3(=8), if we can use both of pre-update
state (eg. R) and post-update state (eg. R+dR), we only need only three joins.
Actually, post-update state is available in AFTER trigger, and pre-update state
can be calculated by using delta tables (transition tables) and cmin/xmin system
columns (or snapshot). This is the approach my implementation adopts.

On Tue, Sep 17, 2019 at 8:50 AM Yugo Nagata <nagata@sraoss.co.jp> wrote:

Hi Paul,

Thank you for your suggestion.

On Sun, 15 Sep 2019 11:52:22 -0600
Paul Draper <paulddraper@gmail.com> wrote:

As I understand it, the current patch performs immediate IVM using AFTER
STATEMENT trigger transition tables.

However, multiple tables can be modified *before* AFTER STATEMENT

triggers

are fired.

CREATE TABLE example1 (a int);
CREATE TABLE example2 (a int);

CREATE INCREMENTAL MATERIALIZED VIEW mv AS
SELECT example1.a, example2.a
FROM example1 JOIN example2 ON a;

WITH
insert1 AS (INSERT INTO example1 VALUES (1)),
insert2 AS (INSERT INTO example2 VALUES (1))
SELECT NULL;

Changes to example1 are visible in an AFTER STATEMENT trigger on

example2,

and vice versa. Would this not result in the (1, 1) tuple being
"double-counted"?

IVM needs to either:

(1) Evaluate deltas "serially' (e.g. EACH ROW triggers)

(2) Have simultaneous access to multiple deltas:
delta_mv = example1 x delta_example2 + example2 x delta_example1 -
delta_example1 x delta_example2

This latter method is the "logged" approach that has been discussed for
deferred evaluation.

tl;dr It seems that AFTER STATEMENT triggers required a deferred-like
implementation anyway.

You are right, the latest patch doesn't support the situation where
multiple tables are modified in a query. I noticed this when working
on self-join, which also virtually need to handle multiple table
modification.

I am now working on this issue and the next patch will enable to handle
this situation. I plan to submit the patch during this month. Roughly
speaking, in the new implementation, AFTER STATEMENT triggers are used to
collect information of modified table and its changes (= transition
tables),
and then the only last trigger updates the view. This will avoid the
double-counting. I think this implementation also would be a base of
deferred approach implementation in future where "logs" are used instead
of transition tables.

Regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo Nagata <nagata@sraoss.co.jp>

#35Yugo Nagata
nagata@sraoss.co.jp
In reply to: Tatsuo Ishii (#30)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

Attached is the latest patch for supporting self-join views. This also
including the following fix mentioned by Tatsuo Ishii.

On 2019-Aug-06, Tatsuo Ishii wrote:

It's not mentioned below but some bugs including seg fault when
--enable-casser is enabled was also fixed in this patch.

BTW, I found a bug with min/max support in this patch and I believe
Yugo is working on it. Details:
https://github.com/sraoss/pgsql-ivm/issues/20

This patch allows to support self-join views, simultaneous updates of more
than one base tables, and also multiple updates of the same base table.
I first tried to support just self-join, but I found that this is essentially
same as to support simultaneous table updates, so I decided to support them in
the same commit. I think this will be a base for implementing
Deferred-maintenance in future.

In the new implementation, AFTER triggers are used to collecting tuplestores
containing transition table contents. When multiple tables are changed,
multiple AFTER triggers are invoked, then the final AFTER trigger performs
actual update of the matview. In addition AFTER trigger, also BEFORE trigger
is used to handle global information for view maintenance.

For example, suppose that we have a view V joining table R,S, and new tuples are
inserted to each table, dR,dS, and dT respectively.

V = R*S*T
R_new = R + dR
S_new = S + dS
T_new = T + dT

In this situation, we can calculate the new view state as bellow.

V_new
= R_new * S_new * T_new
= (R + dR) * (S + dS) * (T + dT)
= R*S*T + dR*(S + dS)*(T + dT) + R*dS*(T + dT) + R*S*dT
= V + dR*(S + dS)*(T + dT) + R*dS*(T + dT) + R*S*dT
= V + (dR *S_new*T_new) + (R*dS*T_new) + (R*S*dT)

To calculate view deltas, we need both pre-state (R,S, and T) and post-state
(R_new, S_new, and T_new) of base tables.

Post-update states are available in AFTER trigger, and we calculate pre-update
states by filtering inserted tuples using cmin/xmin system columns, and appendding
deleted tuples which are contained in a old transition table.

In the original core implementation, tuplestores of transition tables were
freed for each query depth. However, we want to prolong their life plan because
we have to preserve these for a whole query assuming some base tables are changed
in other trigger functions, so I added a hack to trigger.c.

Regression tests are also added for self join view, multiple change on the same
table, simultaneous two table changes, and foreign reference constrains.

Here are behavior examples:

1. Table definition
- t: for self-join
- r,s: for 2-ways join

CREATE TABLE r (i int, v int);
CREATE TABLE
CREATE TABLE s (i int, v int);
CREATE TABLE
CREATE TABLE t (i int, v int);
CREATE TABLE

2. Initial data

INSERT INTO r VALUES (1, 10), (2, 20), (3, 30);
INSERT 0 3
INSERT INTO s VALUES (1, 100), (2, 200), (3, 300);
INSERT 0 3
INSERT INTO t VALUES (1, 10), (2, 20), (3, 30);
INSERT 0 3

3. View definition

3.1. self-join(mv_self, v_slef)

CREATE INCREMENTAL MATERIALIZED VIEW mv_self(v1, v2) AS
SELECT t1.v, t2.v FROM t t1 JOIN t t2 ON t1.i = t2.i;
SELECT 3
CREATE VIEW v_self(v1, v2) AS
SELECT t1.v, t2.v FROM t t1 JOIN t t2 ON t1.i = t2.i;
CREATE VIEW

3.2. 2-ways join (mv, v)

CREATE INCREMENTAL MATERIALIZED VIEW mv(v1, v2) AS
SELECT r.v, s.v FROM r JOIN s USING(i);
SELECT 3
CREATE VIEW v(v1, v2) AS
SELECT r.v, s.v FROM r JOIN s USING(i);
CREATE VIEW

3.3 Initial contents

SELECT * FROM mv_self ORDER BY v1;
v1 | v2
----+----
10 | 10
20 | 20
30 | 30
(3 rows)

SELECT * FROM mv ORDER BY v1;
v1 | v2
----+-----
10 | 100
20 | 200
30 | 300
(3 rows)

4. Update a base table for the self-join view

INSERT INTO t VALUES (4,40);
INSERT 0 1
DELETE FROM t WHERE i = 1;
DELETE 1
UPDATE t SET v = v*10 WHERE i=2;
UPDATE 1

4.1. Results
- Comparison with the normal view

SELECT * FROM mv_self ORDER BY v1;
v1 | v2
-----+-----
30 | 30
40 | 40
200 | 200
(3 rows)

SELECT * FROM v_self ORDER BY v1;
v1 | v2
-----+-----
30 | 30
40 | 40
200 | 200
(3 rows)

5. pdate a base table for the 2-way join view

WITH
ins_r AS (INSERT INTO r VALUES (1,11) RETURNING 1),
ins_r2 AS (INSERT INTO r VALUES (3,33) RETURNING 1),
ins_s AS (INSERT INTO s VALUES (2,222) RETURNING 1),
upd_r AS (UPDATE r SET v = v + 1000 WHERE i = 2 RETURNING 1),
dlt_s AS (DELETE FROM s WHERE i = 3 RETURNING 1)
SELECT NULL;
?column?
----------

(1 row)

5.1. Results
- Comparison with the normal view

SELECT * FROM mv ORDER BY v1;
v1 | v2
------+-----
10 | 100
11 | 100
1020 | 200
1020 | 222
(4 rows)

SELECT * FROM v ORDER BY v1;
v1 | v2
------+-----
10 | 100
11 | 100
1020 | 200
1020 | 222
(4 rows)

========

Best Regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

WIP_immediate_IVM_v6.patchtext/x-diff; name=WIP_immediate_IVM_v6.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 5e71a2e865..a288bddcda 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1952,6 +1952,13 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       <entry>True if table or index is a partition</entry>
      </row>
 
+     <row>
+      <entry><structfield>relisivm</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if materialized view enables incremental view maintenance</entry>
+     </row>
+
      <row>
       <entry><structfield>relrewrite</structfield></entry>
       <entry><type>oid</type></entry>
diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml
index ec8847ed40..a23366a342 100644
--- a/doc/src/sgml/ref/create_materialized_view.sgml
+++ b/doc/src/sgml/ref/create_materialized_view.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
+CREATE [ INCREMENTAL ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
     [ (<replaceable>column_name</replaceable> [, ...] ) ]
     [ USING <replaceable class="parameter">method</replaceable> ]
     [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
@@ -54,6 +54,16 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
   <title>Parameters</title>
 
   <variablelist>
+   <varlistentry>
+    <term><literal>INCREMENTAL</literal></term>
+    <listitem>
+     <para>
+      If specified, a materialized view enables incremental view maintenance.
+      You can replace only the contents of a materialized view, which based rows are changed.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>IF NOT EXISTS</literal></term>
     <listitem>
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 9162286c98..c5551f59e2 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_enum.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/matview.h"
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "executor/spi.h"
@@ -2661,6 +2662,7 @@ AbortTransaction(void)
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
 	AtAbort_Twophase();
+	AtAbort_IVM();
 
 	/*
 	 * Advertise the fact that we aborted in pg_xact (assuming that we got as
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index b7bcdd9d0f..cb7e8be84a 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -890,6 +890,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
+	values[Anum_pg_class_relisivm - 1] = BoolGetDatum(rd_rel->relisivm);
 	if (relacl != (Datum) 0)
 		values[Anum_pg_class_relacl - 1] = relacl;
 	else
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 098732cc4a..5aadbf96a8 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -955,6 +955,7 @@ index_create(Relation heapRelation,
 	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
 	indexRelation->rd_rel->relam = accessMethodObjectId;
 	indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid);
+	indexRelation->rd_rel->relisivm = false;
 
 	/*
 	 * store index's pg_class entry
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index b7d220699f..6f2639130c 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -51,6 +51,19 @@
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
 
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
+#include "commands/trigger.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
+#include "parser/parse_func.h"
+#include "parser/parse_type.h"
+#include "nodes/print.h"
+#include "nodes/primnodes.h"
+#include "optimizer/optimizer.h"
+#include "commands/defrem.h"
+#include "utils/syscache.h"
+
 
 typedef struct
 {
@@ -74,6 +87,9 @@ static bool intorel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void intorel_shutdown(DestReceiver *self);
 static void intorel_destroy(DestReceiver *self);
 
+static void CreateIvmTrigger(Oid relOid, Oid viewOid, char *matviewname, int16 type, int16 timing);
+static void CreateIvmTriggersOnBaseTables(Query *qry, Node *jtnode, Oid matviewOid, char* matviewname, Bitmapset **relid_map);
+static void check_ivm_restriction_walker(Node *node);
 
 /*
  * create_ctas_internal
@@ -109,6 +125,8 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	create->oncommit = into->onCommit;
 	create->tablespacename = into->tableSpaceName;
 	create->if_not_exists = false;
+	/* Using Materialized view only */
+	create->ivm = into->ivm;
 	create->accessMethod = into->accessMethod;
 
 	/*
@@ -239,6 +257,7 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 	List	   *rewritten;
 	PlannedStmt *plan;
 	QueryDesc  *queryDesc;
+	Query	   *copied_query;
 
 	if (stmt->if_not_exists)
 	{
@@ -256,6 +275,9 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		}
 	}
 
+	if (is_matview && into->ivm)
+		check_ivm_restriction_walker((Node *) query);
+
 	/*
 	 * Create the tuple receiver object and insert info it will need
 	 */
@@ -319,7 +341,148 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		 * and is executed repeatedly.  (See also the same hack in EXPLAIN and
 		 * PREPARE.)
 		 */
-		rewritten = QueryRewrite(copyObject(query));
+
+		copied_query = copyObject(query);
+		if (is_matview && into->ivm)
+		{
+			TargetEntry *tle;
+			Node *node;
+			ParseState *pstate = make_parsestate(NULL);
+			FuncCall *fn;
+
+			pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+			/* group keys must be in targetlist */
+			if (copied_query->groupClause)
+			{
+				ListCell *lc;
+				foreach(lc, copied_query->groupClause)
+				{
+					SortGroupClause *scl = (SortGroupClause *) lfirst(lc);
+					TargetEntry *tle = get_sortgroupclause_tle(scl, copied_query->targetList);
+
+					if (tle->resjunk)
+						elog(ERROR, "GROUP BY expression must appear in select list for incremental materialized views");
+				}
+			}
+			else if (!copied_query->hasAggs)
+				copied_query->groupClause = transformDistinctClause(NULL, &copied_query->targetList, copied_query->sortClause, false);
+
+			if (copied_query->hasAggs)
+			{
+				ListCell *lc;
+				List *agg_counts = NIL;
+				AttrNumber next_resno = list_length(copied_query->targetList) + 1;
+				Const	*dmy_arg = makeConst(INT4OID,
+											 -1,
+											 InvalidOid,
+											 sizeof(int32),
+											 Int32GetDatum(1),
+											 false,
+											 true); /* pass by value */
+
+				foreach(lc, copied_query->targetList)
+				{
+					TargetEntry *tle = (TargetEntry *) lfirst(lc);
+					TargetEntry *tle_count;
+					char *resname = (into->colNames == NIL ? tle->resname : strVal(list_nth(into->colNames, tle->resno-1)));
+
+
+					if (IsA(tle->expr, Aggref))
+					{
+						Aggref *aggref = (Aggref *) tle->expr;
+						const char *aggname = get_func_name(aggref->aggfnoid);
+
+						/* XXX: need some generalization
+						 *
+						 * Specifically, Using func names is not robust.  We can use oids instead
+						 * of names, but it would be nice to add some information to pg_aggregate.
+						 */
+						if (strcmp(aggname, "sum") !=0
+							&& strcmp(aggname, "count") != 0
+							&& strcmp(aggname, "avg") != 0
+							&& strcmp(aggname, "min") != 0
+							&& strcmp(aggname, "max") != 0
+						)
+							elog(ERROR, "aggregate function %s is not supported", aggname);
+
+						/*
+						 * For aggregate functions except to count, add count func with the same arg parameters.
+						 * Also, add sum func for agv.
+						 *
+						 * XXX: If there are same expressions explicitly in the target list, we can use this instead
+						 * of adding new duplicated one.
+						 */
+						if (strcmp(aggname, "count") != 0)
+						{
+							fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+
+							/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+							node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+							((Aggref *)node)->args = aggref->args;
+
+							tle_count = makeTargetEntry((Expr *) node,
+														next_resno,
+														pstrdup(makeObjectName("__ivm_count",resname, "_")),
+														false);
+							agg_counts = lappend(agg_counts, tle_count);
+							next_resno++;
+						}
+						if (strcmp(aggname, "avg") == 0)
+						{
+							List *dmy_args = NIL;
+							ListCell *lc;
+							foreach(lc, aggref->aggargtypes)
+							{
+								Oid		typeid = lfirst_oid(lc);
+								Type 	type = typeidType(typeid);
+
+								Const *con = makeConst(typeid,
+													   -1,
+													   typeTypeCollation(type),
+													   typeLen(type),
+													   (Datum) 0,
+													   true,
+													   typeByVal(type));
+								dmy_args = lappend(dmy_args, con);
+								ReleaseSysCache(type);
+
+							}
+							fn = makeFuncCall(list_make1(makeString("sum")), NIL, -1);
+
+							/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+							node = ParseFuncOrColumn(pstate, fn->funcname, dmy_args, NULL, fn, false, -1);
+							((Aggref *)node)->args = aggref->args;
+
+							tle_count = makeTargetEntry((Expr *) node,
+														next_resno,
+														pstrdup(makeObjectName("__ivm_sum",resname, "_")),
+														false);
+							agg_counts = lappend(agg_counts, tle_count);
+							next_resno++;
+						}
+
+					}
+				}
+				copied_query->targetList = list_concat(copied_query->targetList, agg_counts);
+
+			}
+
+			/* Add count(*) for counting algorithm */
+			fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+			fn->agg_star = true;
+
+			node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+			tle = makeTargetEntry((Expr *) node,
+								  	list_length(copied_query->targetList) + 1,
+								  	pstrdup("__ivm_count__"),
+								  	false);
+			copied_query->targetList = lappend(copied_query->targetList, tle);
+			copied_query->hasAggs = true;
+		}
+
+		rewritten = QueryRewrite(copied_query);
 
 		/* SELECT should never rewrite to more or less than one SELECT query */
 		if (list_length(rewritten) != 1)
@@ -378,11 +541,77 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 
 		/* Restore userid and security context */
 		SetUserIdAndSecContext(save_userid, save_sec_context);
+
+
+		if (into->ivm)
+		{
+
+			Oid matviewOid = address.objectId;
+			Relation matviewRel = table_open(matviewOid, NoLock);
+			char	*matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+													 RelationGetRelationName(matviewRel));
+			Bitmapset  *relid_map = NULL;
+
+			copied_query = copyObject(query);
+			AcquireRewriteLocks(copied_query, true, false);
+
+			CreateIvmTriggersOnBaseTables(copied_query, (Node *)copied_query->jointree, matviewOid, matviewname, &relid_map);
+
+			table_close(matviewRel, NoLock);
+
+			bms_free(relid_map);
+		}
 	}
 
 	return address;
 }
 
+static void CreateIvmTriggersOnBaseTables(Query *qry, Node *jtnode, Oid matviewOid, char* matviewname, Bitmapset **relid_map)
+{
+
+	if (jtnode == NULL)
+		return;
+	if (IsA(jtnode, RangeTblRef))
+	{
+		int			rti = ((RangeTblRef *) jtnode)->rtindex;
+		RangeTblEntry *rte = rt_fetch(rti, qry->rtable);
+
+		if (rte->rtekind == RTE_RELATION)
+		{
+			if (!bms_is_member(rte->relid, *relid_map))
+			{
+				CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_BEFORE);
+				CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_BEFORE);
+				CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_BEFORE);
+				CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_AFTER);
+				CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_AFTER);
+				CreateIvmTrigger(rte->relid, matviewOid, matviewname, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_AFTER);
+
+				*relid_map = bms_add_member(*relid_map, rte->relid);
+			}
+		}
+		else
+			elog(ERROR, "unsupported RTE kind: %d", (int) rte->rtekind);
+	}
+	else if (IsA(jtnode, FromExpr))
+	{
+		FromExpr   *f = (FromExpr *) jtnode;
+		ListCell   *l;
+
+		foreach(l, f->fromlist)
+			CreateIvmTriggersOnBaseTables(qry, lfirst(l), matviewOid, matviewname, relid_map);
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+
+		CreateIvmTriggersOnBaseTables(qry, j->larg, matviewOid, matviewname, relid_map);
+		CreateIvmTriggersOnBaseTables(qry, j->rarg, matviewOid, matviewname, relid_map);
+	}
+	else
+		elog(ERROR, "unrecognized node type: %d", (int) nodeTag(jtnode));
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -547,6 +776,11 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	if (is_matview && !into->skipData)
 		SetMatViewPopulatedState(intoRelationDesc, true);
 
+	/*
+	 * Mark relisivm field, if it's a matview and into->ivm is true.
+	 */
+	if (is_matview && into->ivm)
+		SetMatViewIVMState(intoRelationDesc, true);
 	/*
 	 * Fill private fields of myState for use by later routines
 	 */
@@ -619,3 +853,237 @@ intorel_destroy(DestReceiver *self)
 {
 	pfree(self);
 }
+
+
+static void
+CreateIvmTrigger(Oid relOid, Oid viewOid, char *matviewname, int16 type, int16 timing)
+{
+	CreateTrigStmt *ivm_trigger;
+	List *transitionRels = NIL;
+	ObjectAddress address, refaddr;
+
+	Assert(timing == TRIGGER_TYPE_BEFORE || timing == TRIGGER_TYPE_AFTER);
+
+	refaddr.classId = RelationRelationId;
+	refaddr.objectId = viewOid;
+	refaddr.objectSubId = 0;
+
+	ivm_trigger = makeNode(CreateTrigStmt);
+	ivm_trigger->relation = NULL;
+	ivm_trigger->row = false;
+
+	ivm_trigger->timing = timing;
+	ivm_trigger->events = type;
+
+	switch (type)
+	{
+		case TRIGGER_TYPE_INSERT:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_ins_before" : "IVM_trigger_ins_after");
+			break;
+		case TRIGGER_TYPE_DELETE:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_del_before" : "IVM_trigger_del_after");
+			break;
+		case TRIGGER_TYPE_UPDATE:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_upd_before" : "IVM_trigger_upd_after");
+			break;
+		default:
+			elog(ERROR, "unsupported trigger type");
+	}
+
+	if (timing == TRIGGER_TYPE_AFTER)
+	{
+		if (type == TRIGGER_TYPE_INSERT || type == TRIGGER_TYPE_UPDATE)
+		{
+			TriggerTransition *n = makeNode(TriggerTransition);
+			n->name = "ivm_newtable";
+			n->isNew = true;
+			n->isTable = true;
+
+			transitionRels = lappend(transitionRels, n);
+		}
+		if (type == TRIGGER_TYPE_DELETE || type == TRIGGER_TYPE_UPDATE)
+		{
+			TriggerTransition *n = makeNode(TriggerTransition);
+			n->name = "ivm_oldtable";
+			n->isNew = false;
+			n->isTable = true;
+
+			transitionRels = lappend(transitionRels, n);
+		}
+	}
+
+	ivm_trigger->funcname =
+		(timing == TRIGGER_TYPE_BEFORE ? SystemFuncName("IVM_immediate_before") : SystemFuncName("IVM_immediate_maintenance"));
+
+	ivm_trigger->columns = NIL;
+	ivm_trigger->transitionRels = transitionRels;
+	ivm_trigger->whenClause = NULL;
+	ivm_trigger->isconstraint = false;
+	ivm_trigger->deferrable = false;
+	ivm_trigger->initdeferred = false;
+	ivm_trigger->constrrel = NULL;
+	ivm_trigger->args = list_make1(makeString(matviewname));
+
+	address = CreateTrigger(ivm_trigger, NULL, relOid, InvalidOid, InvalidOid,
+						 InvalidOid, InvalidOid, InvalidOid, NULL, true, false);
+
+	recordDependencyOn(&address, &refaddr, DEPENDENCY_AUTO);
+
+	/* Make changes-so-far visible */
+	CommandCounterIncrement();
+}
+
+/*
+ * check_ivm_restriction_walker --- look for specify nodes in the query tree
+ */
+static void
+check_ivm_restriction_walker(Node *node)
+{
+	/* This can recurse, so check for excessive recursion */
+	check_stack_depth();
+
+	if (node == NULL)
+		return;
+	/*
+	 * We currently don't support Sub-Query.
+	 */
+	if (IsA(node, SubPlan) || IsA(node, SubLink))
+		ereport(ERROR, (errmsg("subquery is not supported with IVM")));
+
+	switch (nodeTag(node))
+	{
+		case T_Query:
+			{
+				Query *qry = (Query *)node;
+				ListCell   *lc;
+				/* if contained CTE, return error */
+				if (qry->cteList != NIL)
+					ereport(ERROR, (errmsg("CTE is not supported with IVM")));
+
+				/* if contained VIEW or subquery into RTE, return error */
+				foreach(lc, qry->rtable)
+				{
+					RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+					if (rte->relkind == RELKIND_VIEW ||
+							rte->relkind == RELKIND_MATVIEW)
+						ereport(ERROR, (errmsg("VIEW or MATERIALIZED VIEW is not supported with IVM")));
+					if (rte->rtekind ==  RTE_SUBQUERY)
+						ereport(ERROR, (errmsg("subquery is not supported with IVM")));
+				}
+
+				/* search in jointree */
+				check_ivm_restriction_walker((Node *) qry->jointree);
+
+				/* search in target lists */
+				foreach(lc, qry->targetList)
+				{
+					TargetEntry *tle = (TargetEntry *) lfirst(lc);
+					check_ivm_restriction_walker((Node *) tle->expr);
+				}
+
+				break;
+			}
+		case T_JoinExpr:
+			{
+				JoinExpr *joinexpr = (JoinExpr *)node;
+				if (joinexpr->jointype > JOIN_INNER)
+					ereport(ERROR, (errmsg("OUTER JOIN is not supported with IVM")));
+				/* left side */
+				check_ivm_restriction_walker((Node *) joinexpr->larg);
+				/* right side */
+				check_ivm_restriction_walker((Node *) joinexpr->rarg);
+				check_ivm_restriction_walker((Node *) joinexpr->quals);
+			}
+			break;
+		case T_FromExpr:
+			{
+				ListCell *lc;
+				FromExpr *fromexpr = (FromExpr *)node;
+				foreach(lc, fromexpr->fromlist)
+				{
+					check_ivm_restriction_walker((Node *) lfirst(lc));
+				}
+				check_ivm_restriction_walker((Node *) fromexpr->quals);
+			}
+			break;
+		case T_Var:
+			{
+				/* if system column, return error */
+				Var	*variable = (Var *) node;
+				if (variable->varattno < 0)
+					ereport(ERROR, (errmsg("system column is not supported with IVM")));
+			}
+			break;
+		case T_BoolExpr:
+			{
+				BoolExpr   *boolexpr = (BoolExpr *) node;
+				ListCell   *lc;
+
+				foreach(lc, boolexpr->args)
+				{
+					Node	   *arg = (Node *) lfirst(lc);
+					check_ivm_restriction_walker(arg);
+				}
+				break;
+			}
+		case T_NullIfExpr: /* same as OpExpr */
+		case T_DistinctExpr: /* same as OpExpr */
+		case T_OpExpr:
+			{
+				OpExpr	   *op = (OpExpr *) node;
+				ListCell   *lc;
+				foreach(lc, op->args)
+				{
+					Node	   *arg = (Node *) lfirst(lc);
+					check_ivm_restriction_walker(arg);
+				}
+				break;
+			}
+		case T_CaseExpr:
+			{
+				CaseExpr *caseexpr = (CaseExpr *) node;
+				ListCell *lc;
+				/* result for ELSE clause */
+				check_ivm_restriction_walker((Node *) caseexpr->defresult);
+				/* expr for WHEN clauses */
+				foreach(lc, caseexpr->args)
+				{
+					CaseWhen *when = (CaseWhen *) lfirst(lc);
+					Node *w_expr = (Node *) when->expr;
+					/* result for WHEN clause */
+					check_ivm_restriction_walker((Node *) when->result);
+					/* expr clause*/
+					check_ivm_restriction_walker((Node *) w_expr);
+				}
+				break;
+			}
+		case T_SubLink:
+			{
+				/* Now, not supported */
+/*
+				SubLink	*sublink = (SubLink *) node;
+				Query	*qry =(Query *) sublink->subselect;
+				if (qry != NULL && qry->jointree != NULL)
+					check_ivm_restriction_walker((Node *) qry->jointree->quals);
+*/
+				break;
+			}
+		case T_SubPlan:
+			{
+				/* Now, not supported */
+				break;
+			}
+		case T_Aggref:
+		case T_GroupingFunc:
+		case T_WindowFunc:
+		case T_FuncExpr:
+		case T_SQLValueFunction:
+		case T_Const:
+		case T_Param:
+		default:
+			/* do nothing */
+			break;
+	}
+
+	return;
+}
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 537d0e8cef..c2831590b8 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -46,6 +46,79 @@
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 
+#include "utils/regproc.h"
+#include "nodes/makefuncs.h"
+#include "parser/analyze.h"
+#include "parser/parser.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_func.h"
+#include "parser/parse_type.h"
+#include "nodes/print.h"
+#include "catalog/pg_type_d.h"
+#include "optimizer/optimizer.h"
+#include "commands/defrem.h"
+
+/*
+ * Local definitions
+ */
+
+#define MV_INIT_QUERYHASHSIZE	16
+
+/* MV query type codes */
+#define MV_PLAN_RECALC_MINMAX	1
+#define MV_PLAN_SET_MINMAX		2
+
+/*
+ * MI_QueryKey
+ *
+ * The key identifying a prepared SPI plan in our query hashtable
+ */
+typedef struct MV_QueryKey
+{
+	Oid			matview_id;	/* OID of materialized view */
+	int32		query_type;	/* query type ID, see MV_PLAN_XXX above */
+} MV_QueryKey;
+
+/*
+ * MV_QueryHashEntry
+ */
+typedef struct MV_QueryHashEntry
+{
+	MV_QueryKey key;
+	SPIPlanPtr	plan;
+} MV_QueryHashEntry;
+
+/*
+ * MV_TriggerHashEntry
+ */
+typedef struct MV_TriggerHashEntry
+{
+	Oid	matview_id;
+	int	before_trig_count;
+	int	after_trig_count;
+	TransactionId	xid;
+	CommandId	cid;
+	List *tables;
+	bool	has_old;
+	bool	has_new;
+} MV_TriggerHashEntry;
+
+/*
+ * MV_TiggerTable
+ */
+typedef struct MV_TriggerTable
+{
+	Oid	table_id;
+	List *old_tuplestores;
+	List *new_tuplestores;
+	RangeTblEntry *original_rte;
+	List *old_rtes;
+	List *new_rtes;
+	List *rte_indexes;
+} MV_TriggerTable;
+
+static HTAB *mv_query_cache = NULL;
+static HTAB *mv_trigger_info = NULL;
 
 typedef struct
 {
@@ -58,6 +131,12 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_transientrel;
 
+typedef enum
+{
+	IVM_ADD,
+	IVM_SUB
+} IvmOp;
+
 static int	matview_maintenance_depth = 0;
 
 static void transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
@@ -65,7 +144,8 @@ static bool transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void transientrel_shutdown(DestReceiver *self);
 static void transientrel_destroy(DestReceiver *self);
 static uint64 refresh_matview_datafill(DestReceiver *dest, Query *query,
-									   const char *queryString);
+						 QueryEnvironment *queryEnv,
+						 const char *queryString);
 static char *make_temptable_name_n(char *tempname, int n);
 static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 								   int save_sec_context);
@@ -74,6 +154,33 @@ static bool is_usable_unique_index(Relation indexRel);
 static void OpenMatViewIncrementalMaintenance(void);
 static void CloseMatViewIncrementalMaintenance(void);
 
+static char *get_null_condition_string(IvmOp op, char *arg1, char *arg2, char* count_col);
+static char *get_operation_string(IvmOp op, char *col, char *arg1, char *arg2,
+								  char* count_col, const char *castType);
+static Query* rewrite_query_for_preupdate_state(Query *query, List *tables, TransactionId xid, CommandId cid, ParseState *pstate);
+static Query *rewrite_query_for_counting_and_aggregation(Query *query, ParseState *pstate);
+static void calc_delta(MV_TriggerTable *table, int index, Query *query,
+						DestReceiver *dest_old, DestReceiver *dest_new, QueryEnvironment *queryEnv);
+static RangeTblEntry* union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix, QueryEnvironment *queryEnv);
+static char *make_delta_enr_name(const char *prefix, Oid relid, int count);
+static void register_delta_ENRs(ParseState *pstate, Query *query, List *tables);
+static void apply_delta(Oid matviewOid, Oid tempOid_new, Oid tempOid_old, Query *query);
+static void truncate_view_delta(Oid delta_oid);
+static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry);
+static void clean_up_IVM_temptable(Oid tempOid_old, Oid tempOid_new);
+
+static void mv_InitHashTables(void);
+static SPIPlanPtr mv_FetchPreparedPlan(MV_QueryKey *key);
+static void mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan);
+static void mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type);
+static SPIPlanPtr get_plan_for_recalc_min_max(Oid matviewOid, const char *min_max_list,
+						  const char *group_keys, int nkeys, Oid *keyTypes, bool with_group);
+static SPIPlanPtr get_plan_for_set_min_max(Oid matviewOid, char *matviewname, const char *min_max_list,
+						  int num_min_max, Oid *valTypes, bool with_group);
+
+static Query *get_matview_query(Relation matviewRel);
+
+
 /*
  * SetMatViewPopulatedState
  *		Mark a materialized view as populated, or not.
@@ -114,6 +221,46 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
 	CommandCounterIncrement();
 }
 
+/*
+ * SetMatViewIVMState
+ *		Mark a materialized view as IVM, or not.
+ *
+ * NOTE: caller must be holding an appropriate lock on the relation.
+ */
+void
+SetMatViewIVMState(Relation relation, bool newstate)
+{
+	Relation	pgrel;
+	HeapTuple	tuple;
+
+	Assert(relation->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Update relation's pg_class entry.  Crucial side-effect: other backends
+	 * (and this one too!) are sent SI message to make them rebuild relcache
+	 * entries.
+	 */
+	pgrel = table_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(relation)));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u",
+			 RelationGetRelid(relation));
+
+	((Form_pg_class) GETSTRUCT(tuple))->relisivm = newstate;
+
+	CatalogTupleUpdate(pgrel, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	table_close(pgrel, RowExclusiveLock);
+
+	/*
+	 * Advance command counter to make the updated pg_class row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+}
+
 /*
  * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command
  *
@@ -140,8 +287,6 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 {
 	Oid			matviewOid;
 	Relation	matviewRel;
-	RewriteRule *rule;
-	List	   *actions;
 	Query	   *dataQuery;
 	Oid			tableSpace;
 	Oid			relowner;
@@ -187,32 +332,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("CONCURRENTLY and WITH NO DATA options cannot be used together")));
 
-	/*
-	 * Check that everything is correct for a refresh. Problems at this point
-	 * are internal errors, so elog is sufficient.
-	 */
-	if (matviewRel->rd_rel->relhasrules == false ||
-		matviewRel->rd_rules->numLocks < 1)
-		elog(ERROR,
-			 "materialized view \"%s\" is missing rewrite information",
-			 RelationGetRelationName(matviewRel));
-
-	if (matviewRel->rd_rules->numLocks > 1)
-		elog(ERROR,
-			 "materialized view \"%s\" has too many rules",
-			 RelationGetRelationName(matviewRel));
-
-	rule = matviewRel->rd_rules->rules[0];
-	if (rule->event != CMD_SELECT || !(rule->isInstead))
-		elog(ERROR,
-			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
-			 RelationGetRelationName(matviewRel));
 
-	actions = rule->actions;
-	if (list_length(actions) != 1)
-		elog(ERROR,
-			 "the rule for materialized view \"%s\" is not a single action",
-			 RelationGetRelationName(matviewRel));
+	dataQuery = get_matview_query(matviewRel);
 
 	/*
 	 * Check that there is a unique index with no WHERE clause on one or more
@@ -247,12 +368,6 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 					 errhint("Create a unique index with no WHERE clause on one or more columns of the materialized view.")));
 	}
 
-	/*
-	 * The stored query was rewritten at the time of the MV definition, but
-	 * has not been scribbled on by the planner.
-	 */
-	dataQuery = linitial_node(Query, actions);
-
 	/*
 	 * Check for active uses of the relation in the current transaction, such
 	 * as open scans.
@@ -311,7 +426,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 
 	/* Generate the data, if wanted. */
 	if (!stmt->skipData)
-		processed = refresh_matview_datafill(dest, dataQuery, queryString);
+		processed = refresh_matview_datafill(dest, dataQuery, NULL, queryString);
 
 	/* Make the matview match the newly generated data. */
 	if (concurrent)
@@ -369,6 +484,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  */
 static uint64
 refresh_matview_datafill(DestReceiver *dest, Query *query,
+						 QueryEnvironment *queryEnv,
 						 const char *queryString)
 {
 	List	   *rewritten;
@@ -405,7 +521,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 	/* Create a QueryDesc, redirecting output to our tuple receiver */
 	queryDesc = CreateQueryDesc(plan, queryString,
 								GetActiveSnapshot(), InvalidSnapshot,
-								dest, NULL, NULL, 0);
+								dest, NULL, queryEnv ? queryEnv: NULL, 0);
 
 	/* call ExecutorStart to prepare the plan for execution */
 	ExecutorStart(queryDesc, 0);
@@ -926,3 +1042,1580 @@ CloseMatViewIncrementalMaintenance(void)
 	matview_maintenance_depth--;
 	Assert(matview_maintenance_depth >= 0);
 }
+
+/*
+ * IVM trigger function
+ */
+
+Datum
+IVM_immediate_before(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	char		*matviewname = trigdata->tg_trigger->tgargs[0];
+	List	*names = stringToQualifiedNameList(matviewname);
+	Oid	matviewOid;
+
+	MV_TriggerHashEntry *entry;
+	bool	found;
+
+	/*
+	 * Wait for concurrent transactions which update this materialized view at READ COMMITED.
+	 * This is needed to see changes commited in othre transactions. No wait and raise an error
+	 * at REPEATABLE READ or SERIALIZABLE to prevent anormal update of matviews.
+	 * XXX: dead-lock is possible here.
+	 */
+	if (!IsolationUsesXactSnapshot())
+		matviewOid = RangeVarGetRelid(makeRangeVarFromNameList(names), ExclusiveLock, true);
+	else
+		matviewOid = RangeVarGetRelidExtended(makeRangeVarFromNameList(names), ExclusiveLock, RVR_MISSING_OK | RVR_NOWAIT, NULL, NULL);
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_query_cache)
+		mv_InitHashTables();
+
+	entry = (MV_TriggerHashEntry *) hash_search(mv_trigger_info,
+											  (void *) &matviewOid,
+											  HASH_ENTER, &found);
+
+	/* On the first BEFORE to update the view, initialize trigger data */
+	if (!found)
+	{
+		Snapshot snapshot = GetActiveSnapshot();
+
+		entry->matview_id = matviewOid;
+		entry->before_trig_count = 0;
+		entry->after_trig_count = 0;
+		entry->xid = GetCurrentTransactionId();
+		entry->cid = snapshot->curcid;
+		entry->tables = NIL;
+		entry->has_old = false;
+		entry->has_new = false;
+
+	}
+
+	entry->before_trig_count++;
+
+	return PointerGetDatum(NULL);
+}
+
+Datum
+IVM_immediate_maintenance(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	Relation	rel;
+	Oid relid;
+	Oid matviewOid;
+	Query	   *query, *rewritten;
+	char*		matviewname = trigdata->tg_trigger->tgargs[0];
+	List	   *names;
+	Relation matviewRel;
+	int old_depth = matview_maintenance_depth;
+
+	Oid			tableSpace;
+	Oid			relowner;
+	Oid			OIDDelta_new = InvalidOid;
+	Oid			OIDDelta_old = InvalidOid;
+	DestReceiver *dest_new = NULL, *dest_old = NULL;
+	Oid			save_userid;
+	int			save_sec_context;
+	int			save_nestlevel;
+
+	MV_TriggerHashEntry *entry;
+	MV_TriggerTable	*table;
+	bool	found;
+	ListCell   *lc;
+	MemoryContext oldcxt;
+
+
+	QueryEnvironment *queryEnv = create_queryEnv();
+	ParseState *pstate;
+
+	/* Create a ParseState for rewriting the view definition query */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	rel = trigdata->tg_relation;
+	relid = rel->rd_id;
+
+	names = stringToQualifiedNameList(matviewname);
+	matviewOid = RangeVarGetRelid(makeRangeVarFromNameList(names), NoLock, true);
+
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_query_cache)
+		mv_InitHashTables();
+
+	entry = (MV_TriggerHashEntry *) hash_search(mv_trigger_info,
+											  (void *) &matviewOid,
+											  HASH_FIND, &found);
+
+	Assert (entry != NULL);
+
+	entry->after_trig_count++;
+
+	found = false;
+	foreach(lc, entry->tables)
+	{
+		table = (MV_TriggerTable *) lfirst(lc);
+		if (table->table_id == relid)
+		{
+			found = true;
+			break;
+		}
+	}
+	if (!found)
+	{
+		oldcxt = MemoryContextSwitchTo(CurTransactionContext);
+
+		table = (MV_TriggerTable *) palloc0(sizeof(MV_TriggerTable));
+		table->table_id = relid;
+		table->old_tuplestores = NIL;
+		table->new_tuplestores = NIL;
+		table->old_rtes = NIL;
+		table->new_rtes = NIL;
+		table->rte_indexes = NIL;
+		entry->tables = lappend(entry->tables, table);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	if (trigdata->tg_oldtable)
+	{
+		oldcxt = MemoryContextSwitchTo(CurTransactionContext);
+		table->old_tuplestores = lappend(table->old_tuplestores, trigdata->tg_oldtable);
+		entry->has_old = true;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (trigdata->tg_newtable)
+	{
+		oldcxt = MemoryContextSwitchTo(CurTransactionContext);
+		table->new_tuplestores = lappend(table->new_tuplestores, trigdata->tg_newtable);
+		entry->has_new = true;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (entry->has_new || entry->has_old)
+	{
+		CmdType cmd;
+
+		if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+			cmd = CMD_INSERT;
+		else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+			cmd = CMD_DELETE;
+		else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+			cmd = CMD_UPDATE;
+		else
+			elog(ERROR,"unsupported trigger type");
+
+		SetTransitionTablePreserved(relid, cmd);
+	}
+
+
+	/* If this is not the last AFTER trigger call, immediately exit. */
+	Assert (entry->before_trig_count >= entry->after_trig_count);
+	if (entry->before_trig_count != entry->after_trig_count)
+		return PointerGetDatum(NULL);
+
+
+	/* If this is the last AFTER trigger call, update the view. */
+
+	matviewRel = table_open(matviewOid, NoLock);
+
+	/* get view query*/
+	query = get_matview_query(matviewRel);
+
+	/* Make sure it is a materialized view. */
+	Assert(matviewRel->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Get and push the latast snapshot to see any changes which is commited during waiting in
+	 * other transactions at READ COMMITTED level.
+	 * XXX: Is this safe?
+	 */
+	PushActiveSnapshot(GetTransactionSnapshot());
+
+	/*
+	 * Check for active uses of the relation in the current transaction, such
+	 * as open scans.
+	 *
+	 * NB: We count on this to protect us against problems with refreshing the
+	 * data using TABLE_INSERT_FROZEN.
+	 */
+	 // XXX: necesarry?
+	CheckTableNotInUse(matviewRel, "REFRESH MATERIALIZED VIEW");
+
+	/* rewrite query */
+	rewritten = rewrite_query_for_preupdate_state(query, entry->tables, entry->xid, entry->cid, pstate);
+	rewritten = rewrite_query_for_counting_and_aggregation(rewritten, pstate);
+
+	relowner = matviewRel->rd_rel->relowner;
+
+	/*
+	 * Switch to the owner's userid, so that any functions are run as that
+	 * user.  Also arrange to make GUC variable changes local to this command.
+	 * Don't lock it down too tight to create a temporary table just yet.  We
+	 * will switch modes when we are about to execute user code.
+	 */
+	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+	save_nestlevel = NewGUCNestLevel();
+
+	/* Create temporary tables to store view deltas */
+	tableSpace = GetDefaultTablespace(RELPERSISTENCE_TEMP, false);
+	if (entry->has_old)
+	{
+		OIDDelta_old = make_new_heap(matviewOid, tableSpace, RELPERSISTENCE_TEMP,
+									 ExclusiveLock);
+		LockRelationOid(OIDDelta_old, AccessExclusiveLock);
+		dest_old = CreateTransientRelDestReceiver(OIDDelta_old);
+	}
+	if (entry->has_new)
+	{
+		if (entry->has_old)
+			OIDDelta_new = make_new_heap(OIDDelta_old, tableSpace, RELPERSISTENCE_TEMP,
+										 ExclusiveLock);
+		else
+			OIDDelta_new = make_new_heap(matviewOid, tableSpace, RELPERSISTENCE_TEMP,
+										 ExclusiveLock);
+		LockRelationOid(OIDDelta_new, AccessExclusiveLock);
+		dest_new = CreateTransientRelDestReceiver(OIDDelta_new);
+	}
+
+	/*
+	 * Now lock down security-restricted operations.
+	 */
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+
+	/* for all modified tables */
+	foreach(lc, entry->tables)
+	{
+		ListCell *lc2;
+
+		table = (MV_TriggerTable *) lfirst(lc);
+
+		/* loop for self-join */
+		foreach(lc2, table->rte_indexes)
+		{
+			int index = lfirst_int(lc2);
+
+			calc_delta(table, index, rewritten, dest_old, dest_new, queryEnv);
+
+			PG_TRY();
+			{
+				apply_delta(matviewOid, OIDDelta_new, OIDDelta_old, query);
+			}
+			PG_CATCH();
+			{
+				matview_maintenance_depth = old_depth;
+				PG_RE_THROW();
+			}
+			PG_END_TRY();
+
+			/* truncate view delta tables */
+			truncate_view_delta(OIDDelta_old);
+			truncate_view_delta(OIDDelta_new);
+		}
+	}
+
+	/* Pop the original snapshot. */
+	PopActiveSnapshot();
+
+	table_close(matviewRel, NoLock);
+
+	/* Roll back any GUC changes */
+	AtEOXact_GUC(false, save_nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(save_userid, save_sec_context);
+
+	/* Clean up hash entry and drop temporary tables */
+	clean_up_IVM_hash_entry(entry);
+	clean_up_IVM_temptable(OIDDelta_old, OIDDelta_new);
+
+	return PointerGetDatum(NULL);
+}
+
+#define IVM_colname(type, col) makeObjectName("__ivm_" type, col, "_")
+
+static char *
+get_null_condition_string(IvmOp op, char *arg1, char *arg2, char* count_col)
+{
+	StringInfoData null_cond;
+	initStringInfo(&null_cond);
+
+	switch (op)
+	{
+		case IVM_ADD:
+			appendStringInfo(&null_cond,
+				"%s = 0 AND %s = 0",
+				quote_qualified_identifier(arg1, count_col),
+				quote_qualified_identifier(arg2, count_col)
+			);
+			break;
+		case IVM_SUB:
+			appendStringInfo(&null_cond,
+				"%s = %s",
+				quote_qualified_identifier(arg1, count_col),
+				quote_qualified_identifier(arg2, count_col)
+			);
+			break;
+		default:
+			elog(ERROR,"unkwon opration");
+	}
+
+	return null_cond.data;
+}
+
+static char *
+get_operation_string(IvmOp op, char *col, char *arg1, char *arg2, char* count_col, const char *castType)
+{
+	StringInfoData buf, castString;
+	char	*col1 = quote_qualified_identifier(arg1, col);
+	char	*col2 = quote_qualified_identifier(arg2, col);
+
+	char op_char = (op == IVM_SUB ? '-' : '+');
+	char *sign = (op == IVM_SUB ? "-" : "");
+
+	initStringInfo(&buf);
+	initStringInfo(&castString);
+
+	if (castType)
+		appendStringInfo(&castString, "::%s", castType);
+
+
+	if (!count_col)
+	{
+		appendStringInfo(&buf, "(%s %c %s)%s",
+			col1, op_char, col2, castString.data);
+	}
+	else
+	{
+		char *null_cond = get_null_condition_string(op, arg1, arg2, count_col);
+
+		appendStringInfo(&buf,
+			"(CASE WHEN %s THEN NULL "
+				" WHEN %s IS NULL THEN %s%s "
+				" WHEN %s IS NULL THEN (%s) "
+				" ELSE (%s %c %s)%s END)",
+			null_cond,
+			col1, sign, col2,
+			col2, col1,
+			col1, op_char, col2, castString.data
+		);
+	}
+
+	return buf.data;
+}
+
+
+static void
+calc_delta(MV_TriggerTable *table, int index, Query *query,
+			DestReceiver *dest_old, DestReceiver *dest_new, QueryEnvironment *queryEnv)
+{
+	ListCell *lc;
+	RangeTblEntry *rte;
+
+	lc = list_nth_cell(query->rtable, index);
+	rte = (RangeTblEntry *) lfirst(lc);
+
+	/* Generate old delta */
+	if (list_length(table->old_rtes) > 0)
+	{
+		lfirst(lc) = union_ENRs(rte, table->table_id, table->old_rtes, "old", queryEnv);
+		refresh_matview_datafill(dest_old, query, queryEnv, NULL);
+	}
+
+	/* Generate new delta */
+	if (list_length(table->new_rtes) > 0)
+	{
+		lfirst(lc) = union_ENRs(rte, table->table_id, table->new_rtes, "new", queryEnv);
+		refresh_matview_datafill(dest_new, query, queryEnv, NULL);
+	}
+
+	/* Retore the original RTE */
+	lfirst(lc) = table->original_rte;
+}
+
+
+static RangeTblEntry*
+union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix, QueryEnvironment *queryEnv)
+{
+	StringInfoData str;
+	ParseState	*pstate;
+	RawStmt *raw;
+	Query *sub;
+	int	i;
+
+	/* Create a ParseState for rewriting the view definition query */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	initStringInfo(&str);
+
+	for (i = 0; i < list_length(enr_rtes); i++)
+	{
+		if (i > 0)
+			appendStringInfo(&str, " UNION ALL ");
+		appendStringInfo(&str," SELECT * FROM %s", make_delta_enr_name(prefix, relid, i));
+	}
+
+	raw = (RawStmt*)linitial(raw_parser(str.data));
+	sub = transformStmt(pstate, raw->stmt);
+
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = sub;
+	rte->security_barrier = false;
+	/* Clear fields that should not be set in a subquery RTE */
+	rte->relid = InvalidOid;
+	rte->relkind = 0;
+	rte->rellockmode = 0;
+	rte->tablesample = NULL;
+	rte->inh = false;			/* must not be set for a subquery */
+
+	rte->requiredPerms = 0;		/* no permission check on subquery itself */
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->insertedCols = NULL;
+	rte->updatedCols = NULL;
+	rte->extraUpdatedCols = NULL;
+
+	return rte;
+}
+
+static char*
+make_delta_enr_name(const char *prefix, Oid relid, int count)
+{
+	char buf[NAMEDATALEN];
+	char *name;
+
+	snprintf(buf, NAMEDATALEN, "%s_%u_%u", prefix, relid, count);
+	name = pstrdup(buf);
+
+	return name;
+}
+
+static void
+register_delta_ENRs(ParseState *pstate, Query *query, List *tables)
+{
+	QueryEnvironment *queryEnv = pstate->p_queryEnv;
+	ListCell *lc;
+	RangeTblEntry	*rte;
+
+	foreach(lc, tables)
+	{
+		MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc);
+		ListCell *lc2;
+		int count;
+
+		count = 0;
+		foreach(lc2, table->old_tuplestores)
+		{
+			Tuplestorestate *oldtable = (Tuplestorestate *) lfirst(lc2);
+			EphemeralNamedRelation enr =
+				palloc(sizeof(EphemeralNamedRelationData));
+
+			enr->md.name = make_delta_enr_name("old", table->table_id, count);
+			enr->md.reliddesc = table->table_id;
+			enr->md.tupdesc = NULL;
+			enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+			enr->md.enrtuples = tuplestore_tuple_count(oldtable);
+			enr->reldata = oldtable;
+			register_ENR(queryEnv, enr);
+
+			rte = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+			query->rtable = lappend(query->rtable, rte);
+			table->old_rtes = lappend(table->old_rtes, rte);
+
+			count++;
+		}
+
+		count = 0;
+		foreach(lc2, table->new_tuplestores)
+		{
+			Tuplestorestate *newtable = (Tuplestorestate *) lfirst(lc2);
+			EphemeralNamedRelation enr =
+				palloc(sizeof(EphemeralNamedRelationData));
+
+			enr->md.name = make_delta_enr_name("new", table->table_id, count);
+			enr->md.reliddesc = table->table_id;
+			enr->md.tupdesc = NULL;
+			enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+			enr->md.enrtuples = tuplestore_tuple_count(newtable);
+			enr->reldata = newtable;
+			register_ENR(queryEnv, enr);
+
+			rte = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+			query->rtable = lappend(query->rtable, rte);
+			table->new_rtes = lappend(table->new_rtes, rte);
+
+			count++;
+		}
+	}
+}
+
+static RangeTblEntry*
+get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table, TransactionId xid, CommandId cid, QueryEnvironment *queryEnv)
+{
+	StringInfoData str;
+	RawStmt *raw;
+	Query *sub;
+	Relation rel;
+	ParseState *pstate;
+	char *relname;
+	int i;
+
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	/*
+	 * We can use NoLock here since AcquireRewriteLocks should
+	 * have locked the rel already.
+	 */
+	rel = table_open(table->table_id, NoLock);
+	relname = quote_qualified_identifier(
+					get_namespace_name(RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+	table_close(rel, NoLock);
+
+	initStringInfo(&str);
+	appendStringInfo(&str,
+		"SELECT t.* FROM %s t"
+		" WHERE (age(t.xmin) - age(%u::text::xid) > 0) OR"
+		" (t.xmin = %u AND t.cmin::text::int < %u)",
+			relname, xid, xid, cid);
+
+	for (i=0; i<list_length(table->old_tuplestores); i++)
+	{
+		appendStringInfo(&str, " UNION ALL ");
+		appendStringInfo(&str," SELECT * FROM %s", make_delta_enr_name("old", table->table_id, i));
+	}
+
+	raw = (RawStmt*)linitial(raw_parser(str.data));
+	sub = transformStmt(pstate, raw->stmt);
+
+	table->original_rte = copyObject(rte);
+
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = sub;
+	rte->security_barrier = false;
+	/* Clear fields that should not be set in a subquery RTE */
+	rte->relid = InvalidOid;
+	rte->relkind = 0;
+	rte->rellockmode = 0;
+	rte->tablesample = NULL;
+	rte->inh = false;			/* must not be set for a subquery */
+
+	rte->requiredPerms = 0;		/* no permission check on subquery itself */
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->insertedCols = NULL;
+	rte->updatedCols = NULL;
+	rte->extraUpdatedCols = NULL;
+
+	return rte;
+}
+
+static Query*
+rewrite_query_for_preupdate_state(Query *query, List *tables, TransactionId xid, CommandId cid, ParseState *pstate)
+{
+	Query *rewritten = copyObject(query);
+	ListCell *lc;
+	int num_rte = list_length(query->rtable);
+	int i;
+
+	register_delta_ENRs(pstate, rewritten, tables);
+
+	// XXX: Is necessary? Is this right timing?
+	AcquireRewriteLocks(rewritten, true, false);
+
+	foreach(lc, tables)
+	{
+		MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc);
+		ListCell *lc2;
+
+		i = 0;
+		foreach(lc2, rewritten->rtable)
+		{
+			RangeTblEntry *r = (RangeTblEntry*) lfirst(lc2);
+
+			if (r->relid == table->table_id)
+			{
+				lfirst(lc2) = get_prestate_rte(r, table, xid, cid, pstate->p_queryEnv);
+				table->rte_indexes = lappend_int(table->rte_indexes, i);
+			}
+
+			if (++i >= num_rte)
+				break;
+		}
+	}
+
+	return rewritten;
+}
+
+static Query *
+rewrite_query_for_counting_and_aggregation(Query *query, ParseState *pstate)
+{
+	TargetEntry *tle_count;
+	FuncCall *fn;
+	Node *node;
+	Const	*dmy_arg = makeConst(INT4OID,
+								 -1,
+								 InvalidOid,
+								 sizeof(int32),
+								 Int32GetDatum(1),
+								 false,
+								 true);
+
+	if (query->hasAggs)
+	{
+		ListCell *lc;
+		List *agg_counts = NIL;
+		AttrNumber next_resno = list_length(query->targetList) + 1;
+
+		foreach(lc, query->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+			if (IsA(tle->expr, Aggref))
+			{
+				Aggref *aggref = (Aggref *) tle->expr;
+				const char *aggname = get_func_name(aggref->aggfnoid);
+
+				/*
+				 * For aggregate functions except to count, add count func with the same arg parameters.
+				 * Also, add sum func for agv.
+				 *
+				 * XXX: need some generalization
+				 * XXX: If there are same expressions explicitly in the target list, we can use this instead
+				 * of adding new duplicated one.
+				 */
+				if (strcmp(aggname, "count") != 0)
+				{
+					fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+
+					/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+					node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+					((Aggref *)node)->args = aggref->args;
+
+					tle_count = makeTargetEntry((Expr *) node,
+											next_resno,
+											NULL,
+											false);
+					agg_counts = lappend(agg_counts, tle_count);
+					next_resno++;
+				}
+				if (strcmp(aggname, "avg") == 0)
+				{
+					List *dmy_args = NIL;
+					ListCell *lc;
+					foreach(lc, aggref->aggargtypes)
+					{
+						Oid		typeid = lfirst_oid(lc);
+						Type	type = typeidType(typeid);
+
+						Const *con = makeConst(typeid,
+											   -1,
+											   typeTypeCollation(type),
+											   typeLen(type),
+											   (Datum) 0,
+											   true,
+											   typeByVal(type));
+						dmy_args = lappend(dmy_args, con);
+						ReleaseSysCache(type);
+
+					}
+					fn = makeFuncCall(list_make1(makeString("sum")), NIL, -1);
+
+					/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+					node = ParseFuncOrColumn(pstate, fn->funcname, dmy_args, NULL, fn, false, -1);
+					((Aggref *)node)->args = aggref->args;
+
+					tle_count = makeTargetEntry((Expr *) node,
+												next_resno,
+												NULL,
+												false);
+					agg_counts = lappend(agg_counts, tle_count);
+					next_resno++;
+					}
+				}
+
+			}
+			query->targetList = list_concat(query->targetList, agg_counts);
+	}
+
+	fn = makeFuncCall(list_make1(makeString("count")), NIL, -1);
+	fn->agg_star = true;
+	if (!query->groupClause && !query->hasAggs)
+		query->groupClause = transformDistinctClause(NULL, &query->targetList, query->sortClause, false);
+
+	node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+	tle_count = makeTargetEntry((Expr *) node,
+							  list_length(query->targetList) + 1,
+							  NULL,
+							  false);
+	query->targetList = lappend(query->targetList, tle_count);
+	query->hasAggs = true;
+
+	return query;
+}
+
+
+static void
+apply_delta(Oid matviewOid, Oid tempOid_new, Oid tempOid_old, Query *query)
+{
+	StringInfoData querybuf;
+	StringInfoData mvatts_buf, diffatts_buf;
+	StringInfoData mv_gkeys_buf, diff_gkeys_buf, updt_gkeys_buf;
+	StringInfoData diff_aggs_buf, update_aggs_old, update_aggs_new;
+	StringInfoData returning_buf, result_buf;
+	StringInfoData min_or_max_buf;
+	Relation	matviewRel;
+	Relation	tempRel_new = NULL, tempRel_old = NULL;
+	char	   *matviewname;
+	char	   *tempname_new = NULL, *tempname_old = NULL;
+	ListCell	*lc;
+	char	   *sep, *sep_agg;
+	bool		with_group = query->groupClause != NULL;
+	int			i;
+	bool		has_min_or_max = false;
+	int			num_group_keys = 0;
+	int			num_min_or_max = 0;
+
+
+	initStringInfo(&querybuf);
+	matviewRel = table_open(matviewOid, NoLock);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	if (OidIsValid(tempOid_new))
+	{
+		tempRel_new = table_open(tempOid_new, NoLock);
+		tempname_new = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(tempRel_new)),
+												  RelationGetRelationName(tempRel_new));
+	}
+	if (OidIsValid(tempOid_old))
+	{
+		tempRel_old = table_open(tempOid_old, NoLock);
+		tempname_old = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(tempRel_old)),
+												  RelationGetRelationName(tempRel_old));
+	}
+
+	initStringInfo(&mvatts_buf);
+	initStringInfo(&diffatts_buf);
+	initStringInfo(&diff_aggs_buf);
+	initStringInfo(&update_aggs_old);
+	initStringInfo(&update_aggs_new);
+	initStringInfo(&returning_buf);
+	initStringInfo(&result_buf);
+	initStringInfo(&min_or_max_buf);
+
+	sep = "";
+	sep_agg= "";
+	i = 0;
+	foreach (lc, query->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char *resname = NameStr(attr->attname);
+
+		i++;
+
+		if (tle->resjunk)
+			continue;
+
+		appendStringInfo(&mvatts_buf, "%s", sep);
+		appendStringInfo(&diffatts_buf, "%s", sep);
+		sep = ", ";
+
+		appendStringInfo(&mvatts_buf, "%s", quote_qualified_identifier("mv", resname));
+		appendStringInfo(&diffatts_buf, "%s", quote_qualified_identifier("diff", resname));
+
+		if (query->hasAggs && IsA(tle->expr, Aggref))
+		{
+			Aggref *aggref = (Aggref *) tle->expr;
+			const char *aggname = get_func_name(aggref->aggfnoid);
+			const char *aggtype = format_type_be(aggref->aggtype); /* XXX: should be add_cast_to ? */
+
+			appendStringInfo(&update_aggs_old, "%s", sep_agg);
+			appendStringInfo(&update_aggs_new, "%s", sep_agg);
+			appendStringInfo(&diff_aggs_buf, "%s", sep_agg);
+
+			sep_agg = ", ";
+
+			/* XXX: need some generalization
+			 *
+			 * Specifically, Using func names is not robust.  We can use oids instead
+			 * of names, but it would be nice to add some information to pg_aggregate
+			 * and handler functions.
+			 */
+
+			if (!strcmp(aggname, "count"))
+			{
+				/* resname = mv.resname - t.resname */
+				appendStringInfo(&update_aggs_old,
+					"%s = %s",
+					quote_qualified_identifier(NULL, resname),
+					get_operation_string(IVM_SUB,resname, "mv", "t", NULL, NULL));
+
+				/* resname = mv.resname + diff.resname */
+				appendStringInfo(&update_aggs_new,
+					"%s = %s",
+					quote_qualified_identifier(NULL, resname),
+					get_operation_string(IVM_ADD, resname, "mv", "diff", NULL, NULL));
+
+				appendStringInfo(&diff_aggs_buf, "%s",
+					quote_qualified_identifier("diff", resname)
+				);
+			}
+			else if (!strcmp(aggname, "sum"))
+			{
+				char *count_col = IVM_colname("count", resname);
+
+				/* sum = mv.sum - t.sum */
+				appendStringInfo(&update_aggs_old,
+					"%s = %s, ",
+					quote_qualified_identifier(NULL, resname),
+					get_operation_string(IVM_SUB, resname, "mv", "t", count_col, NULL)
+				);
+				/* count = mv.count - t.count */
+				appendStringInfo(&update_aggs_old,
+					"%s = %s",
+					quote_qualified_identifier(NULL, count_col),
+					get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+				);
+
+				/* sum = mv.sum + diff.sum */
+				appendStringInfo(&update_aggs_new,
+					"%s = %s, ",
+					quote_qualified_identifier(NULL, resname),
+					get_operation_string(IVM_ADD, resname, "mv", "diff", count_col, NULL)
+				);
+				/* count = mv.count + diff.count */
+				appendStringInfo(&update_aggs_new,
+					"%s = %s",
+					quote_qualified_identifier(NULL, count_col),
+					get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+				);
+
+				appendStringInfo(&diff_aggs_buf, "%s, %s",
+					quote_qualified_identifier("diff", resname),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",resname,"_"))
+				);
+			}
+			else if (!strcmp(aggname, "avg"))
+			{
+				char *sum_col = IVM_colname("sum", resname);
+				char *count_col = IVM_colname("count", resname);
+
+				/* avg = (mv.sum - t.sum)::aggtype / (mv.count - t.count) */
+				appendStringInfo(&update_aggs_old,
+					"%s = %s / %s, ",
+					quote_qualified_identifier(NULL, resname),
+					get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, aggtype),
+					get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+				);
+				/* sum = mv.sum - t.sum */
+				appendStringInfo(&update_aggs_old,
+					"%s = %s, ",
+					quote_qualified_identifier(NULL, sum_col),
+					get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, NULL)
+				);
+				/* count = mv.count - t.count */
+				appendStringInfo(&update_aggs_old,
+					"%s = %s",
+					quote_qualified_identifier(NULL, count_col),
+					get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+				);
+
+				/* avg = (mv.sum + diff.sum)::aggtype / (mv.count + diff.count) */
+				appendStringInfo(&update_aggs_new,
+					"%s = %s / %s, ",
+					quote_qualified_identifier(NULL, resname),
+					get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, aggtype),
+					get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+				);
+				/* sum = mv.sum + diff.sum */
+				appendStringInfo(&update_aggs_new,
+					"%s = %s, ",
+					quote_qualified_identifier(NULL, sum_col),
+					get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, NULL)
+				);
+				/* count = mv.count + diff.count */
+				appendStringInfo(&update_aggs_new,
+					"%s = %s",
+					quote_qualified_identifier(NULL, count_col),
+					get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+				);
+
+				appendStringInfo(&diff_aggs_buf, "%s, %s, %s",
+					quote_qualified_identifier("diff", resname),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_sum",resname,"_")),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",resname,"_"))
+				);
+			}
+			else if (!strcmp(aggname, "min") || !strcmp(aggname, "max"))
+			{
+				bool is_min = !strcmp(aggname, "min");
+				char *count_col = IVM_colname("count", resname);
+
+				if (!has_min_or_max)
+				{
+					has_min_or_max = true;
+					appendStringInfo(&returning_buf, "RETURNING mv.ctid, (");
+				}
+				else
+				{
+					appendStringInfo(&returning_buf, " OR ");
+					appendStringInfo(&min_or_max_buf, ",");
+				}
+
+				appendStringInfo(&returning_buf, "%s %s %s",
+					quote_qualified_identifier("mv", resname),
+					is_min ? ">=" : "<=",
+					quote_qualified_identifier("t", resname)
+				);
+				appendStringInfo(&min_or_max_buf, "%s", quote_qualified_identifier(NULL, resname));
+
+				/* Even if the new values is not NULL, this might be recomputated afterwords. */
+				appendStringInfo(&update_aggs_old,
+					"%s = CASE WHEN %s THEN NULL ELSE %s END, ",
+					quote_qualified_identifier(NULL, resname),
+					get_null_condition_string(IVM_SUB, "mv", "t", count_col),
+					quote_qualified_identifier("mv", resname)
+				);
+				/* count = mv.count - t.count */
+				appendStringInfo(&update_aggs_old,
+					"%s = %s",
+					quote_qualified_identifier(NULL, count_col),
+					get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+				);
+
+				/*
+				 * min = least(mv.min, diff.min)
+				 * max = greatest(mv.max, diff.max)
+				 */
+				appendStringInfo(&update_aggs_new,
+					"%s = CASE WHEN %s THEN NULL ELSE %s(%s,%s) END, ",
+					quote_qualified_identifier(NULL, resname),
+					get_null_condition_string(IVM_ADD, "mv", "diff", count_col),
+
+					is_min ? "least" : "greatest",
+					quote_qualified_identifier("mv", resname),
+					quote_qualified_identifier("diff", resname)
+				);
+
+				/* count = mv.count + diff.count */
+				appendStringInfo(&update_aggs_new,
+					"%s = %s",
+					quote_qualified_identifier(NULL, count_col),
+					get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+				);
+
+				appendStringInfo(&diff_aggs_buf, "%s, %s",
+					quote_qualified_identifier("diff", resname),
+					quote_qualified_identifier("diff", makeObjectName("__ivm_count",resname,"_"))
+				);
+
+				num_min_or_max++;
+			}
+			else
+				elog(ERROR, "unsupported aggregate function: %s", aggname);
+
+		}
+	}
+	if (has_min_or_max)
+		appendStringInfo(&returning_buf, ") AS recalc");
+
+	if (query->hasAggs)
+	{
+		initStringInfo(&mv_gkeys_buf);
+		initStringInfo(&diff_gkeys_buf);
+		initStringInfo(&updt_gkeys_buf);
+
+		if (with_group)
+		{
+			sep_agg= "";
+			foreach (lc, query->groupClause)
+			{
+				SortGroupClause *sgcl = (SortGroupClause *) lfirst(lc);
+				TargetEntry *tle = get_sortgroupclause_tle(sgcl, query->targetList);
+
+				Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno-1);
+				char *resname = NameStr(attr->attname);
+
+				appendStringInfo(&mv_gkeys_buf, "%s", sep_agg);
+				appendStringInfo(&diff_gkeys_buf, "%s", sep_agg);
+				appendStringInfo(&updt_gkeys_buf, "%s", sep_agg);
+
+				sep_agg = ", ";
+
+				appendStringInfo(&mv_gkeys_buf, "%s", quote_qualified_identifier("mv", resname));
+				appendStringInfo(&diff_gkeys_buf, "%s", quote_qualified_identifier("diff", resname));
+				appendStringInfo(&updt_gkeys_buf, "%s", quote_qualified_identifier("updt", resname));
+
+				num_group_keys++;
+			}
+
+			if (has_min_or_max)
+			{
+				appendStringInfo(&returning_buf, ", %s", mv_gkeys_buf.data);
+				appendStringInfo(&result_buf, "SELECT ctid AS tid, %s FROM updt WHERE recalc", updt_gkeys_buf.data);
+			}
+		}
+		else
+		{
+			appendStringInfo(&mv_gkeys_buf, "1");
+			appendStringInfo(&diff_gkeys_buf, "1");
+			appendStringInfo(&updt_gkeys_buf, "1");
+		}
+	}
+
+
+	/* Open SPI context. */
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	/* Analyze the temp table with the new contents. */
+	if (tempname_new)
+	{
+		appendStringInfo(&querybuf, "ANALYZE %s", tempname_new);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+	if (tempname_old)
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf, "ANALYZE %s", tempname_old);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+
+	OpenMatViewIncrementalMaintenance();
+
+	if (query->hasAggs)
+	{
+		if (tempname_old)
+		{
+			resetStringInfo(&querybuf);
+			appendStringInfo(&querybuf,
+							"WITH t AS ("
+							"  SELECT diff.__ivm_count__, "
+							"         %s,"
+							"         (diff.__ivm_count__ = mv.__ivm_count__) AS for_dlt, "
+							"         mv.ctid"
+							"  FROM %s AS mv, %s AS diff WHERE (%s) = (%s)"
+							"), updt AS ("
+							"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ - t.__ivm_count__,"
+							"                      %s"
+							"  FROM t WHERE mv.ctid = t.ctid AND NOT for_dlt"
+							"   %s"
+							"), dlt AS ("
+							"  DELETE FROM %s AS mv USING t WHERE mv.ctid = t.ctid AND for_dlt "
+							") %s",
+							diff_aggs_buf.data,
+							matviewname, tempname_old, mv_gkeys_buf.data, diff_gkeys_buf.data,
+							matviewname, update_aggs_old.data,
+							returning_buf.data,
+							matviewname,
+							(has_min_or_max && with_group) ? result_buf.data : "SELECT 1"
+							);
+			if (SPI_exec(querybuf.data, 0) != SPI_OK_SELECT)
+				elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+
+			if (has_min_or_max && SPI_processed > 0)
+			{
+				SPITupleTable *tuptable_recalc = SPI_tuptable;
+				TupleDesc   tupdesc_recalc = tuptable_recalc->tupdesc;
+				int64		processed = SPI_processed;
+				uint64      i;
+				Oid			*keyTypes = NULL, *minmaxTypes = NULL;
+				char		*keyNulls = NULL, *minmaxNulls = NULL;
+				Datum		*keyVals = NULL, *minmaxVals = NULL;
+
+				if (with_group)
+				{
+					keyTypes = palloc(sizeof(Oid) * num_group_keys);
+					keyNulls = palloc(sizeof(char) * num_group_keys);
+					keyVals = palloc(sizeof(Datum) * num_group_keys);
+					Assert(tupdesc_recalc->natts == num_group_keys + 1);
+
+					for (i = 0; i < num_group_keys; i++)
+						keyTypes[i] = TupleDescAttr(tupdesc_recalc, i+1)->atttypid;
+				}
+
+				minmaxTypes = palloc(sizeof(Oid) * (num_min_or_max + 1));
+				minmaxNulls = palloc(sizeof(char) * (num_min_or_max + 1));
+				minmaxVals = palloc(sizeof(Datum) * (num_min_or_max + 1));
+
+				for (i=0; i< processed; i++)
+				{
+					int j;
+					bool isnull;
+					SPIPlanPtr plan;
+					SPITupleTable *tuptable_minmax;
+					TupleDesc   tupdesc_minmax;
+
+					if (with_group)
+					{
+						for (j = 0; j < num_group_keys; j++)
+						{
+							keyVals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, j+2, &isnull);
+							if (isnull)
+								keyNulls[j] = 'n';
+							else
+								keyNulls[j] = ' ';
+						}
+					}
+
+					plan = get_plan_for_recalc_min_max(matviewOid, min_or_max_buf.data,
+													   mv_gkeys_buf.data, num_group_keys, keyTypes, with_group);
+
+					if (SPI_execute_plan(plan, keyVals, keyNulls, false, 0) != SPI_OK_SELECT)
+						elog(ERROR, "SPI_execcute_plan1");
+					if (SPI_processed != 1)
+						elog(ERROR, "SPI_execcute_plan returned zere or more than one rows");
+
+					tuptable_minmax = SPI_tuptable;
+					tupdesc_minmax = tuptable_minmax->tupdesc;
+
+					Assert(tupdesc_minmax->natts == num_min_or_max);
+
+					for (j = 0; j < tupdesc_minmax->natts; j++)
+					{
+						if (i == 0)
+							minmaxTypes[j] = TupleDescAttr(tupdesc_minmax, j)->atttypid;
+
+						minmaxVals[j] = SPI_getbinval(tuptable_minmax->vals[0], tupdesc_minmax, j+1, &isnull);
+						if (isnull)
+							minmaxNulls[j] = 'n';
+						else
+							minmaxNulls[j] = ' ';
+					}
+					minmaxTypes[j] = TIDOID;
+					minmaxVals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, 1, &isnull);
+					minmaxNulls[j] = ' ';
+
+					plan = get_plan_for_set_min_max(matviewOid, matviewname, min_or_max_buf.data,
+													num_min_or_max, minmaxTypes, with_group);
+
+					if (SPI_execute_plan(plan, minmaxVals, minmaxNulls, false, 0) != SPI_OK_UPDATE)
+						elog(ERROR, "SPI_execcute_plan2");
+
+				}
+			}
+
+		}
+		if (tempname_new)
+		{
+			resetStringInfo(&querybuf);
+			appendStringInfo(&querybuf,
+							"WITH updt AS ("
+							"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ + diff.__ivm_count__"
+							", %s "
+							"  FROM %s AS diff WHERE (%s) = (%s)"
+							"  RETURNING %s"
+							") INSERT INTO %s (SELECT * FROM %s AS diff WHERE (%s) NOT IN (SELECT %s FROM updt));",
+							matviewname, update_aggs_new.data, tempname_new,
+							mv_gkeys_buf.data, diff_gkeys_buf.data, diff_gkeys_buf.data,
+							matviewname, tempname_new, diff_gkeys_buf.data, updt_gkeys_buf.data);
+			if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+				elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+		}
+	}
+	else
+	{
+		if (tempname_old)
+		{
+			resetStringInfo(&querybuf);
+			appendStringInfo(&querybuf,
+							"WITH t AS ("
+							"  SELECT diff.__ivm_count__, (diff.__ivm_count__ = mv.__ivm_count__) AS for_dlt, mv.ctid"
+							"  FROM %s AS mv, %s AS diff WHERE (%s) = (%s)"
+							"), updt AS ("
+							"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ - t.__ivm_count__"
+							"  FROM t WHERE mv.ctid = t.ctid AND NOT for_dlt"
+							") DELETE FROM %s AS mv USING t WHERE mv.ctid = t.ctid AND for_dlt;",
+							matviewname, tempname_old, mvatts_buf.data, diffatts_buf.data, matviewname, matviewname);
+			if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+				elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+		}
+		if (tempname_new)
+		{
+			resetStringInfo(&querybuf);
+			appendStringInfo(&querybuf,
+							"WITH updt AS ("
+							"  UPDATE %s AS mv SET __ivm_count__ = mv.__ivm_count__ + diff.__ivm_count__"
+							"  FROM %s AS diff WHERE (%s) = (%s)"
+							"  RETURNING %s"
+							") INSERT INTO %s (SELECT * FROM %s AS diff WHERE (%s) NOT IN (SELECT * FROM updt));",
+							matviewname, tempname_new, mvatts_buf.data, diffatts_buf.data, diffatts_buf.data, matviewname, tempname_new, diffatts_buf.data);
+			if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+				elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+		}
+	}
+
+	/* We're done maintaining the materialized view. */
+	CloseMatViewIncrementalMaintenance();
+
+	if (OidIsValid(tempOid_new))
+		table_close(tempRel_new, NoLock);
+	if (OidIsValid(tempOid_old))
+		table_close(tempRel_old, NoLock);
+
+	table_close(matviewRel, NoLock);
+
+
+	/* Close SPI context. */
+	if (SPI_finish() != SPI_OK_FINISH)
+		elog(ERROR, "SPI_finish failed");
+}
+
+
+static void
+mv_InitHashTables(void)
+{
+	HASHCTL		ctl;
+
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(MV_QueryKey);
+	ctl.entrysize = sizeof(MV_QueryHashEntry);
+	mv_query_cache = hash_create("MV query cache",
+								 MV_INIT_QUERYHASHSIZE,
+								 &ctl, HASH_ELEM | HASH_BLOBS);
+
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(MV_TriggerHashEntry);
+	mv_trigger_info = hash_create("MV trigger info",
+								 MV_INIT_QUERYHASHSIZE,
+								 &ctl, HASH_ELEM | HASH_BLOBS);
+}
+
+static SPIPlanPtr
+mv_FetchPreparedPlan(MV_QueryKey *key)
+{
+	MV_QueryHashEntry *entry;
+	SPIPlanPtr	plan;
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_query_cache)
+		mv_InitHashTables();
+
+	/*
+	 * Lookup for the key
+	 */
+	entry = (MV_QueryHashEntry *) hash_search(mv_query_cache,
+											  (void *) key,
+											  HASH_FIND, NULL);
+	if (entry == NULL)
+		return NULL;
+
+	/*
+	 * Check whether the plan is still valid.  If it isn't, we don't want to
+	 * simply rely on plancache.c to regenerate it; rather we should start
+	 * from scratch and rebuild the query text too.  This is to cover cases
+	 * such as table/column renames.  We depend on the plancache machinery to
+	 * detect possible invalidations, though.
+	 *
+	 * CAUTION: this check is only trustworthy if the caller has already
+	 * locked both materialized views and base tables.
+	 */
+	plan = entry->plan;
+	if (plan && SPI_plan_is_valid(plan))
+		return plan;
+
+	/*
+	 * Otherwise we might as well flush the cached plan now, to free a little
+	 * memory space before we make a new one.
+	 */
+	entry->plan = NULL;
+	if (plan)
+		SPI_freeplan(plan);
+
+	return NULL;
+}
+
+/*
+ * mv_HashPreparedPlan -
+ *
+ * Add another plan to our private SPI query plan hashtable.
+ */
+static void
+mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan)
+{
+	MV_QueryHashEntry *entry;
+	bool		found;
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_query_cache)
+		mv_InitHashTables();
+
+	/*
+	 * Add the new plan.  We might be overwriting an entry previously found
+	 * invalid by mv_FetchPreparedPlan.
+	 */
+	entry = (MV_QueryHashEntry *) hash_search(mv_query_cache,
+											  (void *) key,
+											  HASH_ENTER, &found);
+	Assert(!found || entry->plan == NULL);
+	entry->plan = plan;
+}
+
+/* ----------
+ * mv_BuildQueryKey -
+ *
+ *	Construct a hashtable key for a prepared SPI plan for IVM.
+ *
+ *		key: output argument, *key is filled in based on the other arguments
+ *		matview_id: OID of materialized view
+ *		query_type: an internal number identifying the query type
+ *			(see MV_PLAN_XXX constants at head of file)
+ * ----------
+ */
+static void
+mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type)
+{
+	/*
+	 * We assume struct MV_QueryKey contains no padding bytes, else we'd need
+	 * to use memset to clear them.
+	 */
+	key->matview_id = matview_id;
+	key->query_type = query_type;
+}
+
+static SPIPlanPtr
+get_plan_for_recalc_min_max(Oid matviewOid, const char *min_max_list,
+							const char *group_keys, int nkeys, Oid *keyTypes, bool with_group)
+{
+	MV_QueryKey key;
+	SPIPlanPtr	plan;
+
+	/* Fetch or prepare a saved plan for the real check */
+	mv_BuildQueryKey(&key, matviewOid, MV_PLAN_RECALC_MINMAX);
+
+	if ((plan = mv_FetchPreparedPlan(&key)) == NULL)
+	{
+		char	*viewdef;
+		StringInfoData	str;
+		int		i;
+
+
+		/* get view definition of matview */
+		viewdef = text_to_cstring((text *) DatumGetPointer(
+					DirectFunctionCall1(pg_get_viewdef, ObjectIdGetDatum(matviewOid))));
+		/* get rid of tailling semi-collon */
+		viewdef[strlen(viewdef)-1] = '\0';
+
+		initStringInfo(&str);
+		appendStringInfo(&str, "SELECT %s FROM (%s) mv", min_max_list, viewdef);
+
+		if (with_group)
+		{
+			appendStringInfo(&str, " WHERE (%s) = (", group_keys);
+
+			for (i = 1; i <= nkeys; i++)
+				appendStringInfo(&str, "%s$%d", (i==1 ? "" : ", "),i );
+
+			appendStringInfo(&str, ")");
+		}
+
+		plan = SPI_prepare(str.data, (with_group ? nkeys : 0), (with_group ? keyTypes : NULL));
+		if (plan == NULL)
+			elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), str.data);
+
+		SPI_keepplan(plan);
+		mv_HashPreparedPlan(&key, plan);
+	}
+
+	return plan;
+}
+
+static SPIPlanPtr
+get_plan_for_set_min_max(Oid matviewOid, char *matviewname, const char *min_max_list,
+						  int num_min_max, Oid *valTypes, bool with_group)
+{
+	MV_QueryKey key;
+	SPIPlanPtr plan;
+
+	/* Fetch or prepare a saved plan for the real check */
+	mv_BuildQueryKey(&key, matviewOid, MV_PLAN_SET_MINMAX);
+
+	if ((plan = mv_FetchPreparedPlan(&key)) == NULL)
+	{
+		StringInfoData str;
+		int		i;
+
+		initStringInfo(&str);
+		appendStringInfo(&str, "UPDATE %s AS mv SET (%s) = (",
+			matviewname, min_max_list);
+
+		for (i = 1; i <= num_min_max; i++)
+			appendStringInfo(&str, "%s$%d", (i==1 ? "" : ", "), i);
+
+		appendStringInfo(&str, ")");
+
+		if (with_group)
+			appendStringInfo(&str, " WHERE ctid = $%d", num_min_max + 1);
+
+		plan = SPI_prepare(str.data, num_min_max + (with_group ? 1 : 0), valTypes);
+		if (plan == NULL)
+			elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), str.data);
+
+		SPI_keepplan(plan);
+		mv_HashPreparedPlan(&key, plan);
+	}
+
+	return plan;
+}
+
+
+/*
+ * get_matview_query - get the Query from a matview's _RETURN rule.
+ */
+static Query *
+get_matview_query(Relation matviewRel)
+{
+	RewriteRule *rule;
+	List * actions;
+
+	/*
+	 * Check that everything is correct for a refresh. Problems at this point
+	 * are internal errors, so elog is sufficient.
+	 */
+	if (matviewRel->rd_rel->relhasrules == false ||
+		matviewRel->rd_rules->numLocks < 1)
+		elog(ERROR,
+			 "materialized view \"%s\" is missing rewrite information",
+			 RelationGetRelationName(matviewRel));
+
+	if (matviewRel->rd_rules->numLocks > 1)
+		elog(ERROR,
+			 "materialized view \"%s\" has too many rules",
+			 RelationGetRelationName(matviewRel));
+
+	rule = matviewRel->rd_rules->rules[0];
+	if (rule->event != CMD_SELECT || !(rule->isInstead))
+		elog(ERROR,
+			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
+			 RelationGetRelationName(matviewRel));
+
+	actions = rule->actions;
+	if (list_length(actions) != 1)
+		elog(ERROR,
+			 "the rule for materialized view \"%s\" is not a single action",
+			 RelationGetRelationName(matviewRel));
+
+	/*
+	 * The stored query was rewritten at the time of the MV definition, but
+	 * has not been scribbled on by the planner.
+	 */
+	return linitial_node(Query, actions);
+}
+
+static void
+truncate_view_delta(Oid delta_oid)
+{
+	Relation	rel;
+
+	if (!OidIsValid(delta_oid))
+		return;
+
+	rel = table_open(delta_oid, NoLock);
+	ExecuteTruncateGuts(list_make1(rel), list_make1_oid(delta_oid), NIL,
+						DROP_RESTRICT, false);
+	table_close(rel, NoLock);
+}
+
+void
+AtAbort_IVM()
+{
+	HASH_SEQ_STATUS seq;
+	MV_TriggerHashEntry *entry;
+
+	if (mv_trigger_info)
+	{
+		hash_seq_init(&seq, mv_trigger_info);
+		while ((entry = hash_seq_search(&seq)) != NULL)
+			clean_up_IVM_hash_entry(entry);
+	}
+}
+
+static void
+clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry)
+{
+	bool found;
+	ListCell *lc;
+
+	foreach(lc, entry->tables)
+	{
+		MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc);
+
+		list_free(table->old_tuplestores);
+		list_free(table->new_tuplestores);
+	}
+	list_free(entry->tables);
+
+	hash_search(mv_trigger_info, (void *) &entry->matview_id, HASH_REMOVE, &found);
+}
+
+static void
+clean_up_IVM_temptable(Oid tempOid_old, Oid tempOid_new)
+{
+	StringInfoData querybuf;
+	Relation tempRel_old, tempRel_new;
+	char *tempname_old = NULL, *tempname_new = NULL;
+
+	if (OidIsValid(tempOid_new))
+	{
+		tempRel_new = table_open(tempOid_new, NoLock);
+		tempname_new = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(tempRel_new)),
+												  RelationGetRelationName(tempRel_new));
+		table_close(tempRel_new, NoLock);
+	}
+	if (OidIsValid(tempOid_old))
+	{
+		tempRel_old = table_open(tempOid_old, NoLock);
+		tempname_old = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(tempRel_old)),
+												  RelationGetRelationName(tempRel_old));
+		table_close(tempRel_old, NoLock);
+	}
+
+	initStringInfo(&querybuf);
+
+	/* Open SPI context. */
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	/* Clean up temp tables. */
+	if (OidIsValid(tempOid_old))
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf, "DROP TABLE %s", tempname_old);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+	if (OidIsValid(tempOid_new))
+	{
+		resetStringInfo(&querybuf);
+		appendStringInfo(&querybuf, "DROP TABLE %s", tempname_new);
+		if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
+			elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+	}
+
+	/* Close SPI context. */
+	if (SPI_finish() != SPI_OK_FINISH)
+		elog(ERROR, "SPI_finish failed");
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index cdb1105b4a..096ab1b67c 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3803,6 +3803,7 @@ typedef struct AfterTriggersData
 	AfterTriggersQueryData *query_stack;	/* array of structs shown below */
 	int			query_depth;	/* current index in above array */
 	int			maxquerydepth;	/* allocated len of above array */
+	List	   *prolonged_tuplestores;
 
 	/* per-subtransaction-level data: */
 	AfterTriggersTransData *trans_stack;	/* array of structs shown below */
@@ -3833,6 +3834,7 @@ struct AfterTriggersTableData
 	bool		closed;			/* true when no longer OK to add tuples */
 	bool		before_trig_done;	/* did we already queue BS triggers? */
 	bool		after_trig_done;	/* did we already queue AS triggers? */
+	bool		prolonged;
 	AfterTriggerEventList after_trig_events;	/* if so, saved list pointer */
 	Tuplestorestate *old_tuplestore;	/* "old" transition table, if any */
 	Tuplestorestate *new_tuplestore;	/* "new" transition table, if any */
@@ -4586,6 +4588,35 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
 	return all_fired;
 }
 
+void
+SetTransitionTablePreserved(Oid relid, CmdType cmdType)
+{
+	AfterTriggersTableData *table;
+	AfterTriggersQueryData *qs;
+	bool		found = false;
+	ListCell   *lc;
+
+	/* Check state, like AfterTriggerSaveEvent. */
+	if (afterTriggers.query_depth < 0)
+		elog(ERROR, "SetTransitionTablePreserved() called outside of query");
+
+	qs = &afterTriggers.query_stack[afterTriggers.query_depth];
+
+	foreach(lc, qs->tables)
+	{
+		table = (AfterTriggersTableData *) lfirst(lc);
+		if (table->relid == relid && table->cmdType == cmdType &&
+			table->closed)
+		{
+			table->prolonged = true;
+			found = true;
+		}
+
+	}
+
+	if (!found)
+		elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType);
+}
 
 /*
  * GetAfterTriggersTableData
@@ -4755,6 +4786,7 @@ AfterTriggerBeginXact(void)
 	 */
 	afterTriggers.firing_counter = (CommandId) 1;	/* mustn't be 0 */
 	afterTriggers.query_depth = -1;
+	afterTriggers.prolonged_tuplestores = NIL;
 
 	/*
 	 * Verify that there is no leftover state remaining.  If these assertions
@@ -4915,11 +4947,29 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
 		ts = table->old_tuplestore;
 		table->old_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+		{
+			if (table->prolonged && afterTriggers.query_depth > 0)
+			{
+				MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext);
+				afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts);
+				MemoryContextSwitchTo(oldcxt);
+			}
+			else
+				tuplestore_end(ts);
+		}
 		ts = table->new_tuplestore;
 		table->new_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+		{
+			if (table->prolonged && afterTriggers.query_depth > 0)
+			{
+				MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext);
+				afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts);
+				MemoryContextSwitchTo(oldcxt);
+			}
+			else
+				tuplestore_end(ts);
+		}
 	}
 
 	/*
@@ -4929,6 +4979,17 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
 	 */
 	qs->tables = NIL;
 	list_free_deep(tables);
+
+	if (afterTriggers.query_depth == 0)
+	{
+		foreach(lc, afterTriggers.prolonged_tuplestores)
+		{
+			ts = (Tuplestorestate *) lfirst(lc);
+			if (ts)
+				tuplestore_end(ts);
+		}
+		afterTriggers.prolonged_tuplestores = NIL;
+	}
 }
 
 
@@ -6024,6 +6085,9 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 		else
 			new_shared.ats_table = NULL;
 
+		if (new_shared.ats_table != NULL && trigger->tgfoid == F_IVM_IMMEDIATE_MAINTENANCE)
+			new_shared.ats_table->prolonged = true;
+
 		afterTriggerAddEvent(&afterTriggers.query_stack[afterTriggers.query_depth].events,
 							 &new_event, &new_shared);
 	}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb921d..a872b59f13 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2365,6 +2365,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(relisivm);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014373..503915b9ef 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2651,6 +2651,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(relisivm);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b0dcd02ff6..52e898d615 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3058,6 +3058,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 764e3bb90c..3301ccfbc8 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1366,6 +1366,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf30e..2ccdda580a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -418,6 +418,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target create_mv_target
+%type <boolean>	incremental
 
 %type <defelt>	createfunc_opt_item common_func_opt_item dostmt_opt_item
 %type <fun_param> func_arg func_arg_with_default table_func_column aggr_arg
@@ -645,7 +646,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
 	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
-	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+	INCLUDING INCREMENT INCREMENTAL INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
 	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
 	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -4083,30 +4084,32 @@ opt_with_data:
  *****************************************************************************/
 
 CreateMatViewStmt:
-		CREATE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
+		CREATE OptNoLog incremental MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $7;
-					ctas->into = $5;
+					ctas->query = $8;
+					ctas->into = $6;
 					ctas->relkind = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = false;
 					/* cram additional flags into the IntoClause */
-					$5->rel->relpersistence = $2;
-					$5->skipData = !($8);
+					$6->rel->relpersistence = $2;
+					$6->skipData = !($9);
+					$6->ivm = $3;
 					$$ = (Node *) ctas;
 				}
-		| CREATE OptNoLog MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
+		| CREATE OptNoLog incremental MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $10;
-					ctas->into = $8;
+					ctas->query = $11;
+					ctas->into = $9;
 					ctas->relkind = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = true;
 					/* cram additional flags into the IntoClause */
-					$8->rel->relpersistence = $2;
-					$8->skipData = !($11);
+					$9->rel->relpersistence = $2;
+					$9->skipData = !($12);
+					$9->ivm = $3;
 					$$ = (Node *) ctas;
 				}
 		;
@@ -4123,9 +4126,14 @@ create_mv_target:
 					$$->tableSpaceName = $5;
 					$$->viewQuery = NULL;		/* filled at analysis time */
 					$$->skipData = false;		/* might get changed later */
+					$$->ivm = false;
 				}
 		;
 
+incremental:	INCREMENTAL				{ $$ = true; }
+				| /*EMPTY*/				{ $$ = false; }
+		;
+
 OptNoLog:	UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
 		;
@@ -15157,6 +15165,7 @@ unreserved_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 4dd81507a7..a6c8c3fd4d 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -37,6 +37,7 @@
 #include "utils/syscache.h"
 #include "utils/varlena.h"
 
+#include "commands/matview.h"
 
 #define MAX_FUZZY_DISTANCE				3
 
@@ -56,9 +57,10 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							int count, int offset,
 							int rtindex, int sublevels_up,
 							int location, bool include_dropped,
-							List **colnames, List **colvars);
+							List **colnames, List **colvars, bool is_ivm);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool isIvmColumn(const char *s);
 
 
 /*
@@ -1241,6 +1243,7 @@ addRangeTableEntry(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -1320,6 +1323,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -2318,7 +2322,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 						expandTupleDesc(tupdesc, rte->eref,
 										rtfunc->funccolcount, atts_done,
 										rtindex, sublevels_up, location,
-										include_dropped, colnames, colvars);
+										include_dropped, colnames, colvars, false);
 					}
 					else if (functypclass == TYPEFUNC_SCALAR)
 					{
@@ -2570,10 +2574,16 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
 	expandTupleDesc(rel->rd_att, eref, rel->rd_att->natts, 0,
 					rtindex, sublevels_up,
 					location, include_dropped,
-					colnames, colvars);
+					colnames, colvars, RelationIsIVM(rel));
 	relation_close(rel, AccessShareLock);
 }
 
+static bool
+isIvmColumn(const char *s)
+{
+	return (strncmp(s, "__ivm_", 6) == 0); 
+}
+
 /*
  * expandTupleDesc -- expandRTE subroutine
  *
@@ -2587,7 +2597,7 @@ static void
 expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 				int rtindex, int sublevels_up,
 				int location, bool include_dropped,
-				List **colnames, List **colvars)
+				List **colnames, List **colvars, bool is_ivm)
 {
 	ListCell   *aliascell;
 	int			varattno;
@@ -2600,6 +2610,9 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 	{
 		Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
 
+		if (is_ivm && isIvmColumn(NameStr(attr->attname)) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		if (attr->attisdropped)
 		{
 			if (include_dropped)
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 7df2b6154c..5385a038b1 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -765,7 +765,8 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 														attr->atttypmod))));
 	}
 
-	if (i != resultDesc->natts)
+	/* No check for materialized views since this could have special columns for IVM */
+	if ((!isSelect || requireColumnNameMatch) && i != resultDesc->natts)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 isSelect ?
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 3e38007643..5b71f0c665 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -41,6 +41,8 @@
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 
+#include "parser/parser.h"
+#include "commands/matview.h"
 
 /* We use a list of these to detect recursion in RewriteQuery */
 typedef struct rewrite_event
@@ -1597,6 +1599,50 @@ ApplyRetrieveRule(Query *parsetree,
 	if (rule->qual != NULL)
 		elog(ERROR, "cannot handle qualified ON SELECT rule");
 
+	if (RelationIsIVM(relation))
+	{
+		rule_action = copyObject(linitial(rule->actions));
+
+		if (!rule_action->distinctClause && !rule_action->groupClause && !rule_action->hasAggs)
+		{
+			StringInfoData str;
+			RawStmt *raw;
+			Query *sub;
+
+			if (rule_action->hasDistinctOn)
+				elog(ERROR, "DISTINCT ON is not supported in IVM");
+
+			initStringInfo(&str);
+			appendStringInfo(&str, "SELECT mv.*, __ivm_count__ FROM %s mv, generate_series(1, mv.__ivm_count__)",
+						quote_qualified_identifier(get_namespace_name(RelationGetNamespace(relation)),
+													RelationGetRelationName(relation)));
+
+			raw = (RawStmt*)linitial(raw_parser(str.data));
+			sub = transformStmt(make_parsestate(NULL),raw->stmt);
+
+			rte = rt_fetch(rt_index, parsetree->rtable);
+
+			rte->rtekind = RTE_SUBQUERY;
+			rte->subquery = sub;
+			rte->security_barrier = RelationIsSecurityView(relation);
+			/* Clear fields that should not be set in a subquery RTE */
+			rte->relid = InvalidOid;
+			rte->relkind = 0;
+			rte->rellockmode = 0;
+			rte->tablesample = NULL;
+			rte->inh = false;			/* must not be set for a subquery */
+
+			rte->requiredPerms = 0;		/* no permission check on subquery itself */
+			rte->checkAsUser = InvalidOid;
+			rte->selectedCols = NULL;
+			rte->insertedCols = NULL;
+			rte->updatedCols = NULL;
+			rte->extraUpdatedCols = NULL;
+		}
+
+		return parsetree;
+	}
+
 	if (rt_index == parsetree->resultRelation)
 	{
 		/*
@@ -1906,7 +1952,8 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
 		 * In that case this test would need to be postponed till after we've
 		 * opened the rel, so that we could check its state.
 		 */
-		if (rte->relkind == RELKIND_MATVIEW)
+		if (rte->relkind == RELKIND_MATVIEW &&
+			(!rte->relisivm || MatViewIncrementalMaintenanceIsEnabled() || parsetree->commandType != CMD_SELECT))
 			continue;
 
 		/*
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 27602fa49c..694e01b70f 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1844,6 +1844,30 @@ get_rel_relispartition(Oid relid)
 		return false;
 }
 
+/*
+ * get_rel_relisivm
+ *
+ *		Returns the relisivm flag associated with a given relation.
+ */
+bool
+get_rel_relisivm(Oid relid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp);
+		bool		result;
+
+		result = reltup->relisivm;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return false;
+}
+
 /*
  * get_rel_tablespace
  *
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 585dcee5db..db04cc98b9 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1860,6 +1860,8 @@ formrdesc(const char *relationName, Oid relationReltype,
 
 	/* ... and they're always populated, too */
 	relation->rd_rel->relispopulated = true;
+	/* ... and they're always no ivm, too */
+	relation->rd_rel->relisivm = false;
 
 	relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
 	relation->rd_rel->relpages = 0;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index d7c0fc0c1e..a767b50f26 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1492,6 +1492,7 @@ describeOneTableDetails(const char *schemaname,
 		char		relpersistence;
 		char		relreplident;
 		char	   *relam;
+		bool		isivm;
 	}			tableinfo;
 	bool		show_column_details = false;
 
@@ -1512,6 +1513,7 @@ describeOneTableDetails(const char *schemaname,
 						  "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
 						  "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
 						  "c.relpersistence, c.relreplident, am.amname\n"
+						  ",c.relisivm\n"
 						  "FROM pg_catalog.pg_class c\n "
 						  "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
 						  "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
@@ -1688,6 +1690,10 @@ describeOneTableDetails(const char *schemaname,
 			(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
 	else
 		tableinfo.relam = NULL;
+	if (pset.sversion >= 130000)
+		tableinfo.isivm = strcmp(PQgetvalue(res, 0, 15), "t") == 0;
+	else
+		tableinfo.isivm = false;
 	PQclear(res);
 	res = NULL;
 
@@ -3292,6 +3298,12 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
 			printTableAddFooter(&cont, buf.data);
 		}
+
+		/* Incremental view maintance info */
+		if (verbose && tableinfo.relkind == RELKIND_MATVIEW && tableinfo.isivm)
+		{
+			printTableAddFooter(&cont, _("Incremental view maintenance: yes"));
+		}
 	}
 
 	/* reloptions, if verbose */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index cb12668276..474b0d482a 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1001,6 +1001,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"INCREMENTAL MATERIALIZED VIEW", NULL, NULL, &Query_for_list_of_matviews},
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2499,7 +2500,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
-		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
+		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW", "INCREMENTAL MATERIALIZED VIEW");
 	/* Complete PARTITION BY with RANGE ( or LIST ( or ... */
 	else if (TailMatches("PARTITION", "BY"))
 		COMPLETE_WITH("RANGE (", "LIST (", "HASH (");
@@ -2749,13 +2750,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches("CREATE", "MATERIALIZED"))
+	else if (Matches("CREATE", "MATERIALIZED") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED"))
 		COMPLETE_WITH("VIEW");
-	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	/* Complete CREATE MATERIALIZED VIEW <name> with AS  */
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny) ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH("SELECT");
 
 /* CREATE EVENT TRIGGER */
diff --git a/src/include/catalog/pg_class.dat b/src/include/catalog/pg_class.dat
index 9bcf28676d..7deda405af 100644
--- a/src/include/catalog/pg_class.dat
+++ b/src/include/catalog/pg_class.dat
@@ -27,7 +27,7 @@
   relpersistence => 'p', relkind => 'r', relnatts => '31', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 { oid => '1249',
@@ -37,7 +37,7 @@
   relpersistence => 'p', relkind => 'r', relnatts => '25', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 { oid => '1255',
@@ -47,17 +47,17 @@
   relpersistence => 'p', relkind => 'r', relnatts => '29', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 { oid => '1259',
   relname => 'pg_class', reltype => 'pg_class', relam => 'heap',
   relfilenode => '0', relpages => '0', reltuples => '0', relallvisible => '0',
   reltoastrelid => '0', relhasindex => 'f', relisshared => 'f',
-  relpersistence => 'p', relkind => 'r', relnatts => '33', relchecks => '0',
+  relpersistence => 'p', relkind => 'r', relnatts => '34', relchecks => '0',
   relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
   relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
-  relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
+  relreplident => 'n', relispartition => 'f', relisivm => 'f', relfrozenxid => '3',
   relminmxid => '1', relacl => '_null_', reloptions => '_null_',
   relpartbound => '_null_' },
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 090b6ba907..ff535f5504 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
 	/* is relation a partition? */
 	bool		relispartition;
 
+	/* is relation a matview with ivm? */
+	bool		relisivm;
+
 	/* heap for rewrite during DDL, link to original rel */
 	Oid			relrewrite BKI_DEFAULT(0);
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 58ea5b982b..d0ad20f89c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10731,4 +10731,13 @@
   proname => 'pg_partition_root', prorettype => 'regclass',
   proargtypes => 'regclass', prosrc => 'pg_partition_root' },
 
+# IVM
+{ oid => '786', descr => 'ivm trigger (before)',
+  proname => 'IVM_immediate_before', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_before' },
+{ oid => '787', descr => 'ivm trigger (after)',
+  proname => 'IVM_immediate_maintenance', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_maintenance' },
+
+
 ]
diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h
index edf04bf415..edf6ddaebe 100644
--- a/src/include/commands/matview.h
+++ b/src/include/commands/matview.h
@@ -23,6 +23,8 @@
 
 extern void SetMatViewPopulatedState(Relation relation, bool newstate);
 
+extern void SetMatViewIVMState(Relation relation, bool newstate);
+
 extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										ParamListInfo params, char *completionTag);
 
@@ -30,4 +32,8 @@ extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
 
 extern bool MatViewIncrementalMaintenanceIsEnabled(void);
 
+extern Datum IVM_immediate_before(PG_FUNCTION_ARGS);
+extern Datum IVM_immediate_maintenance(PG_FUNCTION_ARGS);
+extern void AtAbort_IVM(void);
+
 #endif							/* MATVIEW_H */
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index a46feeedb0..3e63733d3e 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -253,6 +253,8 @@ extern void AfterTriggerEndSubXact(bool isCommit);
 extern void AfterTriggerSetState(ConstraintsSetStmt *stmt);
 extern bool AfterTriggerPendingOnRel(Oid relid);
 
+extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType);
+
 
 /*
  * in utils/adt/ri_triggers.c
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a554..637d9bacb9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1002,6 +1002,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		relisivm;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -2059,6 +2060,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		ivm;			/* incremental view maintenance is used by materialized view */
 } CreateStmt;
 
 /* ----------
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 860a84de7c..6df58e2ff9 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -117,6 +117,7 @@ typedef struct IntoClause
 	char	   *tableSpaceName; /* table space to use, or NULL */
 	Node	   *viewQuery;		/* materialized view's SELECT query */
 	bool		skipData;		/* true for WITH NO DATA */
+	bool		ivm;			/* true for WITH IVM */
 } IntoClause;
 
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 00ace8425e..d682ee11cc 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -198,6 +198,7 @@ PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
 PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
+PG_KEYWORD("incremental", INCREMENTAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
 PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index c8df5bff9f..8fd919349e 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -129,6 +129,7 @@ extern Oid	get_rel_namespace(Oid relid);
 extern Oid	get_rel_type_id(Oid relid);
 extern char get_rel_relkind(Oid relid);
 extern bool get_rel_relispartition(Oid relid);
+extern bool get_rel_relisivm(Oid relid);
 extern Oid	get_rel_tablespace(Oid relid);
 extern char get_rel_persistence(Oid relid);
 extern Oid	get_transform_fromsql(Oid typid, Oid langid, List *trftypes);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index a5cf804f9f..3322eb0794 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -567,6 +567,8 @@ typedef struct ViewOptions
  */
 #define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated)
 
+#define RelationIsIVM(relation) ((relation)->rd_rel->relisivm)
+
 /*
  * RelationIsAccessibleInLogicalDecoding
  *		True if we need to log enough information to have access via
diff --git a/src/test/regress/expected/incremental_matview.out b/src/test/regress/expected/incremental_matview.out
new file mode 100644
index 0000000000..153b4401e1
--- /dev/null
+++ b/src/test/regress/expected/incremental_matview.out
@@ -0,0 +1,450 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- immediaite maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- result of materliazied view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 20
+ 30
+ 40
+ 50
+(6 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+ROLLBACK;
+-- support SUM(), COUNT() and AVG() aggregation function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i),AVG(j)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 | 120 |     2 | 60.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg          
+---+-----+-------+----------------------
+ 1 |  10 |     1 |  10.0000000000000000
+ 2 | 220 |     2 | 110.0000000000000000
+ 3 |  30 |     1 |  30.0000000000000000
+ 4 |  40 |     1 |  40.0000000000000000
+ 5 |  50 |     1 |  50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support COUNT(*) aggregation function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j),COUNT(*)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 |  20 |     1
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 | 120 |     2
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+ROLLBACK;
+-- support having only aggregation function without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j)FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum 
+-----
+ 150
+(1 row)
+
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum 
+-----
+ 170
+(1 row)
+
+ROLLBACK;
+-- resolved issue: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     3 |  3.3333333333333333
+ 2 |  80 |     3 | 26.6666666666666667
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support MIN(), MAX() aggregation functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT i, MIN(j), MAX(j)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  10 |  10
+ 2 |  20 |  20
+ 3 |  30 |  30
+ 4 |  40 |  40
+ 5 |  50 |  50
+(5 rows)
+
+INSERT INTO mv_base_a VALUES
+  (1,11), (1,12),
+  (2,21), (2,22),
+  (3,31), (3,32),
+  (4,41), (4,42),
+  (5,51), (5,52);
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  10 |  12
+ 2 |  20 |  22
+ 3 |  30 |  32
+ 4 |  40 |  42
+ 5 |  50 |  52
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  11 |  12
+ 2 |  20 |  22
+ 3 |  30 |  31
+ 4 |  40 |  42
+ 5 |  50 |  52
+(5 rows)
+
+ROLLBACK;
+-- support MIN(), MAX() aggregation functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT MIN(j), MAX(j)  FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+  10 |  50
+(1 row)
+
+INSERT INTO mv_base_a VALUES
+  (0,0), (6,60), (7,70);
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+   0 |  70
+(1 row)
+
+DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+  10 |  60
+(1 row)
+
+ROLLBACK;
+-- support self join view and multiple change on the same table
+BEGIN;
+CREATE TABLE t (i int, v int);
+INSERT INTO t VALUES (1, 10), (2, 20), (3, 30);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_self(v1, v2) AS
+ SELECT t1.v, t2.v FROM t t1 JOIN t t2 ON t1.i = t2.i;
+SELECT * FROM mv_self ORDER BY v1;
+ v1 | v2 
+----+----
+ 10 | 10
+ 20 | 20
+ 30 | 30
+(3 rows)
+
+INSERT INTO t VALUES (4,40);
+DELETE FROM t WHERE i = 1;
+UPDATE t SET v = v*10 WHERE i=2;
+SELECT * FROM mv_self ORDER BY v1;
+ v1  | v2  
+-----+-----
+  30 |  30
+  40 |  40
+ 200 | 200
+(3 rows)
+
+WITH
+ ins_t1 AS (INSERT INTO t VALUES (5,50) RETURNING 1),
+ ins_t2 AS (INSERT INTO t VALUES (6,60) RETURNING 1),
+ upd_t AS (UPDATE t SET v = v + 100  RETURNING 1),
+ dlt_t AS (DELETE FROM t WHERE i IN (4,5)  RETURNING 1)
+SELECT NULL;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT * FROM mv_self ORDER BY v1;
+ v1  | v2  
+-----+-----
+  50 |  50
+  60 |  60
+ 130 | 130
+ 300 | 300
+(4 rows)
+
+ROLLBACK;
+-- support simultaneous table changes
+BEGIN;
+CREATE TABLE r (i int, v int);
+CREATE TABLE s (i int, v int);
+INSERT INTO r VALUES (1, 10), (2, 20), (3, 30);
+INSERT INTO s VALUES (1, 100), (2, 200), (3, 300);
+CREATE INCREMENTAL MATERIALIZED VIEW mv(v1, v2) AS
+ SELECT r.v, s.v FROM r JOIN s USING(i);
+SELECT * FROM mv ORDER BY v1;
+ v1 | v2  
+----+-----
+ 10 | 100
+ 20 | 200
+ 30 | 300
+(3 rows)
+
+WITH
+ ins_r AS (INSERT INTO r VALUES (1,11) RETURNING 1),
+ ins_r2 AS (INSERT INTO r VALUES (3,33) RETURNING 1),
+ ins_s AS (INSERT INTO s VALUES (2,222) RETURNING 1),
+ upd_r AS (UPDATE r SET v = v + 1000 WHERE i = 2 RETURNING 1),
+ dlt_s AS (DELETE FROM s WHERE i = 3 RETURNING 1)
+SELECT NULL;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT * FROM mv ORDER BY v1;
+  v1  | v2  
+------+-----
+   10 | 100
+   11 | 100
+ 1020 | 200
+ 1020 | 222
+(4 rows)
+
+ROLLBACK;
+-- support foreign reference constrains
+BEGIN;
+CREATE TABLE ri1 (i int PRIMARY KEY);
+CREATE TABLE ri2 (i int PRIMARY KEY REFERENCES ri1(i) ON UPDATE CASCADE ON DELETE CASCADE, v int);
+INSERT INTO ri1 VALUES (1),(2),(3);
+INSERT INTO ri2 VALUES (1),(2),(3);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ri(i1, i2) AS
+ SELECT ri1.i, ri2.i FROM ri1 JOIN ri2 USING(i);
+SELECT * FROM mv_ri ORDER BY i1;
+ i1 | i2 
+----+----
+  1 |  1
+  2 |  2
+  3 |  3
+(3 rows)
+
+UPDATE ri1 SET i=10 where i=1;
+DELETE FROM ri1 WHERE i=2;
+SELECT * FROM mv_ri ORDER BY i2;
+ i1 | i2 
+----+----
+  3 |  3
+ 10 | 10
+(2 rows)
+
+ROLLBACK;
+-- contain system column
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm01 AS SELECT i,j,xmin FROM mv_base_a;
+ERROR:  system column is not supported with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+ERROR:  system column is not supported with IVM
+-- contain subquery
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm03 AS SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );
+ERROR:  subquery is not supported with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT a.i,a.j FROM mv_base_a a,( SELECT * FROM mv_base_b) b WHERE a.i = b.i;
+ERROR:  subquery is not supported with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm05 AS SELECT i,j, (SELECT k FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+ERROR:  subquery is not supported with IVM
+-- contain CTE
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm06 AS WITH b AS (SELECT i,k FROM mv_base_b WHERE k < 103) SELECT a.i,a.j FROM mv_base_a a,b WHERE a.i = b.i;
+ERROR:  CTE is not supported with IVM
+-- contain view or materialized view
+CREATE VIEW b_view AS SELECT i,k FROM mv_base_b;
+CREATE MATERIALIZED VIEW b_mview AS SELECT i,k FROM mv_base_b;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm07 AS SELECT a.i,a.j FROM mv_base_a a,b_view b WHERE a.i = b.i;
+ERROR:  VIEW or MATERIALIZED VIEW is not supported with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm08 AS SELECT a.i,a.j FROM mv_base_a a,b_mview b WHERE a.i = b.i;
+ERROR:  VIEW or MATERIALIZED VIEW is not supported with IVM
+DROP TABLE mv_base_b CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to materialized view mv_ivm_1
+drop cascades to view b_view
+drop cascades to materialized view b_mview
+DROP TABLE mv_base_a CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index fc0f14122b..4b5d448098 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -78,7 +78,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tidscan collate.icu.utf8
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tidscan collate.icu.utf8 incremental_matview
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 68ac56acdb..0f6f8f503f 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -112,6 +112,7 @@ test: init_privs
 test: security_label
 test: collate
 test: matview
+test: incremental_matview
 test: lock
 test: replica_identity
 test: rowsecurity
diff --git a/src/test/regress/sql/incremental_matview.sql b/src/test/regress/sql/incremental_matview.sql
new file mode 100644
index 0000000000..d815bd8662
--- /dev/null
+++ b/src/test/regress/sql/incremental_matview.sql
@@ -0,0 +1,180 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i);
+
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+-- immediaite maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+
+-- result of materliazied view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ROLLBACK;
+
+-- support SUM(), COUNT() and AVG() aggregation function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i),AVG(j)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ROLLBACK;
+
+-- support COUNT(*) aggregation function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j),COUNT(*)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support having only aggregation function without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j)FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ROLLBACK;
+
+-- resolved issue: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support MIN(), MAX() aggregation functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT i, MIN(j), MAX(j)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES
+  (1,11), (1,12),
+  (2,21), (2,22),
+  (3,31), (3,32),
+  (4,41), (4,42),
+  (5,51), (5,52);
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support MIN(), MAX() aggregation functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT MIN(j), MAX(j)  FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+INSERT INTO mv_base_a VALUES
+  (0,0), (6,60), (7,70);
+SELECT * FROM mv_ivm_min_max;
+DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
+SELECT * FROM mv_ivm_min_max;
+ROLLBACK;
+
+-- support self join view and multiple change on the same table
+BEGIN;
+CREATE TABLE t (i int, v int);
+INSERT INTO t VALUES (1, 10), (2, 20), (3, 30);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_self(v1, v2) AS
+ SELECT t1.v, t2.v FROM t t1 JOIN t t2 ON t1.i = t2.i;
+SELECT * FROM mv_self ORDER BY v1;
+INSERT INTO t VALUES (4,40);
+DELETE FROM t WHERE i = 1;
+UPDATE t SET v = v*10 WHERE i=2;
+SELECT * FROM mv_self ORDER BY v1;
+WITH
+ ins_t1 AS (INSERT INTO t VALUES (5,50) RETURNING 1),
+ ins_t2 AS (INSERT INTO t VALUES (6,60) RETURNING 1),
+ upd_t AS (UPDATE t SET v = v + 100  RETURNING 1),
+ dlt_t AS (DELETE FROM t WHERE i IN (4,5)  RETURNING 1)
+SELECT NULL;
+SELECT * FROM mv_self ORDER BY v1;
+ROLLBACK;
+
+-- support simultaneous table changes
+BEGIN;
+CREATE TABLE r (i int, v int);
+CREATE TABLE s (i int, v int);
+INSERT INTO r VALUES (1, 10), (2, 20), (3, 30);
+INSERT INTO s VALUES (1, 100), (2, 200), (3, 300);
+CREATE INCREMENTAL MATERIALIZED VIEW mv(v1, v2) AS
+ SELECT r.v, s.v FROM r JOIN s USING(i);
+SELECT * FROM mv ORDER BY v1;
+WITH
+ ins_r AS (INSERT INTO r VALUES (1,11) RETURNING 1),
+ ins_r2 AS (INSERT INTO r VALUES (3,33) RETURNING 1),
+ ins_s AS (INSERT INTO s VALUES (2,222) RETURNING 1),
+ upd_r AS (UPDATE r SET v = v + 1000 WHERE i = 2 RETURNING 1),
+ dlt_s AS (DELETE FROM s WHERE i = 3 RETURNING 1)
+SELECT NULL;
+SELECT * FROM mv ORDER BY v1;
+ROLLBACK;
+
+-- support foreign reference constrains
+BEGIN;
+CREATE TABLE ri1 (i int PRIMARY KEY);
+CREATE TABLE ri2 (i int PRIMARY KEY REFERENCES ri1(i) ON UPDATE CASCADE ON DELETE CASCADE, v int);
+INSERT INTO ri1 VALUES (1),(2),(3);
+INSERT INTO ri2 VALUES (1),(2),(3);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ri(i1, i2) AS
+ SELECT ri1.i, ri2.i FROM ri1 JOIN ri2 USING(i);
+SELECT * FROM mv_ri ORDER BY i1;
+UPDATE ri1 SET i=10 where i=1;
+DELETE FROM ri1 WHERE i=2;
+SELECT * FROM mv_ri ORDER BY i2;
+ROLLBACK;
+
+-- contain system column
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm01 AS SELECT i,j,xmin FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+-- contain subquery
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm03 AS SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT a.i,a.j FROM mv_base_a a,( SELECT * FROM mv_base_b) b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm05 AS SELECT i,j, (SELECT k FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+-- contain CTE
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm06 AS WITH b AS (SELECT i,k FROM mv_base_b WHERE k < 103) SELECT a.i,a.j FROM mv_base_a a,b WHERE a.i = b.i;
+-- contain view or materialized view
+CREATE VIEW b_view AS SELECT i,k FROM mv_base_b;
+CREATE MATERIALIZED VIEW b_mview AS SELECT i,k FROM mv_base_b;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm07 AS SELECT a.i,a.j FROM mv_base_a a,b_view b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm08 AS SELECT a.i,a.j FROM mv_base_a a,b_mview b WHERE a.i = b.i;
+
+DROP TABLE mv_base_b CASCADE;
+DROP TABLE mv_base_a CASCADE;
#36Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo Nagata (#35)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Attached is the latest patch to add support for Incremental
Materialized View Maintenance (IVM). IVM allows to reflect
modifications made on base tables immediately to the target
materialized views.

Up to now, IVM supports materialized views using:

- Inner joins
- Some aggregate functions (count, sum, min, max, avg)
- GROUP BY
- Self joins

With the latest patch now IVM supports subqueries in addition to
above.

Known limitations are listed here:

https://github.com/sraoss/pgsql-ivm/issues

See more details at:
https://wiki.postgresql.org/wiki/Incremental_View_Maintenance

About subquery support:

The patch supports simple subqueries using EXISTS:

CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_exists_subquery AS SELECT
a.i, a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE
a.i = b.i);

and subqueries in the FROM clause:

CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_subquery AS SELECT a.i,a.j
FROM mv_base_a a,( SELECT * FROM mv_base_b) b WHERE a.i = b.i;

Other form of subqueries such as below are not supported:

-- WHERE IN .. (subquery) is not supported
CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm03 AS SELECT i,j FROM
mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );

-- subqueries in target list is not supported
CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm05 AS SELECT i,j, (SELECT k
FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;

-- nested EXISTS subqueries is not supported
CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm11 AS SELECT a.i,a.j FROM
mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE EXISTS(SELECT
1 FROM mv_base_b c WHERE b.i = c.i));

-- EXISTS subquery with aggragate function is not supported
CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_exists AS SELECT COUNT(*)
FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i =
b.i) OR a.i > 5;

-- EXISTS subquery with condition except AND is not supported.
CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm10 AS SELECT a.i,a.j FROM
mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) OR
a.i > 5;

This work has been done by Yugo Nagata (nagata@sraoss.co.jp), Takuma
Hoshiai (hoshiai@sraoss.co.jp). Adding support for EXISTS clause has
been done by Takuma.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

Attachments:

IVM_v7.patch.gzapplication/octet-streamDownload
#37Yugo Nagata
nagata@sraoss.co.jp
In reply to: Tatsuo Ishii (#36)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

Attached is the latest patch (v8) to add support for Incremental View
Maintenance (IVM). This patch adds OUTER join support in addition
to the patch (v7) submitted last week in the following post.

On Fri, 22 Nov 2019 15:29:45 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

Up to now, IVM supports materialized views using:

- Inner joins
- Some aggregate functions (count, sum, min, max, avg)
- GROUP BY
- Self joins

With the latest patch now IVM supports subqueries in addition to
above.

Known limitations are listed here:

https://github.com/sraoss/pgsql-ivm/issues

See more details at:
https://wiki.postgresql.org/wiki/Incremental_View_Maintenance

* About outer join support:

In case of outer-join, when a table is modified, in addition to deltas
which occur in inner-join case, we also need to deletion or insertion of
dangling tuples, that is, null-extended tuples generated when a join
condition isn't met.

[Example]
---------------------------------------------
-- Create base tables and an outer join view
CREATE TABLE r(i int);
CREATE TABLE s(j int);
INSERT INTO r VALUES (1);
CREATE INCREMENTAL MATERIALIZED VIEW mv
AS SELECT * FROM r LEFT JOIN s ON r.i=s.j;
SELECT * FROM mv;
i | j
---+---
(1 row)

-- After an insertion to a base table ...
INSERT INTO s VALUES (1);
-- (1,1) is inserted and (1,null) is deleted from the view.
SELECT * FROM mv;
i | j
---+---
1 | 1
(1 row)
---------------------------------------------

Our implementation is basically based on the algorithm of Larson & Zhou
(2007) [1]Efficient Maintenance of Materialized Outer-Join Views (Larson & Zhou, 2007) https://ieeexplore.ieee.org/document/4221654. Before view maintenances, the view definition query's jointree
is analysed to make "view maintenance graph". This graph represents
which tuples in the views are affected when a base table is modified.
Specifically, tuples which are not null-extended on the modified table
(that is, tuples generated by joins with the modiifed table) are directly
affected. The delta of such effects are calculated similarly to inner-joins.

On the other hand, dangling tuples generated by anti-joins with directly
affected tuples can be indirectly affected. This means that we may need to
delete dangling tuples when any tuples are inserted to a table, as well as
to insert dangling tuples when tuples are deleted from a table.

[1]: Efficient Maintenance of Materialized Outer-Join Views (Larson & Zhou, 2007) https://ieeexplore.ieee.org/document/4221654
https://ieeexplore.ieee.org/document/4221654

Although the original paper assumes that every base table and view have a
unique key and tuple duplicates is disallowed, we allow this. If a view has
tuple duplicates, we have to determine the number of each dangling tuple to
be inserted into the view when tuples in a table are deleted. For this purpose,
we count the number of each tuples which constitute a deleted tuple. These
counts are stored as JSONB object in the delta table, and we use this
information to maintain outer-join views. Also, we support outer self-joins
that is not assumed in the original paper.

* Restrictions

Currently, we have following restrictions:

- outer join view's targetlist must contain attributes used in join conditions
- outer join view's targetlist cannot contain non-strict functions
- outer join supports only simple equijoin
- outer join view's WHERE clause cannot contain non null-rejecting predicates
- aggregate is not supported with outer join
- subquery (including EXSITS) is not supported with outer join

Regression tests for all patterns of 3-way outer join and are added.

Moreover, I reordered IVM related functions in matview.c so that ones
which have relationship will be located closely. Moreover, I added more
comments.

Regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

IVM_v8.patch.gzapplication/gzip; name=IVM_v8.patch.gzDownload
�#��]IVM_v8.patch�<kw�6���_�fw2�%KV�����Q%��c�������C����">����������69s&��D����\��'��3�S����m'1�?�m�I� �&-|�
�[~��������}w�����w�v���������������3l5�����b�s��Y�@4���'�mx>������y����Ob9W:�@�=
�~*c'��P�Q�O�����	�}e���'2L���(���'"�n��7�'�s'N}�}�f`5&���a�����(�=I�8s��/�,����w�����#��Lsy6����M?���n���9@�	���'�|y/d�[L`�n,g��1s�0�����j�
�o������>����X�����;��Jm,'mX>l�6wh�FVJ���tK��h�Pz�� ��������&�~�
�xj��N�P4���$��r���\q��Y"'�"����x�P�D?�m��z�QO���A�{����+����I�"�����H�>�����U�������,��VCa�����m�r����_o��_�U�v� ��+��/M�j�~u���n��|S�M���$�O@����|�D^Y�����a=�#I���J;o����!wN�Uv�����=�8<�G��G$_����`����^ZI-$d���Q`�c)l�'�^�����I@6983D���1��:f��A��&�?�\�>�)�h&E��)�O8�N�F�F���B�^y'�'�)�`%[4��e�����Lz>t�l������Hv�����F��D?S���0
�I�j����
Pjtk����}AE��4�X��5���a��A�{?�[
��[Xan�M��0��Hf�?�`�}���S&�����}n�����H��~A�9����a�p���F,��>�i(H�B�V����QxB ����6|^�y0�Hd0���z��.Gy�N��Y�(�@g
&{�0i,
M@��YS8w����!|8���0DY*~��G�6��ae��v���Iw\���?�SEd_&4�#sE0i�����*���RM�oB�l�v�i�8�]�}�Q�6E��F
'����~!���"(���|�k��|u�����q�"B�L������E`�dI1����KO��'�l����-iK��A��@��+�R����1��W�-t�'�W����s��h6C�M�������]4q7h#������I4�mt>|��J?�-��;5�l>�b4q���l���
� ��uliilu�I�
Q Y	�M%D2�� �J�R^�I��qoe���U����N�8��g�M[.$����h"����=��������>�&���a\��<�A����1���/����C��|j�0��n���U�w��e>i;����X�	�D2�f,��;���QU�I�`U�d�c%r{�M�����duD� h�A�[��nJ]�e����UAm��gd@����['H@u����br�>�oe��0��}4�MGD�����-a��6|�m���d��*X��l=�C|6f��@�	2�l�&CM�:$�%S3�F:�%�,w*Y�]�;�v'��;>�Gcy�����
�e!� c��w�/�El��	P�zz�)�(�`i���)s�=��M�em�/]�N;�,�>����*N����t���F���4�Y��P;�)	�l�I�&����b�k>b#^`����6R�l5NMb�#ni|/�(X���ha�DX������TXY��k�@`5?!"	�����T�=��d��pr�w��Nd�y��<�{��i,��OC��sF�_$}�gsQ���~@�#(�$-H�c�sZ��b#g�\���-����I~H�"�{��=�i��f�{}5P	�&<(B���[�B���L��'�6
�����5��I�%`BI����p�h��s�ju:��������
R��
IXE�;d���Y��
���6P��jT{P�P����|6����p���Z���u�s�`�6�6m�KcY�lv��l�m��W���+�������<��t]��~Uz1E�X0n�������V+��!��u�^�j��f1?���	>B����p(qx�������^f�-��a��Cc��(�C�v
j��-�E��@�/��[�
�����xQ�P��0�������p"�(5:�)>_�
j�8����bC��pom�v�Hm7
S�9}��3�	�������2��Qwh�q��?p3�-�U^��y�=QY����(���C'�pYb�I�5����91@l#5�I=�
�F�J�u��q�����fg�P#��">������(�a: ��N��)^�C8zc�@�x��,���[��!���[
s�9�7�����?��&J����q�������vHl��>���6M�\�/!�%�$��tX��)^4�%���������j�|	����D�����:�j��w�-��g�(�b�M?�AT���1�<����'��!��u5�}���9��������_�(�=�{����#�?'�`5x��Y�)7zJ��
���������J�gJ�Cl��fs�?EIm��O
��@��.�<����w��F��B�~��Kc6��_�����!�B�G)4��e���j���v�L����9"�7Is7���Og�";�fg�}y�`��s��m^���J'9p\�g����m|���M�Z�����S,�L�j�YI*C��u��������,�+����B�Fs_z�r
B�����8 1���1[��-�:����0z4��0�������EKk(�"�S�e��5�x{�/��K:cL���uw�k�����S���#8���
v-��f�T3������D1��v��O��c?��Zm��S��0F�%���
C���qD�=g�
V���-!������O]{
.��	�=	�e��kW����������s6��c�Bmd����|e{���=���Fz�F��lr]�4m��dw5�Q��8����&}S��WS���8�G:�'lZ��M��|7�R�m�F����9������o��b!���Dj.S�6p�����8�z'h5���!�
)�^��p�B��L/F	����H�Mh��Z��l����d��`�H��X%��V�6Ey�1y���u���I�>����4�9��S���/���Z
�E0��$���r���-'���%�%p��U-	U2L�0�QH*���tiK���}(��y�z�?���37�S���k��m�]�AH,����:�yN������NT�)���&V��+O�YUKm1�cY�������zP;��6_&_JhP���!:�������E�U2/9�j����i@@�n����P���������7}�����Q6�rQ�*��`�������!;�^�����Fq������XK��t��OX�������6�X���(��ex<�I>f�m.�2��Vo0��V�\��/� r�������YcI^���$FQ*��v-W��s4eB�q�wK��;�*�\[Ki�]c���Wx#�)���D��V��=H�s�qrI^�7�����O���0���:��^f�1�u!xlt�a�u�1���c�U��f��M�F�9����*�)d)��~x������;�]4� ��{V_���r����F�-�3 *$���p�t�Lj��r{/��
��Jm��)�On��
"?�wX���C���'�_��Z��{<�y�10���5�h�SW�������Z�����I}��s;�~��tK]��
#\��0"��!�Q���� A�N'a���z<0������R
�V�S�o\����v�K�<�
��*���DH�S��q4k��OR�����|X�t<a4��M1�R��}��k� ��P����h
�
��S[��V���r������/�fO��yws��X	�d���f@�n���>
��\��w�~�������r����O�B�.���>�1bfo���Tf�8��K����I�ph��"�����B�����<tBS��V�	$e
��&(�.
+
�4XP�w���O���z�UB�
!����
�Z��rdJH��\�k�PYk��YF����3[O�R�&p�U��oX8�uNu�<�-��MJ�S�B��d���4�_$L�S?�
��;pL�������NJXq��jP;	w��i�ic�%�9�E�<�-�����du�D�����	��{SL����Vx��.�"B���l[*9`4�����Q�Q��k%�`�zV�H.���56y������t��q��Q�Aj+��Z���IqRX�b���������
�t��o��Ac��?K���
&�a)�a�*��X�l��q�Z����K�S�r}~'�j,�6kP�\`T@HM:��������@��$r�H�����������
9���R��?�� ���VdU��S�b5��y��
];=�su�)Kz3����|"! Ke�$��f���;��F��G0U��SG/Z�L7d���0��l�����\�S�UhT�[!gq���������:�"C����q��j��T����iFo��
�/�b
��Qz2�|���r�����`���� 
�)|pJ�)����Qo���Z�9��*�����t�c����""-e�U��.��]Gt��'�tNXYvK%�����(��sG�AF���qym_I���)[��^�<i��1�[|�!��6CyGu�|�R<�
(���EH����_���T��A�A���m��X��8�t�G�_��Mme �`4�5��D�Y���9-v(�w����7�p��8w���MA0^_7gtGV�#F��+s�F+�K�,/�S���v�������YLp]D���Y���Y��>e]��>��j6D*�x�gl�*�
9���A�.u�U�c�Uc��I��������8T�Pu�X��d�����|�F�^9��\�Q����z�&+���:�R���t�=#��
��$��/�{��b8�����;}%'tm�@�6z���������[�j�/Z��v�7����{�\���q�n�x�/
���5�@����"���@�A3�#�xCF��D���L,�M��L]�����qQ��Po����+>�Q��.�����qNg��a�2�x��F���9���Z�d�X+}�A������|������`Ti|�{}5�i��fyF�������~������X���7�������W>	O��[`�M�-Z8U]��y+!�qY�J��^���7���e.���SQ��Vv=�P�|-��}g�������G����U��iK�YX��������}~]�T!l�,��i�^�������,V�[��!�`����
�E���
�dp00�/�6����?�Dk7�O�tyS��7�7��B�G,�#�H������8����]�x��"�n4
�����N��[G�T�"�/F9%OX����:p���w��n��w�.1�.���y���\���(��A�|����K�K> �X����P-�����j_����F�`��\�\������?����������K���<�|���(&�"�n��P���LZ��[|+��.���4Q��O�4k�xN���)#��h��aS�Z�F��Z]���k�;����; \��������!�m���x�/bH��B,Q�u�����h�u�{��&w6T;c����)L�������W��l�����)��(�T�����Ky��P��K�<�f�]�/����J��{��>�+���@�"����5��T�m�>}�����o��4���PQl�w��	`��06��#��Ye��J$� ��qR�T�?�7����������q,����=�%#�$6�;/��
���.��A`l!)36��������-X����I3�VWWWU���.a��
�>�\qU�~�)�X��]�b���z�l��%|QF���g���j6�n�<
��[����3\#��S������
�����}sF��&�n�������Y�t�<��(dEDXV����#�����2�whk�^3�lscICS��k���{9���	�ew7�D�qY������Q���
i{ik�3dH)��%��x�\0��:z�v�{��A�%K����x������u2/�.����u�$��E����Ps��$�������M��}�)������pG	)4c��N!��a���A�4.�sqWq������^DY\��<���9����UK���T�>s����wn1�D�<n����a�u}�-�$�H�;:�Kb���v�_;������q$+��``y��a���V����O�;����T�!��W,fm��q��^�#��a��D�D���h�������3p�^-��P��s�Lz����`�C�^z@�Ho	*��&j��W�Y����Xzr��&���Z4���3S�*�W�VV0CBvl�7$U���`���M��� $>���($�C<%��Y����ya�X��iP���6���_W���52V�y]�a�&���9�h����\U,�v��p�����H�cG<!\���k�hg=���7�%�B�Q��3k3�����vk�-�'5
��
u�F�x�b�B�I#EE�-�t](�B)�l[�?s}W���I}R��O��j��DC��/E������m`QcG�y5�l��I��}��-���j^�������������"��s�����!����K�:�9��� �`E c��Q/	�h(��R�j����?]%���q�2�X��I>���}��X���,�0C�Wl�Bv����c�����[�,�!����l��9�'$���y�);E.��u��N�.e�3������[}A��F�YN��]�|*�Q�����Q�l>�_z���"*_����;��z�_�c��HF��Sm��5;�
7�����Y'@K�&�	x����5��� /[�j[���%�������Y�^�������[�{�X���
������Y��~J!��������j����`���$7�G����8}����6�������C?&]��mP�]C������l[����^B���������C��l���Z��7���jh<�[�}��&�A�'��z3��)��%�N�^Rd{Dg����������^q,��/��
1x�+hA3�Y�C�����39<$��
v���������e� [�g���%��;J���zu-��b�S���������)u�gIW��O�G���k��Q������'Kp�t�M������si������ ��=��
.�H�������$��c��fk:�����M�|��e�0@t~����9�O���G�!�DZ	�T��{�B��<���=`]�[{��wU����v`����|$�:YcfLS�^N,�&�^���9b��K"��T�x�r����O���K	�L"L�L�DfpA=DurJj��sw�R%T+�[M����awfL$��l�/#��!��3��)��b";��*���'*���J�'������lP��w�{����z(a)�B����z�7�p����I-�d��4�w�?�s(:�;��Pv���M����)R�G��BN��������@���]�n$�(���d0��������O�zs��a��<ir{��TJ��q�\���6���<u��yh�,D�&,�n�����P�vP?�u,-6zM��v��^#5.)j�h{Fy��q]Kn>_����WN�xr���#��^	2����G���z�����������������S������_�1��+)������~@y|��cd8�����s����;<y+d�>y�EbU�I����;��b+�)ACr�\�:��B?p����~�26:��rB��^�����#���"\�)1���t�X����H��\�<W=E��p`�s���C�4���=�gc��\P���/���-U��~����5C8I &XV�WO��^5������2IB�[����k�_��������JZ0��������$`� #|�A���[eG%�T��=]
a�	��vJ"]��`P�����x�<qA�,��RuU����5{���2�DaB���-���n�^p���_75�/D�n����q��22�u�]�7�*F�~�^	��������F��v�Q,��
E����S��l��~��`���O������Q�����G���VH6����K�%�v8�N�t�\7��.���C3�>=A�1�AB��m)����9
�������C���J�����Bq���9g��
�N����P����t�O�������t�H���.�(-��w�/wOj��@���<J5M\�]���d<�/_����M���)
���Bu�k>��n�<�;��5�^�'Ft�6����G�P'���,����2��!yjS�V>�y6��,k��+7_����8��]q�5����q6���G���2��Wo���T~���v9|�
���67�[��V�]�`���Fl<S�#�R��J6������P�y�u��<*���<+�+ 
��}uU����������V���;+j��u���~�K��p�h����t8y�>�����c?*�~S���m�q���+��RY��G��N��������C���dh��8��f�-�%3�T3XL���
���D�����2�?�Dbv��?(b~(��R\o4�~����_N��U[�l1����2p-�������`��y\��=�k�����}��-pRk��KUK�xSg�_C���Yh���;���&�/�'o�CO����&p���[�B�a�1��$hp�����U�	?��$��f��K\?���S�/�$4�����1_�kD�����a �S��[
�_H2"��[���M�O��#�0���)��)%�N'7Vwkp}d��(���K���������6;'�jW|��Z���E��������V�^�%ke��T��.��2Y����X�c�@m~m<���L}��r�K&�W�;���>K����~�#��\����V�=�>��m�,�d;{*���6"3$��Y��i�I�`2�l�N�� ���%�{�;!�� �t����S��MWd���Y���Q���=GTS!a��#�L���*����}�����#��"�,��QA`�d0��Zb��~��4T	.�>�rl}
[_8x��a7j�sW;<�VX�3����\��joU_a=l�����P�/UD0���maH�5���c�X�}I�/!�TS���xty%���6���B�u��g���q�Y%��S��2jB��X)��\��g�Q��O�}�����8����Q��
�0�������G��Q��9~��}"-=H�=a�I%�Z�<�����l/G1�1��hLz�S����X{�
>�������G���L0=m3a9�.���S�D��i�&������B�48��Q�Y�	Z�D�6���;���)Q7������2��v:�Z�y�~@��X8 ���l�m�M�����RV`���Z�64r���������#�x(^��5�>�P�-�v��J^�vq��D��K(#�F/�h�v�Q�*#�2��r�1��E����66��xc�x�=h��[#�,m��@F�{Yz�z�u;��-�Ep���0A{��7j�X�~G�Ax���U,�p���������J��*y���`���n�jJ�v&��}�"�t:�������SM!��84�Mi��n���Ke�/_�L����6)�O�����\LN__��������z��q�jC����^+_(�{�Q�E�N�26Y�U���1u��GpNH/c���k��(�u/	���O��X~+j����#���e�Y%��f������r'FRj���8������H�[�?T��R���Ox1���Z��6��m�A`��f�)�{���a��(#��a��	��n��9Lp����4�@U��48��aW�W0������S��0V�D�7#Q�.?$�q�O���+����3pl�mt��Cr
q�N�_�d��$>#���#ZNC!�y�T�����(W��u�;� h"[���?Z�E����s�7{� ���KqF��:��l,���J ���A>7�|D�_2R�|�J�	A9b���&�o`�\v ���48���#�T�&��D�ORx�]k�=g�\`�`�3;�p�>��2(H�f@q���V�(p��������n����P�����r���CHo�C�����p�W��D�&Ym��n���9�eu@DT5�����c�\g���A������R4$}�J�=�QO��"�9����D�~/�����$�RlXy������Vv}�A��9��f�TY���a����0�0�pM�bh���E�	����L�@2I|^��WU�E���m���5s{���Tg.]l��!���!�-Y(���8Z��K`�/���5�[�e���B�tN�����W�=����#�J�y%�l��O0����!f�M0jZ��k9���3v��!�Y�d�Z�/��:m�]���Y�FTK��g)��<�}��/��[|^�U�����6>�N����i��0��U-O�4%7�^�$�d��2^�z/f���/xT�x������\P�����;|�-/'��l?�t��Ay_s����Z|��2C�� ��U��������^<wA����3�
�����Q{�GS
e[��%t[�E:��1X!�Tj��N'C��^�	�6~�JeDo���t�����
��K�������V_�m��>��]����;:��h&h�AA��ViM��x����*����,t��'��
�=��~(r"<eO��2L����lc�h�z>�}�# /7�}G�eKCppcFg����T���1d��"��%�K��[������+����H:���h�Q[C[Z����^�z�c��Oi�c��b%6FD��Yu���z��PZ���
��um�+����I���^Cu�!ON��P�+��];
���@0.������A��?��"1�����~�������������Q��x�g�C��m���
���Vd��(�������Y� ��� �����v��'���*�I�F��D��oU9�u����s��!��N��{��#��<X]�����u�C��&�4����+MQ��/Y��2���3��a}M��v	�����6+�f4]�Y��������yl;C=st�����v�8���	~�7~
N&{m`�wT�����j�m�����*����>��0���-�#��m�A�tRk�m�X�
���^�!n�/X9S9&XI;6��E-TD�QLp�t�,�C�����$�d�����KK��Xf��cG^���:����n.} �@i��s"X�>�e�����L*5����c�x:�����#�jP�78�������)�o;-�������X���:g�+�����2��XD�@����4j����U�n���r�c�j����t�Je���,��0�<��W�4���-]���������&k|Y������.G=N�������V7�K$w�3��K���-����R�(~yf8k��Q�C����Tl�OA"L*�Q
U�o��>�$�:��7#SX�������C[*q�Gp|Jg����h����%��wjzU�
��+"�z�������0��O�|
<l]p[0f�!)�7p�l��&�+�e����B��k��:0���SgD{�s������a���+i?�����'
���\.��?��c�)�QL��IM�D�8����)�,��Yb�^�$g�)����Og
y����{�5�x�\��1t��i�L�7f�6�t}�	�3��+K+��
���hD�x ���?p��X���Y\�?UE�R7U�m���u�t}���I7�'=`�uXK����0������~�`-~�_�Yu$n��@h���'���i�Y��������d\G��T*�iW���O�[��R�l���O��}�{>h?��~u������_<�y�.���Q��3�'�[��
B~���xw|H��Y���+�]B*}EF�P����7l�oF��������3
��2����,�����2K9��q�����mO9�	����q�������r�����8n{�Hs�S^���p�+�q�jgJ�1���A�!�����a�V�Y���@{�.��q�0����2�����)����L@m�ON���5U
�@�\�Cy��k'�N����E�4�v��e`���G)N�������V|�*�}�|��p��5w�_��cB�S o�y.�s��`����8k!�(�h*����)!R�U�/�V�r�i�(���&;���/��������[����7r���k�Q�����N��@���z�DZ1����se1�H������7j�kj����:�������MK��?��cg��5q����F���q����h�N��v9��8�Qz�����*�D��G9�`s����3�A{�.O�O8�8�����C=�1�z���0f�M��O���� ��rT"���J/3!���rZ'�1	�F6_��Sws�����~rR?|�<�U�E�~��7l8E����g	q��<~��Q��k<�P�������6����E�C�R��n
M!���:?Y7��2���6j�%����������gz���~����DQt�I�5�H$��s��Ux�����p0��"'�GH�0����a({L��B�.���j�{���H3r�V>�.DN��U����� �g����������&7?��+�����P�#�}����`�C1iD����N�,��
��*"@(:��NZD�F�z��6��O����`}-����$Z�.ytNB��R;���t��uI��N[������{wEN~���,�s�_���U�J��J=9����-	c�I��)�{�-��"�S*{3k����ZYNQ=�l�So0�E��y�f6R�fKKlk��<�Z�C9e9i
Y9
�� @MlJ�fk��&�P��K��|��?&�QS=a��&����2����0�D��A��,R�>W�e����#v4s��$�$'r��SK�X}�oZ�W_����d;Q���q�-X�l6`���Jv�{J)�w�u�zY��#	��Y��d�8����n�y�H���Ks;L=|������H��(p���~�c����q�5��S�����o�C\6:#�@�I�=w":l���*�i��5���|q<S����2Bc+�W6p���&Se��4&P��'�6��+4z}�;��s���D���*@��Y��a������z�r���,Y ���)�\��U�o��n��������</	'�������%O���=u�l��Z��	��	1�f�T�FB����l_��#VE'k�(	����F�I����J�7��fq,5�T��t���B���M5��_������e�"p�.�!G�U&�W���~��2c�ER2� �P]�j�K�<T�Ls�~p������8]�$�qI�������O��L�C�Toa9��*$�@���l���{��	���
����1^rvk�9���U�Z������9��]dn���>0a���J�,G����;��3�DXSA�H����6����
q#���T:�1�*v�d�X�R�p���wg5��/�@�&b����H�!^�(
�v������Q���]�g�`G���,�}A����'��Ip��9��0��R�uh+E,M��^���DV<F"&@���g��P������
��rS?q@�W��d�#�3���@G*�������5_�W���D����.��I��/�/�������REJb9��u��HG�"�R��$�$��\���
q�N�F������G��q
��I��K��Y�0�2�n�
*���`�3-���=��R����s\�s�gc���K]�	k��B'\m�qI���B�{t���!����������f�u�&���28v"ik��
�~20JLeC�N�r��������������i�@��%��)�D�$�Y�-	�#�q`w��S��0��<��d
�vI��L0�<�Vk��#��f�J�S)�X�sHz|���������rCXeM�]�G��"q([������B�(7��s��|��%�����|	3=�����������z1]f����\��$��>E�2��%������B�Gp�c#eZ����w��/��<�|�Z=8�����n�}�$gZ��R��p��5�������X���2�
�_���������yp��{�aV�����_v_��yW��RZ�	���PF)���+[:B�[W
,�!���k�}�5���?���m���E�p��l����2�yt�#O�?�������cQ��,m��z�,�7���
v�%�K�h=�,
���,�x@c�z�ip�p��k��$�3v��z
�xa�e��}�p�[r
�
<�IWAzj�������#��*G��U�.mC�o���5����r{��6�r�xIc�%q�@���gI���+��Y���2#1.P���T�a�l�p^n���J��\G�L�B(A.����%���p��|��Us��9�XIF���d+��?2�<q����e{�T���������D�7��"ZY�]��X����D����w�z����e��mX���A�8���tUT��h��U�a�k}<o*������Nv�\9`�C��mt2i4��r ��������o�D�&���U\6�����I�L��%������N����������_7�2���'S-�3Q2o'�-�p��DN��G��2���K'�o/���t��l��
�������B~d���	����l9/�j�}X����i_6�E���y������<2��PLL��C|�Ma�Y�}L�"w�Xvd����!��"-����GY'���zX�K
]����f��i�a�2�X��a�DJ97Y�������pNh������������xfn����v{���5*�����
M3����o�t���}
4��*������%"���)��%�����['j�R��C����lg[�����!@7y�PNS�k��#�lk����%�
VS����#	��}���,9"�OB��	���L D����B�"u��B�X��<� sz�Le^4V��m����%�[1@rt]n�C���p��:��-�1�_��C�=&r�S%C&-g[#M�Fg'���r���K�����1�5���RN8j��N����>@w6��Q���4R�h���R��4��Ja2�w����M�1U��^���g0F�Kt���9��\`
�L��zhP�eYO�{Q���)����T$�M<�����k��Y��9�-������x���0�C�
�/D�2����8D�j%mA0��7�=�=\=�:���J�*B.���Of����'�9����:
Fy��0rFPP,���R������k�c���8��5��G�"[bP-���j�L�n���-���/�K��D�3��dhp�����-9�������*Rr��	yo�\�5�^�FK�'s����~5ne���Z���
���W�i(��Wo(����By�����;��l�l ����$i)o�@�D?��}����7J�B�1�atQ����&���#�@-���0�����������tG��F(�.�R�P�A�P>8FZ'fj�s�8�lIjCR�1�������-�K%�tm�}:�����)j��IeCF����S����m�v����a�+ep�/O~�Z���/.�kR�����\.�IJ�r��!�f��T� 
\L���w�)-�5w��@�y���v�8:�e�r����
:�k3
����qJsp�vGa����v3Gk�1�����X��v;VR��6�,���
��X�-)��#�"��s������m�`q��X��I&CHMV�cu��iV+K����0�����AY���{���]���n,V�i�}�������X0{����9Z>�S�f���9�:���\I~��E�L�(v�x
���b����'�����$N=�(!q�iG��3��[+
#��2����Pq)%?~?�P���6!�p"�I����#����7��H��+^��U�i@d�����k��LF��$AD����%��2�\��QSd`t7�+L�}���z�W{��s�,r�c/����>uP���a$�AD��@O��U���Y�O�X���
?fQ'��Soh��2��`��(Q���Z�����#���TLt*�aa�����X���!�x�vD�����cYK�0�8����O~�����WE��/WE�MJx*j�d��t�f+Mr�F}�F��bY�;����!/��%�U��GP��
�+����W�����_������c�a�gQ���US�����4�����|b�Q�GE�^0%�0��������eP<������V_`(#���/������������C��Xt�#;��>G�I�1��3�@�($u�y��!�+\��w�2@*�$5��� �#2���)R��0
�<I�%cG�$$Y�:�4Hx����u�S�$��3$�h��<g�-��x��i�WQ�B���-(<�5>�X�(��dD��o)�H-t{�O"J�%U��E7
���&�MP���,�'���^'��������@��W�������QE�$ls8�����7�9BG
)0&r�bt�7���?LD/�����U����XhO@�X�CDY�s�%�UZ�Ai��R�����U�D�/������&�z��{P;�_�VH��C�r��0�X�4�����&��Ke�����9��^�����1iC��"�0�X�}z������q�r����8�5��[\��z4y�iMF1������f/����3�S���G��G��8j%Ri6����wRB*Y���F�gK6�1�C��|v#��o���
�`q���Nr�)��>����������X�3�3%�q1H��P(5���e�nh����3�_{x�,=j_^7�I��a�~��ps	��������T�)�G�-O��C�y��"���2���NSO �+u\A�{~�7[�����ab;��!QJ�ljb�����}@���b�n�8�\}�l|`����!����s�L��'����s�$v1���}+?��'�����*��lv���&�,Sqng�|�uu����,]�e�����r��.�Y�
p,�nc:~��~�:�D���SWM��\�h���.��J2��(s.G��D��An�R�_��%���0Y|q��N���ku��KI�@�JG�N��Q����s4+��C�m16`��O���u�/�U�T�e��XN�(�u��T���6�I�`����"y��X/�v��+��������u�g�����l��w�C�=O�%��R�,�O��Q�����>�������Z��������u���>�_0HMI�=a�SW�
?�\5�p��Z�P�E��A)�,��B�9��yb<Q��C\�r�<,	=]�t%���������2���Q����M��<�p��X�N��x}�dL�95=��m�G�
�V�����~�����_-���)u��������
?���6`+L�n�[� �{[�3�(��zh0�k���{`V�=S���Zu��D7S���U���J�%��v�]������������s���G}���A�[3d����g�I����`a��o��SS9>���pS<w���d������ZS/�xhH_���M���S95FK����������8����i!�������#�e� 0J��>LK%�q@�l�u������6�x��)�Tpf�.)�w����h�S;��������:�z�WF�7�����'�CV�X���4�0�^~#���LR>Jz�,���NILi�9tZ�F�	��K(����U��.�Y5#��M���gxI�}'�~Dr��� �`8��a�!��zj~
:�xL��G�6}���g��G#8�����-�shp��g�����?�~B
�J���0F��us�(�@U�3�4W�Kq-�Py�)%�`J(��W_�-|�Z���c��,�iN��(*�7U��5��g�X0}��up�X�C������d�UD�L�|�������%f1���Y{���~����>�~�K��R�7l�T���H�w1e��4��r��u��D����\�
� s4!iQ�V>j��95���H/�R�8�H /��(q&��bK��p]��A�i��4!����C�X\�
;D����������i��!�R�8*���1^B�B'��(w�!TOI�2	R�#�������,���r��V � $�Cd.U	�f���+)V%��~S��|Wo��%�x�Tc�8>�����j�Z	gJ��w�Y�������[��PV����i[L?+Z��	��������������&�����\�=^��UH��BY�*���o 2R�a����o[��($��Z8��\��=TgY��f��.W�\����g�7���g&)��&��S.-��&r����V���t�;Q`��%�t+'C	�Vk(��w��4	�O�)�P����rIq*;�&-�E�iK�FY~������{G�(�:�*����(�f|�l@-x�z��!?�-N���n]����[>JT����J	v�:5%�������Mx�8�]��kl2u|����d8��t���D�m�W1��9 �!o|��T�)����=�bT���C2��x0(���gL���a�T"���ziZ�m5�X���<����I��qD1CE�[��MP���\�0������
|���I��n�yl��8��7�����AA<|�PF���5,�����b[E=k���b����u���	
����*�5}{��K���7�[�<�W���no�)����n�|�+}�Y ��vRPR��i:1A�H"u�%#Wq�%o�)���Y��G�b��k���1�j{'z#n���m�����]�����e�Qu�r��5��w9���n;1���CR��l
���O�4�Y�H����.)J` z������ #���W�B��B
�Nu����eE���To���b�_Q�r������U�}z��YT�2�@������J�(�u�^�tU��f��)z}!��v����y���Kz'��p��l��%����-A����|�O�)t�W3S��l����v�T]f�5���~j�<y:��i6���,�O�IiY
�}�����pD�oF�
(�z��"M���&���VXY6�S$!�J*�����������S3�6��G�^����g�<��*�3���P��rT���7�	������\�)�i%RZ!��!G���W�1�YN�O��'Lth�1.5jI�Foh$��^�a1�Y��5F�Y���0:�)����������a2�N���;v+��K��K�hor	�J^yb�H�dRi��[�QQ#:�n|�o���~�%��&`���{��a;4����IB���~�P�w�&:�H�����C�b�`���ap�����5�2�`�������Mo��@�"L�
P��
c9����$c7�q���,mK�.`��
7���0T����yn�m�g�V�o���S`��x����I��"���c:>C����e��,����`���4�!C���:�m��j�L�����������1�!c�[[A!!B���2��-��$����
�r�I,i���BD	��PuO�V�Q�����b_r����dE+�]�\C��]^M��&l��Z���.���h�����(K�k�Z��cTgq���e�78Q)��dC&��.UI(�1��Lz��9~S�a|0V�cS7g�����;��=�g�Uk����?�Kd�y8h�
D�)C��B�|]��R9���]��J��V�/aS�}*z�!�a%�te�5����S��D���g���M@�8$�{�w���dF�7��#����b�]*�5�d�3����D���l����D�S/���"��c�v4YMgM�2hmO-('�D��#�����p<��ZK��$�A�J��71]#�0�0�~�R4���1O����W$�����zp�5��l���V#�4����%��/8[B��C��1b�����[�q��j�F2e�~����Y1VZ��i��>�_i�}F�l�C[$�
!��<������.�����?r�a$��������%a+���%&��%�B��*
�����XL��l��
#H��q���������P;�%��gu�@��L8�U�2Zk<h����E�f����fN�O;,4q����5�P%���l�"0	r<�A��c�g.���9t
0z���m�P<<j�^����+���.��2:����J�h�\�Mr�Gc� ��������/�����cA'M
c3H�a��@�9��b��t��cA��!�&��!��3Q�A���D_������f�iF�m�TRJ{?����5c�e8�
2?c��2(�2n3�3I{�X+����U����*�'�Np=���zR>����;�v��?t�<I������.<X����z��flT|�@[�,�.��� ��C�w9�4f����.�e�bm �B^��=+��V��cd	�im��W
i��k���u�������������x|K�����X?<�)����sv���.p=�-'�q�g�G�`C\��Q��AZ�<��a�w�%��NV�:Kg��g{$4"���V�*}��s��3�<m�>����^C���:�x?�����Y�J�\g.[��IU������-4BO1�#E�q��^W�_�A ����Ox/������"�n�}��
{�s<�M�
��o*��*3B<|[��H�'-(NK�:���E����	��`0
�����������>G���d�C��'�Kqx���h�z���'�����8y�)��4�z�u����������3A���v��
Te�*^�Z�i�yIs�Li-u�^�"�N�����_v�L]��A� u+k��j)3�Wv0MC��5���n/)�����aDH��X�Q�K�ke3%\�1{�!���
��Lf������*���T�3�j-�2R����`W)�7Y����*��\�f�a���;hK�5��`��Tmm��2j3�AH�.��a��j�
k%�]���4pst.�'}���|��������<���==���;(��\��:�V���^\���z��i�:�����[�G��nl��2�<�4e����c�up��0��<��6���nWY5�
�r"bj��n�O����:�e+	�X�Z�C����H�������u��2`H��DC�$���;�e����r��	����q��gM!�Z�X/�L�R���kS����B�����JBVKi:'P����dol9����y�P�~L�I�$�9]�Q8�����2V���HU����cv���A�zM��s��9]T�q�E���
�Z���*t���u\�"_���L�11l�x�I�PQu���
9xO�X�5�m�H�"�
��+�����9/����Xg���w�A���t���G�/��c��=��w<����/�J�o���C�����UR�i��v<g�g�;�a	D��G�<p���8��ya��|$:�3F/	������<w�c��3�
b������O���as�f������oPI�6H>P[#=��J�c�	���t���B�*�Rtv��99�R�S��mJ���X&k�+|��X���=kY�_���Y&��0 L���5��_Yz���+��%�O�
�5�����b����-="����Vk1�i�9"��B~P��t� U"��oQ�*�X�dNc�M���M�?b��D�@;P_�0���*�@�YZ2k��*���>]D�A�����z�K*)_,*��u�E��P��F����t�?v�:@_F9&�01s?c��d����F�@0(���$�����Ld���+9�NI4��^������.���[�B|V�K�'{:N���V�M>h�����s�{�������8�	�(f��C]���/_���~/�uE������`����}w�h��6�UY;q�����<=$�j��{�w��9������d�Jj7��I�#���u|�U�p@��>�4�Q}8�7Avap����Py��Z�V������r�~z�����5r=f��kd�r�M�ul-J��p���=:�$�4���!�u�����nr��2%$F����za�9� �yyS�}As��g��v��{N���^�!������IC�$It��s��B�$N_T�)����N.Ne�����,��&5�H��<&)����GHM��~P#��8��%���;�~]�a&�k��]���L3����BRX�,2*��{K,�2���@�C-Bj��^��vr;IO��)�=
:f8��i,&Ua\g��]�����l�����-8�~i�=�V]]�x�rBy��x]mXc�T��S`��X$%�����e{E2��v��6�b���O�t��{�<[�����w��$��N�G���t�U�N���9���
\On4�A�E�����MS^�;�4���-q��M�����sA[��U��dQ$<|��E���������+bx������8�h��#�����i��nyk��e�����l��=�F�BWFQo��l�����L�|s�Qz1=�W�g%z����J����F��9�8l����I�yN������6B9f��=���M�<a����l���+-�����0�*&��I	��J�����C��.�����J��W��N(+�:E�����d���14�w���s�T	�1�Y�5����C��I����^Wg�=�s�F���I�=��z_;�J���v&z��d?N�4� �19>� �v�O2d�k�$\_����)o������p��p(Q{H��*O���`M(yVd�f������.37��)AQZ�������� �'��#U��z�������z�l
��b�v2Y�H�j������t��k6���������@���=���_oYp~`����������U����w.�@�}hwp�l��4���U!�c�n�u`�X$����z7#���)~��jg���tU!��P ,t����
+�)e����.��H�mXw;�)�b�)�BZ��
�H�'��^����6��\����Xr����e&�O3�H�IzR��i���	���N�,���kb8	�	n8RYB�����N�j��� iR^m�o�����W�����,���@��wNO��GrGh������(�O-��"������z|V�3����q�B��[��KB=CV���qM�tX��2���X
:m��nk��_�i%	�%��0Q�xe�b}	��&��:8����	�ID��E�4
�l�m.����V�TG5������C���\�%6������i�N������Y3�$�������Z12�����,�t���|v�P�q�����.X���.��
m�aMt)�j3��5��\���:���3e����ySx�L��;e�2�)����2��%�d����t��������]�{s��Y�H����v��D��-���+����V�g�Pk�H���]^���8�7���o������OK�]j�0Q+��X[[���������>�����3���e�1E�8��Lq�}Va\� W���j�(+�������6�eU��/�7��`�/��v�4��5]G�|P?�Q7f���x�?G���O���Y��E��t�/��N5�y�m� MC����-���d6����]���Fl�j�(�"C��1"+]���s�d8��R��F#t��<u����x,��bh��n7A��J=�����E�lm�s�n6�q�UKq��JT���%�K�O��Gh����Q�2�;r���?��q�t���v�����,<��,����`k
�������rT��<G��Q��T��t�y�vT�Cn�V�d>������5�cU�w�p,H�cY��e�`�ve��%���#��0��{��A�/�
��eu���:GOn��4��b�������K�	J����d�d�8�d�k�F���'�Z���F���/�;��D����^3��E��_���H��f�	��'\��d({���t�=��	��[�gzxN��������On��W�������6���(�9�:~=�bi��u����RNBYJ����MT��T��C- �U�$Y���C�U})�C����A���T���������(��F@����jy�P��3�:�����
���G������lj;������m	�����.�����pO�7&���0{���G�8�g�e�].b����s����%�� @Nc_���"`��S�5��_+������x=5P��`�h�v�XA oz1S��~��r,���������!�%��dRe����|L]o.�Y���WA�O�d�}zx��EE�n���t��H�����H�����&e�G?�����3����5fcM�����Jo����*�$�#_B������`s

Z�3c���R�"�����L&��b@!�2V����VB�)y����';�%k�'��k�������G#��V�g+7�s�����'�m"�������O���oES�h�9s�����&s7=�GcQ���|��x����P�����i,�=���7��E%1L�:�z+kG&��3���,��H��/�aE���E�{��D�i)�N����������z8D!] �K}j��J�0�f��|�6L����qm�lc�d�
��*e������f����e,�(9oQ������u���$�p�`x����&�)`�>��U_a'`��M�������c���Z����WsW<�N2�Rr��OL�����w��@`T�X�s=�����}�P��S&���z���X�@uTAn�H�H��c�B���d|\I���vOYw��R�b����N���%�w����'����\�e�t�eG���l��C����%YM�r#�fpH��a����r���v<����{q�C��r]���u2�?�`����.FQ)��)>8���wtx���8G�UA�Q'l[����
�|24�� q"sH}Xe�lKa����W�q����(����2bk����zC�g����$�����XL���v��0�<�&ST�<E��H�&��mY��b2
:�E{����[9��x��i�8	����},]�u�fm���P>�*�d����1+3ji$�
��o��/b�t��l�
!�&��:�_����� +�T�����Fm���!�L�+"�����*�od�����
?��^��wv>����6j�A�#3C$	=g:�?@�� �"g��6���x�4[��m�i	�
����U�Q;��NP57�W��
��-�������;ro��*"������<�����ijd���)eJ�]����~�b_�r��@�7�5����@�r[NL����G�����p_8|lA�'�P���m�D��7��y<�H�G��*�D����R�L�T �p	Y�������sw������F�����
a^r�/�$�|a��CKL���N'S�~(kPD��>��x����3���FSn
�����M1���Nk��5b��ht=9D�MkD=�v�.���.?�[^�SgEX�lQ"6���g�0<[Y�)�<�;������^�z�"	���~�CIK��{�[H9����]�y|������-0��#��)�[�RH����R�0�P�G���v��[���i=�nm�*���������x&>x����������������i24�
F�[i��R-���y���~�7������7�5OAa
��)��Kp�����i����0:�c��N�@��$eNO�%�6R�>��SG���al�C5�S$b5M
+�H�9�t�s��z6��L��t��J�����=��x��(7/[�L����Zr��>���}F�	�*�DK��"��^���l	�#i���AZ��16�I&���L��!V#w�#�����t�+�n�m6��f�����%�~S�-�����zb��\��Fl~��;��p' .�Q�������>b���4���Z�Q��Tj!���$���H{#)pE��V%=�$�����LD\�O�Oj�
�hak)0<�>J��C]��b�<�I����K���~�v���/8R����j��%,^��������GRE����9$y�%�U��hE�M�2i��W�bf�g)~�e'We2�6������=F����vG0��V�@���WJ�fH�����<FJdIs@��j:��)���V%�6�Z�q�tp�pr�S��L��:����?�
Z!-U��2W����r>w�V��#��W,KK��X	~���Gx�-{){+n��p��2H�El0�b�pk,��>�y)�m\�I�d���~�f�&J���d����&�A{���x���2'�����?R)�(��jst�(!������f,�{�	K:x��I}�W�:�4�p��u�k�4�L�*�o����weh*�UA�F�m��N.��]e�N'���c`����������d�,��e*� 1}�@<n��)�L����m��q!�e�j;����x����*�m�Ien�K���b�(�av>�(��j��j��(�|����F��p���������N�d����9Q5��t��T�p��OF���}��$��S�;���3������l����s�k	�������
\���y��][������D79x��P��~�8�f��\�dtz�����Y��5�GC�M��r�����e�:^
y�j-��F&�uQ����jR�m�!I@��8�A�>����l�Oe����l��D���Rd�He�,���W_<������YD����j��>Z���3��h��Q�+~�6�Kl��#r��w�,J� ��KZ����+e'=�g>*�Q������<��C���Y��M*z�N�{�������#baU�FK��C��D/���r2[
����S�RE�c����>h��4��=4F���T���p{�1�9�F*$E�,�I'	;��c��q�D"�rZ����e��l��������I�c����mUP�7�72�	��,P*��n�`U�h�\W^���P��������j�����th��TJ��#L����n'�����.Gi.�4��B�TP>��~2��������F�Y�
G���k�e�����E�l��cx��L�G���dn�g��Z
5��u#6S���i�
%���j�D�\����f�p��}Bvc:nc4�1���,�v��md[S�9����4s��tN3������7���0l��/�����{�9�����7�@�Z�t��[=sH���($���O���0�7f��k����:��o����y�5�S�O�|Z��N/	'���z��@Z��L�	�<&����T�h(f�2�0��f7����|8�.�����_��.\��uI����6������a3"�J6���p`���a+p�n:+�EYRPD���z�96��/����N�S�R��<�����>4URQU�������Y�����qB���_��M>E<�z}����t]~�#�f�g��
�_���'����]�Zr�o(����'p���t0��`���,v<bk;,�?a:�>����$������l��kzf������S�����G v�>���?!z#�L�a�Fc~�����:�_R;��P#C��%f�9�^ul��[o��oR����Tnz@L��x��P<W:�`VU_��U������;/X��u�X��*�u���������V~p0�h�N�\��C+Z9������4��(�%��i��+E�rz-��:U���&�e�s�d=�L�}�0���-�]����/	�*�V���'���m5bj����Tj>M�e3�.B�A����K�=�cO��_oqi��?Bp�1��i�N^ {�<.r��H^qY�Rr]�[��"�a�"��^�s���]���-!�J=�mV�|M�`�p*f�C2s��MRF
�B�m1u(rhD���P�}}���;zPrFF+��x�Q
9!�-�?MQ_�2�8�v?�^�&��L�nm�T��
�����_��G���<:�
�9o�7)S���5��D����������,`�P��	�V����K��,����&c��y������6A��=�k������"ei
M��r�m%5W,���O���G�,�����{����d����bd-����e4��[�A������P�&����-�6��G��>�=^I�
�M^)�����)����.��N�n�U,J� 9�X��N�$�$��V1��N� [�o_'����j�����,��������W&�I��P
e��Pu�%���9�2�Bl�2����*���7�m_�����?0,:I=��QN'9��)EB��W�h����T�L!,-@9)[��~h��U��~������R_E�[����������G�� >H��V����<���������a��v�<���Iv/��.�sIJr	���0	��t�	�����.�����m�&���F��3�vo��Pu��[�d�?q������]"~Rk����������y�B����Q��6D�����
�
��qyGA�M:wo
�v�I��:.��HR�p�{%Z�Nn�c��N���|GZS�0�4�A�x�����i�(
}kb^��:�cL)��giM
�5OdGN*�rP�uT�5v�R�y=$�)��U
�
�<��V��T�@S��������4��{
�F�����UY�(?��du���l�a����)T�20�e�������	����U�%QF��2�!|Y��������.Dv��b8����0@N��
��B�x�l��VX~�^&����Q����a���T�8�����<	|�v������<8zyR����\R��lp��D���	�<3��)��l���E��I��{�%�TE9*u��y�G����Vu���-c>)A��D����2Ponw��������u��}���s�n�m����L����PX���q*W��-�U�~������c�T�@�"���IhM�5�=�>>]�hO��s$J���R�5�
2z�>Q�S(5Y��G�qS��B��k�n�����Z���H��0 �`G��Z�I�]�7D1��V3�mC�S��w0	�^F�r����wmR^f9�!� ��?��qHW��E��Y���C3f��0�u%��x���%I�^
������4����?���}��>c�xy����>p%�O�xpq�B9S.�1C��m�%�z�l5^_g.�8���9����A���B������%�Lv��$x���l#�g{��tk��a��0��Yg�\���t%���}*����!���h0�[O���)��8
��Z����v�u�5wK�T�8/jFE��`GCb
Y'E����I�n|{���+L�7D�>�BD�{>�Gz�	����1Ei�W)�bb���)���#�)uU���������G���+�d���z����[��
�H�q���� ���F
8��h�Dce<�8z�_	3�b�b���MK|F&G!6�	�:�Y,Wre�pP���o�p�,��g	�M�TA\�2vS&K��j�au��I�a�-J''�t)�A�B�^P������m����`W�4�OD�;�pn0I��X}a�R�����������S@����m��������M�I�7N����s��6����[�n��Q��Z^����7�8���Ic���D$��;�������r���@-�K�������Or�w����\W�^�������J/�#���>��I�6��d;�9��WNcp$2�fz-|���!$����l=�1B����F>��z�nH�[mv����0����u����Y/��Bs�&e��T$i�M"!�q�����i7��� �+�)�9S
��LAw��[���T2��'Y������7G�k6��E-�k���{��"~�����:��N�x�{������v|�����~�J0�X��K<����M�]i"�:�J���aEhf���GX���Rj�#.������H����7�]��N��W���J��Z<�	l����E�{9p����%ef����.4���B�r������mW��v#5���4������6FE�h��<�#��'��s��7�W����I������A<�-\�c�;_�5=i�H����$A����
��O��C�����i�/�L�0�	A���|��1���3��q�=�����P]�}_�����O�v1v�xD!$��A#�/�8<��.Td��l�
������E�#�?x	�+� x|-[D�����f�a��5�)_�����%���Nt�OEYI\D�6�(���%���� ��`�j�`�/&�r40��6������WW�#��'q��i����q�wy	��<�L�ZK��)� �����I�Zmo�������������F *����������==XYY����bu�I�Z�+��-�
Xh�!��]d�%� g�@,9�HJ$��Kh��Zw(.B���p[	�?u���B_K.q�v�\P��0&�GD��U(���]����I�^�����I�&o �������&t���Z���a��O-�o���$~�xX��@�����\��u^�I����z"b�;K��0"�QI'hr�8���P��N4t��*:��h�O�m�)T:K�����u��l�@��$
��Pz.�
;)�bV<
*$�>B|�P/���Y��"i��`���0k�i��#W��&���2K�l�$����uN;���������
ol>�������&.q`�����}i�I��G<�K�'@w8,c��@]��8	
=����s�*�����$�.�x�,Z�)I���P��.`��$����n.�sK�����Z��:�~�K���	f��E9�?����#@QT���p�6��E�A����G�O�is�w�X�7{���d�ZM��L�)r�e����!���OB������W{G �a�������S�VR?a����Eg]@
����+������[�����61V�P�3G�}�DM��t�IVG��Ci�mon����F4�.�2<���-���b�w���o��-����1��EQ��~9LxYv���	'U\����AUB�@@4<�>���k�*9�ng�c������{P2!un�e�ie�T�t�nl��O��y�!m�B��F�-a�e�UU��k��&����%��,K4�G��;--d7�	x��t��7t��"��!�>c�E��	95z��a�0��|��&[��n�	J����������2��9���^��ie-��f�@��@_��mY}B4����R%C�&��L�����������
}�*1����$�C�>]�*O�Q���
�F�oe�����AE�� ho�+�����F)C5�Q��� C�����q�A}�~wHc����>�pC%x����:���R�������C���x~���i��o������Z��n��Q;�=�����6��9���n������f�Qei�#��%���*���J��^;L@@�_c��$#(g�K9�zV��7NO��������`c���/'g�����2�7��-����6i�8�GHiuP�t������������z�`��>F]��-�\�pL�����|��8������$���V���2��$<\7OAv��8���0�6fqC���Z]��\�vP+����`mm{}�l�*��q��4���N!Z��M�����L����PO=?�e�v��6j��R���F���2�����B"xJN� m���Z��i��j��gg��7����O�[��qx`5��VR��7�(�O�(�b����^����:��K���`���]z��y���#�]yytt�[u��-��1>y���E�����b�R��g�ZZ��bnom����O������r��:;m=��v;y�i�����om�r�'/'q�S��KK����������
�\'*����a�U�1�����r�:5��\��f������v��v�����$h������]�=���2���!��t�����P�)�%��"'a�(U����at��|:��������	��q�_,����O�����1��$��2j�[7��0��z��������Z�0�bu���������%��X����h^����m���dp9��uCP�	@._PH�&�Bi�#��� ������@�.,����6|ek����������A�X������Z;n�����w��$��~��Qo������A�����-~{�7��7o�{�z���������w�5Q����ko�i��_����N���P8�����������~X�a���#����M���!�~x���}�8��@��5������T�Q{];���t�o�i�#����W�t:>!��~rt����R��(?�,��a���.�Wq��e�
�i�����=e4���g�E(���FD+�s�~���U�����2���w�HL�-Z��k['a���L��!���P�G�]����
P����J���4aD��m�9����R��.��}��o����cyJ������!�`�8g�F���6e3R-��9kv{�;]����5)�4����u���TP�'��M ����-c��Fu�*�|����x.~(�����,�ckT}S�����,�_W3��?P��+����/8���x\%�e��w�}�����on�\)���I:W*~|~z[��*��B�'���4:VL��<���f�:
L������A��Yz
�A���T��� �E�tsR�x�vm�l��D��F���u%��,�f�"x^'��]���TU����QUv+ ~�" �U'��+�*��^h9A�`��@���S��_�h��p��x����m��G��"�6�!e�������~]�_������{�v�Wk�"�����S���=���j�^��*���E2l������zq���G1C�/`d�oM�/���%�����N�/bo����L��1#���2��v����V{�lmm�t�z��r��+sdZ��>2%I�'�p�%��?�:C�C��A�Aa��v��}�\'��{y��T��V�;mx#���*T�0)!����������8���ElY�|�&�W6�������}hU��(�L�2qfIH�043J��:Q2
��;;K���:��p3��%i�n1�����
r��v����vIRD�d
��2<Sr>������-�:J�Rl��@c������;/�.��D���
{���li�A�>��a,)*�J�G-}��kS�n�&�nTx}�<a�PV0�E���	}�M@�����OYGmr��J�1�[����bJ#��\�+�V��0	M7C��k���;�kzg���FW�mV��:�)��R!RH�P�`*OYS���^� �7�Vu�BBzu]iGx�7j^c���j�Ph��mE���1�DW_�_�$�-�Ree�N��0h@�%���,!������C�"?�%
��(	�u��	J���m����=��.�������zJ����:�W�iN��u�*� �3/qq&�F���CY�4D8�����P5�z�m��xk)�h��ma�����	t���|���|�4sp=����N�I���g�����6�!�@��e���o��m�
�����H�=}�WQ�*�Q�`$N�&L7�FW�n�yvx_mR�S�a�7����������]�Q�����"E7%r�h��lKcr������!�.��s����SJ�c����[����������������������Z�J"nl���
~<!)�'����
�
��&)g�,����O�*��UY��@@�l��yl����F���$[E6�<T���(�X&���P�Q ;A�6*y8'�Z�k���j�����84�o0��[���^#��x �y��0�1F>���������~S*!�k�H�{tX�54�����/��;?��b���7���`�}��V�n��vX��&�4��XT3EYi��+e���!<`�&^Q�3RP���J������������D+
dO.��f��&r�Sa?b�����]8�h���@�E�B��M[������+[�Ji�-�0��q���W!���l��>�C����!��P�M��-Rv�������$B�&��C��[Y�D[������:��M���\��
��,td	9..��$d�kU^}���������b�������s�@,'b]�9'i#� /T�O����O�Dx��3:_�K`i_N���K�3�����>�NU��2������ $W�&�g,���$Fc
0���+*lc|��v��K�T��le�3��5�u5�Fkrq��s}���(8.����#;9&�Wq�����0)T0��Z*K����,�g��kvO��w�8������h��Y����f���L�t�@P@]RV��+P9��I".�(A#�/�����,�B)�6;��2��dj�������o�Nz}��g8���;��}������%�9X�N;a+w=
��)p�����3�I���@���E���E�}j	���h�r��/�����V��H9����T+{%n����4a��FT�'���L|��D�$��9S�B����V�;q�	�@�L8!��-��A���3��;	IT�)E����<�$�Cy�&w���7��U��t�i�#��[������q��x�=g���`�s~���XF&6��6���8b�6J��%�~#jR�4I�)|x��0�@��A_ANz���P2�2<:���]�zt��`����~������:x��S�=p@;z+��'������"�!`N���7�TY]b$3!(��e���@�Ls�9����6H�g��S���n����v�,���k-����$|{�A�D����/�@�R�v8#�A�*�:��%y�YA�u}X"���'
VJ.��u$z��@ B&������2��t��80Q�I\���`���sr��!*@����'�:�����t���a6���uoPs0��r���x]k�4���5�2vdr��
��x.����+t �����uCr-tKv���B���r1S`���|<�C��Hn
I���3@����Zu#|���>;����F��U�1|����V&F��sy[<��QO�(%��������_c���)�N���9�8���m��-��.jy�7'��nu%�U��8��8�w�O�+Z�:�mj;����/���;y���y�G�M���~�g�1�6�N-���Q���O�+���[ �������Y�g#�i���[Z��'��J;�
u�Zf?���pJ�'d��%��a�y�!j����v��OY��$]����{t���}������e�2Oh�R���H�#��a���q�X����_s�~�2��SC%@���1Yg�X�L��+s���9c����3$� 
��	O�3��E���a���k��4�K"�\.q����	�bI�
��/L�$�Y�j*������A�UC��Q��[z�Bi��P� �HN}�ZC�k�����H��4�^�	�h����>�YWu�����E��z����L���J�P ��rIT6X���4��A:�L��ZK�B�7�x.*�e�gRME��W�8��u��?X��#;�y�mE���E_,p�8�`�i���^e��^}�Y�To
�>�������}�����p�	���e�+��������%������V����~��a����,	+�����cd���E����a�X�,;�B���X)����]�d-��������b����Y�P�:Rz��j�0���F�
\{3�f��<`*���\>��
��(oVN[�m�GMN+�C&U�6f�\�����5 cQ��5A��7Q��I�C}�m�c���o���q�����^-spl���{w��J�T	)������J��,+�>>z�v�W2�y��J��[���3f��>�6%u��O��_�,T�v_��}]���t��������k����� ����4H��<�0c��GV7�6�*}q��r�Q���R�&]�����:�a���o��/�������f�PK�L��-��!�Qp�����2KD�&������_j�-�
��l�����=5*=������<<�4�y��=n�E�x�O���&
8���I��!O<b����?��>����7��0�'|+������Re�~{����.>j�ol�g��Ii���YN��2�����E�\xJZK��&;%������N��xv��U�s�y��R���v����u��e������P�N"���|���]2�x����)<�j���T�������5���G���qH��������%�X���}�sT�d<=m�U�lmo�����a;�(o�����1�+:���E�i+��a�!�Z1��c�M��`��OH}���K�5I�L����J6!�8�J�3~,�{����]�V���Q���4����<���JRuz�{���#nc�!����
)]�j�gyu/�S	����������%��Z:���cO��Y��=�ru��C�R�i���n������O���)Wcc����W���������+pZ\dy.p�v�<xa��-�E��Kw����hv�C�zt:Wgr��Vz�������+�����z��k���+7lmc���f��w��G]H����|�u<-o����m�o�mn����hh���UD�dC��\����*V�=^:6WNl��.����1�^���_�G������_x���Xh�}k��v�j:�l��f��LU���$X~'�mln%�[�.2u:����"���`���GJ?;&BT���9��K������k�v�4
e��'Y�~�k�f�U	ni�,�j��6������� �|�Y�h�uK#^�b���z����A�RY��6�I�^�f���f��q8�d=�%Y����nN��[|��i����� dN��'[P����w���X���E&��1Z[ty������xW=D%yR\eg#��M:�q'����;��j;zKk=�)�=�<p��T����1/S@"z�>+o��mT6��[����Fy�g��y��tq�8�cG9<�`��G�I8�V:o��F�o�&Q�i��
?�5[����N���{���1^�`*����_�V�O��E�C7=7������Q�C��QE���6�Jv`t���\;W���8�W1d-���0��dp��c�>��
(�3�0a�=���(��;A`B��+UYA��`�o���n������k��:����������6?�S��9�ASY	*8��A
;E���z�p����G��O���?�.ld�p4Ju}�����t���-�(�����0������pk���lq���-n
����Mi�������m�O)�F����8��	������kz���U��+�E�w���o���|��&��ia��T�ln���He�`l���q�����f�7[@����Vu�unU�G=��������EOC������Uy�����l�z��\����.�b�
I
�	��E;�ElL�)�8����L�6X�'*��f!��h-O��Y�2�,��a��L)g�S�l�l�����M�9�g��M��#'K�g�K�lpV-o��Pdb���@��B������C�r�Y�4��������:��r���F�e'dU����y_�(k��l�!�$.a�b����f ���X�P��U�o������;��W)g�����v��v����'a�����
e�����2,�m����Ex'�:"�dh����W#����0Q.U�n_[��x5;&�
";����#��������V�L�V5!����Ox��Y��[%��O6����Z{�I5+���S0����K�1>{�=��i�����oG������\�}%q\;������������5%��p�:�u�������V�z|������t����	+B��k�OZ>LT���Du.�yV��_'�2������WQB��8�p��������t}���*�N��e8�
[vV���,�D�%e����\�S)M3j{��jY����-H\�Xg�w<�4?g���?ZK�;�]�T�������P"�<����t ��,5���u-Fo^�r�u��>9��o���p�a�I�]A������R��m25��'�S��V{@�:���)&Z7�|�`���Is�i�����j6�4�6�x�P����V�m���K.-5����S�=�:�s��������R�����d|�� ����<G��q��O�c�-
����zC�S����A�w,������K�[[�X�+�p;�R<n�W�Q�m!�t�"z�Ke�"��J�[�@�ld|+32gy��O�^��7G+@�Oy��3�cL���0��HC6N��jB{a3��	Z�~�$:�k��*�~��]��g��/�&�P-U�������Q�P_7K������������O��Wt��rUw_)���+�
����n�
z�V(b%s\Q�C�#{��"��=���xw~!�e�G�.�T|������u('"�@�����*��V�}^U���?����?��s����u���6��������k*���QR��*{�������~8��|���]����o��s��i����>.�Y��Z���
�R�a=�Y�C�|�5��r1�TN��>�[m>�r����v|tp�rw��;�/�� �zgL5;Q��H	�t�x
2�:PZ�_���N���f����;�����3R4f�&ex�E=O��{a)y�������� ����������
��	
[f)�=��L�A��1�'0��������?�y���g��2��8y��P,���w��B��������9�$��t)���0�������iT,a���
�+�7��;6�R�������R�
$�_pu���!��O��"vDR����r��$5LkT���� &B����'�N�����|<�r��r6B����%�����{E�m�U����r9Ku�����!F�D�Y���`v)&^��c�jLL���n�'T�,����6�!��,���{8���E�.Ov!� �#����Y�ys�#��x���v���C�n����e��fE��,�b|�0�^�r]��C�>0"�o������O�����Z`z�L%�0|&~���`bFQCyGr�N�N0��#iL\��(�C�[�8[7����6O�#9K�&YKgUv'�����[`A�=T}u�R��oU���wom�k��jm����[k[�����6q<�*;sGwr�����?���Qj��{������^F��e��%�������t���-�����v���Z�jj��SK��yZ!2��E���UsuA����!��#����z�Us�A�����Wf�j
P)~k��T5����~(
�����2��V&�ke���t�����n��;p��)pZ��(3�I�\.�Ro���s���L6�I)FtB����Q|�0[��a��SE�m���a�UN���tH���F���@�i^��4�H���d�Z�]&���\[��W��U�8���T��JbP]�b�
�	�[��*��x%E������	W�K���F_Mg3)���%�5���u?�6P���w2H��I-���kW���QE��=�N1~�@U�S�>qP��SJ�����u���n������8�5�b
W���j��h'f�
��&]����e����d?xq�m�6�nM
N,L����~VpS`s6��.�9�q����1������������p�
!���wr����g7��>���I6�]!q+���4��Kb$��1��d��B���w����}om���g����h��g�������Z��e`�\_���b{���]fT�;.������>���^��>�Ogk�$��?*ek%�d��o6t6�����,�����<Cr�[T�J�=���=�C�Z�#�����)0|=�������L�K��{���_�'r����#�8X,��/�gU��&=������;4������y��4�G5v9��(���G��.f�>�%�������<w�*��f����)�.`A�k���B#O`gt��S#1�D�:��+�����u���P����\���'XK�S���~e�J�tG��n��9KYr�G�D�?-7�����qI|p�i����L�i��/������UR�������5�m��5����5l�6R+�����#}�oc=o��S�hvI���}�*��n�Eu5w�r�*�_N<0WU8I���ht;���2������Hv\�-(��7����U���7�c{���,�XF������!�_�����N���)/�H�*y��u�����=Q��d�2�Z��w���^�m�����"�o��w���B�����N�y����������<e�i'��V��q�<S�[	t�!lYb�t�>������C���r3�P����=C�{���{V��e	�r'u��W�+�^�*�3c����n�����z��S��������~�����)1bj�u��fH���$%w5�U��p��0���D�i�2��7�
"��g�U�rK�h�r���:;�'���l������0:�_xUs�iX�E/�������z����)n������������F	���X���Z�9o�cu�k������K�����a��b�VO���(������jI�M�4�]�A�T�r�*������(�{�A����T
����|��J��~���k��P.�����������2�=|x����C+[x����u!�����job�� ��WEq�n��!�f������.�1<��!����0�
����O�~��w1R��~
�-���a�#~�Q���".y��>�� m]����]�@W�������(�<�;^�x����s�h�����^�7�=�|�8!C��g�fMV��B���@��d�i�J�L�	�����N���'��o��������(�`n�������y~`�dyrC������/G�����E�������h#�@��jy33���lc6K�{K��h����XE�m���i�}30�}`�&w��I��z2+8�p:����op�V�@�����\oJ�+�G{|������������X
j�A_,��]���i����8Z*�'N��$��w��s�����m��N���9��yr�wp2W��c��n�u�?wi+�����G������J�Og������o��C����^��z���1��Nx6�v��������rs{M�3�=�Rm�c:���N�y����`��A�B���H����k-���T�7+�u�:�\�OO��6�v�s�4���-��"�w�a�/p����0/����pVJ����.������*�����Z��n����r�7������%��$����5#���uh��#���Ex�'%V�H9���+��Q5�����:�;���h��J_w�j�{��O6�������q
�C�4JGtg&>_�����7��l�3���=m���<�6�ny��PK3�,gtN��.�0�{��h3�n7���6�Ja~����n��6���������Z������a�a����4�����)f��-���c����|'��d�YM�.���4)�M���� =�O��-9xV3������s{��#�mFX������3F9����z.�r��Ed�;��3+���-���37��{w�����j��Aq���y���S��1���z�L}�:##�[�����{�;35��`���i����������s��vO�r��$9��-E�����9�����W���c��>s��P�=X�Y��*��W�6����es3A�wZ����3����%���g���<��^���;�;��YDR��b����>��"���F�e�n�����w7w��h�,��^rW��&���7��<\���~a
m�� ���+������h4��>���*�Nhnx�����.��:����w2�[vY�[�\DYD��S�$G��wT����m	������Ym�� ���~,����=�U���g��V�O�� <�ps�q��L���u��U����1.n	�K��.co/��uz�Q.����$��]�6��7�����`xO_|[�Ob]���OB��I�"��h�~fp�^���MNAq�H�>�z���.���c����er�T�O�Xz�x���N��,0_������.e��&~��8�����Y	�bB�������8?o���o)G��o��8����Q3���P!z�s?�s�y���������/Sk�����{�;���C����z5�|�s�Th�b�w/�c�/~�$���+������,�OS��l�O�x$O
4��#�����O{Up7��n8�9�;�U/����}���K_����������f���z����fjf���P��R��������9�ofe�y��0+wk�+�E�����w[qV���2}��^�a#�}QV�#N�]UD����,�%���|�w	�w[��o��o$����+�9��$��!\�-K�s���a�3����$��^lK����4Sw{��F�(���N#c��p����D�_���r-)\��&��w��u������H�25��>����*ss?�O��
�)_��k�]@�EZ��>���i��O�{�42?���(�n)I��g��H���~(���7�7Pn�C�Y(�QD�������-�������~�L�����TC�jY���/���&0/43��V��<�}��#����xZL�����cf1�����q�W�I8Wz�E]��"���8baq����y�w�bwa��]/��<��OsN���y��-��S4��nu�aN'��l[�6>�T�]������g��Tb�^I�~�-�Z���B�
�9S���F4n�y#������������������i�m����t��t^w��{���[�
�I�]M}��$���I��[��}~�%�g��#��/�=�o	�;����%a�������*%W=�Q\���b$�Z�HX0�q�����-���O
h�����GV��7V��7�]��6;�k�1��ff0��d���[����&�P��"s��mt�T��@=�~�����r�d&����;z
w�@���x ��r���7L� 8���ut��D��7;��B���f`6�����b�o��/�v�q'�8�5��_�r���l=Wuj�Y�2����H�'�M���[��6�,��fA�-n��s��m04
^sb��H)����w&�H��P����5����f�QuDE����FlhB�K�&6b����R2��`��F'�5�jF�������N�`w��V�;�u'>qQyx��Q��L��p1������<
������SOO�)n(���6C��������0�Y/��o���D�}�C���3xw�Y��s��ul�n�t����A��l�m}���)�)|/�����(���JqVk;��g��<����R�C��EY4�KE��aY������u��3r��k���\�F��|A�����t~�N��3" uj�a����]�X��z	����&va�?>�0�yx��}��C����Y��!`B�\�����A8�v��r8x�E�D�b�ENc���d�g:�,	�����8<�b�;�������+?�'����Pd�����[d�T[H���>��������O����la0-N�\�D1�v���i+���^?i���VR��UY0X�����8��O�����.�u2/�ggx9)(p���e�b�����{�vO��4����k����=�*��7��bz�{B`9��ZN
Y�>SNh}�	���J��F	���.�8�U-���$����? ��&6����f
G��?f�]#{D�ei��gb���Fm��m��P�����]����zm��;=�R��a�cUMH���7��vj�<�������N�m6M���f�y����nO�~����<
q��
�|!6�y��SMYM�$�pV�Q���
%����:mRA{:i�2z�,<N��S����SG��y��P�d{����d��^���>�d��{G��G�4�1x[9%�(���0�Y0i
�p&��}���(�%� d����\�����A�m�R}���m�Fv�pK���2�9�)���N	-�}����$J5dc��A�j{o�dow���{5h��� k%��
r��'�E���;E9+�+��n��W4C�$���+����L'hJn`�L3�vtv&VW���'q�1������0I��8�t�N3i]��a'�zu��g�^_6���z{m��t#|��0�������`E�I[YY������������X���+<�1��s����O�8��:!��F�h����H��0���Npv�� -�,�K�
�v����qo���5*�:IUX����m��O�}E���k��]X������q0��0J�Yr�m��iLO��>���<H�31���]5���5\��<X�������N�NS����%J[I��C��5S��{M���` '@�
4���(Etx��� ^\c<�]3�SE$r�=�:�lmn=�����gU@��5��M��v�"v�R-m���P������`�T.���`E��Y~���>��q����
E����I�:�v{:[��^8���u?�
,.{�P���������oW������SZ�����1��rA?�K\�r��P�lT�`�u%|\�+\ ���p�
 �+�x��I����`\q�$���i�
�[�4�.DPeP�����7����G0b��D����^ZW_7J��fi�\����Tw�qT����+��J���G�O�_)��TM3�j�^���]����dV)UK�|]^��(��T����������~8�J�k�������3��ZC����5����=�A�n:-�@*B{������
���3�s�vE'
�)��"�
�>�z�
%D�NB
�%�5���������4w:m��[�X-�9x��q[�S�@j�n@�[V
�)�C����B��|�`!�cU�
�����D7w���oR>�f���l��%�xP�t�Q��=�2�d�\�i�K�	c>�sZ�%�r9��7�`b��������WZw5�����PF����s��������7�5x�����:������X<p�:Wp|DI2���.�.�bL94t��\�!J~�{�AL"`�J�y�u�}��H��gLpu�<�$5�M���� �l~*d�\�b����a����ND/���i�M���s���Xy��{�Q��l�/�.H
��E��>��nt��Vo��`���j�~z���^�G�Y5b=����|zC���E�;)��Q�������>C��E���0f���w�D��*m��vi;��f'\�85:A�yk@|���E��=t7e�YFZ��	�V<��Px%�eF,�nK�cW�]����R�~���UI�kW*������J�x6��~i�@4
_�G#�|�uC��x�U���u��/���n�d
���HE`2�tO��f���g����n��T�NB��jGd��;Y�D���f���� -�&I.:���� m�EZ�}2��I�:����k�*�l��q$j���������.e������Qf�p����%#<������4��4N�����zQ6����:�S!��xz�������Y��V���d�����U$.����7���_kd]$��`�`B�{Jh�,���u�'�I��H�.�n�����$>�
 w�F��B�QU���p�������e�a�gl�&z���,�tNu\F��H���^�I;Ih
��8Q�<LPd�W���16��(��Q��L�fy{mM����[�aw�l�D�?-7�3��ez#>�C����T��G����Ue������R��WjJm�y�^��4*��x�U�<5�B����J^a~]�:��������V�8|j�(��s~��:T� �x��z0��R|�(��I�������yv�]���tm{�WF����sR
��}Q����)��B��,������X���}�l[bj��SkE�Xr��E_Ad����5��x��`�0��&ct��V���62\��������)�i9��H9"�����3�_?O1n������{�����(P�q�� l�O)IYH��P��a��"�Z�C|��p�Qy�8NK)�D������?���;�=�=X�L�w$�@��^�{�V����q��]�7Xh@^����!�~>|x��+�����%w�2�D��^�mC��P��r&4��g��h-F66��Kl���!C5�0�
��q�{�������w�����X��+g�NG�$�����S5���te�^GG�����6�6�/v��R������U��q�y��L�k�N�����g�p*3HR��iI&ov���7��
�����
��O��Ta�o�R7��2�t=}��R
���f������9����[p�nq�����2g��5�N�9u��3nq�-����8���3��D�3�r���3.����8�g���[�q�������3����������8����[p��wK7q9G��o���uq�-���8���s���M\�Y3�����]�q�3nq�-������q�u�s���&n�ng���[�q�3nq�}g	p�wgIN�y7u��3nq�-����8���3��n���9_�M����[�q�3nq�-����������f�7r���8�����[s�c�;:�n�N.��������.���1�8�������9:�n�V�:Un�Vn�ng���[�q�3nq�}Gg�-���6s�������8�g���[�q��w[�ry���o���wq�-���1�8����;��s�������q�#f�0��%~{!��SlfMOuP������t+����S�S�S��S�S�S��1J�n(�t�Rq&�y���K,�^+*��4kV�NN���P,��li��Vg��G�q#0}�cv��P�r�t��� ���a"�i'���{&����.��#x����N����_��8����q�Cf%���b	�%�z�A��P#�!-T�o�8r�Fv����A2�1O����Q_#�����E(��2�������P7��#*2��V�g��I��;�O�?���Ds��"���A'J�i����|����p����v4�z��v�����X��$7WCvp@(������z*}(}���w'�PJ�>B����ua�%�8j
�������0sI>�0V)jP���J���C0)u?���t+�D�l��N9�4d�q��n�Z�����h:
�N��9L7�S��,},}��[�5�0=|�.@����h�|8�v:�q�!���~��V0'G����f���(�'��{:��t���@����9@��@hJ������q��q�G�dxY�x��,^�{�'�j���*�,�?-�39����7�G3��������g%5NqZ����!�k5����\'��~v���B{��.�k.x(}����L
�d�m2
�
��\<�����l�3����E�J�[b���[�d|�+�R�EQ���w�EqZm���;��Y}��-~���;.�a������AV:�5��������@�L����;R�ZN��h��)�y�(����E������e�=��nHO�~H��r}�8y�_�����.�`��8
:�����CW�9mRA{x�E5����i�r���a�v�v�1���C��:{2Yg�����$�Y�K��;l���`C��G���Q9GQ�I��E��t9�9��*3��b-Y��F�������}yP�
�����_������S�3r
#38Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo Nagata (#37)
Re: Implementing Incremental View Maintenance

Note that this is the last patch in the series of IVM patches: now we
would like focus on blushing up the patches, rather than adding new
SQL support to IVM, so that the patch is merged into PostgreSQL 13
(hopefully). We are very welcome reviews, comments on the patch.

BTW, the SGML docs in the patch is very poor at this point. I am going
to add more descriptions to the doc.

Show quoted text

Hi,

Attached is the latest patch (v8) to add support for Incremental View
Maintenance (IVM). This patch adds OUTER join support in addition
to the patch (v7) submitted last week in the following post.

On Fri, 22 Nov 2019 15:29:45 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

Up to now, IVM supports materialized views using:

- Inner joins
- Some aggregate functions (count, sum, min, max, avg)
- GROUP BY
- Self joins

With the latest patch now IVM supports subqueries in addition to
above.

Known limitations are listed here:

https://github.com/sraoss/pgsql-ivm/issues

See more details at:
https://wiki.postgresql.org/wiki/Incremental_View_Maintenance

* About outer join support:

In case of outer-join, when a table is modified, in addition to deltas
which occur in inner-join case, we also need to deletion or insertion of
dangling tuples, that is, null-extended tuples generated when a join
condition isn't met.

[Example]
---------------------------------------------
-- Create base tables and an outer join view
CREATE TABLE r(i int);
CREATE TABLE s(j int);
INSERT INTO r VALUES (1);
CREATE INCREMENTAL MATERIALIZED VIEW mv
AS SELECT * FROM r LEFT JOIN s ON r.i=s.j;
SELECT * FROM mv;
i | j
---+---
(1 row)

-- After an insertion to a base table ...
INSERT INTO s VALUES (1);
-- (1,1) is inserted and (1,null) is deleted from the view.
SELECT * FROM mv;
i | j
---+---
1 | 1
(1 row)
---------------------------------------------

Our implementation is basically based on the algorithm of Larson & Zhou
(2007) [1]. Before view maintenances, the view definition query's jointree
is analysed to make "view maintenance graph". This graph represents
which tuples in the views are affected when a base table is modified.
Specifically, tuples which are not null-extended on the modified table
(that is, tuples generated by joins with the modiifed table) are directly
affected. The delta of such effects are calculated similarly to inner-joins.

On the other hand, dangling tuples generated by anti-joins with directly
affected tuples can be indirectly affected. This means that we may need to
delete dangling tuples when any tuples are inserted to a table, as well as
to insert dangling tuples when tuples are deleted from a table.

[1] Efficient Maintenance of Materialized Outer-Join Views (Larson & Zhou, 2007)
https://ieeexplore.ieee.org/document/4221654

Although the original paper assumes that every base table and view have a
unique key and tuple duplicates is disallowed, we allow this. If a view has
tuple duplicates, we have to determine the number of each dangling tuple to
be inserted into the view when tuples in a table are deleted. For this purpose,
we count the number of each tuples which constitute a deleted tuple. These
counts are stored as JSONB object in the delta table, and we use this
information to maintain outer-join views. Also, we support outer self-joins
that is not assumed in the original paper.

* Restrictions

Currently, we have following restrictions:

- outer join view's targetlist must contain attributes used in join conditions
- outer join view's targetlist cannot contain non-strict functions
- outer join supports only simple equijoin
- outer join view's WHERE clause cannot contain non null-rejecting predicates
- aggregate is not supported with outer join
- subquery (including EXSITS) is not supported with outer join

Regression tests for all patterns of 3-way outer join and are added.

Moreover, I reordered IVM related functions in matview.c so that ones
which have relationship will be located closely. Moreover, I added more
comments.

Regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

#39Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Tatsuo Ishii (#38)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Note that this is the last patch in the series of IVM patches: now we
would like focus on blushing up the patches, rather than adding new
SQL support to IVM, so that the patch is merged into PostgreSQL 13
(hopefully). We are very welcome reviews, comments on the patch.

BTW, the SGML docs in the patch is very poor at this point. I am going
to add more descriptions to the doc.

As promised, I have created the doc (CREATE MATERIALIZED VIEW manual)
patch.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

Attachments:

create_materialized_view.patchtext/x-patch; charset=us-asciiDownload
diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml
index 964c9abbf7..92f5668771 100644
--- a/doc/src/sgml/ref/create_materialized_view.sgml
+++ b/doc/src/sgml/ref/create_materialized_view.sgml
@@ -62,36 +62,167 @@ CREATE [ INCREMENTAL ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_na
       of the materialized view are immediately updated when base tables of the
       materialized view are updated. In general, this allows faster update of
       the materialized view at a price of slower update of the base tables
-      because the triggers will be invoked.
+      because the triggers will be invoked. We call this form of materialized
+      view as "Incremantal materialized View Maintenance" (IVM).
      </para>
      <para>
       There are restrictions of query definitions allowed to use this
-      option. Followings are allowed query definitions:
+      option. Followings are supported query definitions for IVM:
       <itemizedlist>
+
        <listitem>
         <para>
          Inner joins (including self-joins).
         </para>
        </listitem>
+
        <listitem>
         <para>
-         Some of aggregations (count, sum, avg, min, max) without HAVING clause.
+         Outer joins with following restrictions:
+
+         <itemizedlist>
+          <listitem>
+           <para>
+            Outer join view's targetlist must contain attributes used in the
+            join conditions.
+            <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT a.i FROM mv_base_a a LEFT
+JOIN mv_base_b b ON a.i=b.i;
+ERROR:  targetlist must contain vars in the join condition for IVM with outer join
+            </programlisting>
+           </para>
+          </listitem>
+
+          <listitem>
+           <para>
+            Outer join view's targetlist cannot contain non strict functions.
+            <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT a.i, b.i, (k > 10 OR k = -1)
+FROM mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i;
+ERROR:  targetlist cannot contain non strict functions for IVM with outer join
+             </programlisting>
+           </para>
+          </listitem>
+
+          <listitem>
+           <para>
+            Outer join supports only simple equijoin.
+            <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a
+a LEFT JOIN mv_base_b b ON a.i>b.i;
+ERROR:  Only simple equijoin is supported for IVM with outer join
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b,k,j) AS SELECT a.i, b.i, k j FROM
+mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i AND k=j;
+ERROR:  Only simple equijoin is supported for IVM with outer join
+            </programlisting>
+           </para>
+           </listitem>
+
+          <listitem>
+           <para>
+            Outer join view's WHERE clause cannot contain non null-rejecting
+            predicates.
+            <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a
+a LEFT JOIN mv_base_b b ON a.i=b.i WHERE k IS NULL;
+ERROR:  WHERE cannot contain non null-rejecting predicates for IVM with outer join
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a
+a LEFT JOIN mv_base_b b ON a.i=b.i WHERE (k > 10 OR k = -1);
+ERROR:  WHERE cannot contain non null-rejecting predicates for IVM with outer join
+            </programlisting>
+           </para>
+          </listitem>
+
+          <listitem>
+           <para>
+            Aggregate is not supported with outer join.
+            <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b,v) AS SELECT a.i, b.i, sum(k) FROM
+mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i GROUP BY a.i, b.i;
+ERROR:  aggregate is not supported with IVM together with outer join
+</programlisting>
+           </para>
+          </listitem>
+
+          <listitem>
+           <para>
+            Subquery is not supported with outer join.
+            <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a
+a LEFT JOIN (SELECT * FROM mv_base_b) b ON a.i=b.i;
+ERROR:  subquery is not supported with IVM together with outer join
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a
+a LEFT JOIN mv_base_b b ON a.i=b.i WHERE EXISTS (SELECT 1 FROM mv_base_b b2
+WHERE a.j = b.k);
+ERROR:  subquery is not supported by IVM together with outer join
+             </programlisting>
+           </para>
+          </listitem>
+         </itemizedlist>
         </para>
-        </listitem>
-      </itemizedlist>
 
-      Prohibited queries with this option include followings:
-      <itemizedlist>
+       </listitem>
+
        <listitem>
         <para>
-         Outer joins.
+         Subqueries. However following forms are not supported.
         </para>
-       </listitem>
+
+        <para>
+         WHERE IN .. (subquery) is not supported:
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm03 AS SELECT i,j FROM
+mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k &lt; 103 );
+         </programlisting>
+        </para>
+        <para>
+         subqueries in target list is not supported:
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm05 AS SELECT i,j, (SELECT k
+FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+         </programlisting>
+        </para>
+        <para>
+         Nested EXISTS subqueries is not supported:
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm11 AS SELECT a.i,a.j FROM
+mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE EXISTS(SELECT
+1 FROM mv_base_b c WHERE b.i = c.i));
+         </programlisting>
+        </para>
+        <para>
+         EXISTS subquery with aggregate function is not supported:
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_exists AS SELECT COUNT(*)
+FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i =
+b.i) OR a.i &gt; 5;
+         </programlisting>
+        </para>
+        <para>
+         EXISTS subquery with condition other than AND is not supported:
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm10 AS SELECT a.i,a.j FROM
+mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) OR
+a.i &gt; 5;
+         </programlisting>
+        </para>
+        </listitem>
+
        <listitem>
         <para>
-         Subqueries.
+         Some of aggregations (count, sum, avg, min, max) without HAVING
+         clause.  However, aggregate functions in subquery is not supported:
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm09 AS SELECT a.i,a.j FROM mv_base_a
+a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i;
+         </programlisting>
         </para>
         </listitem>
+      </itemizedlist>
+
+      Unsupported queries with this option include followings:
+
+      <itemizedlist>
        <listitem>
         <para>
          Aggregations other than count, sum, avg, min and max.
@@ -111,24 +242,50 @@ CREATE [ INCREMENTAL ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_na
 
       Other restrictions include:
       <itemizedlist>
+
        <listitem>
         <para>
-         Incremental materialized views must be based on simple base
-         tables. Views or materialized views are not allowed to create
-         incremental materialized views.
+         IVMs must be based on simple base tables. Views or materialized views
+         are not allowed to create IVM on them.
         </para>
        </listitem>
+
+       <listitem>
+        <para>
+         <command>pg_dump</command> and <command>pg_restore</command> do not
+         support IVMs. IVMs are dumped as ordinary materialized views.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         <command>REFRESH MATERIALIZED VIEW</command> does not support IVMs.
+        </para>
+       </listitem>
+
        <listitem>
         <para>
          When TRUNCATE command is executed on a base table, nothing occurs and
          this is not applied to the materialized view.
         </para>
        </listitem>
+
+       <listitem>
+        <para>
+         IVM including system columns is not supported.
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+ERROR:  system column is not supported with IVM
+         </programlisting>
+        </para>
+       </listitem>
+
        <listitem>
         <para>
-         Incremental materialized views are not supported by logical replication.
+         IVMs not supported by logical replication.
         </para>
         </listitem>
+
       </itemizedlist>
 
      </para>
#40Yugo Nagata
nagata@sraoss.co.jp
In reply to: Tatsuo Ishii (#39)
Re: Implementing Incremental View Maintenance

On Thu, 28 Nov 2019 11:26:40 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

Note that this is the last patch in the series of IVM patches: now we
would like focus on blushing up the patches, rather than adding new
SQL support to IVM, so that the patch is merged into PostgreSQL 13
(hopefully). We are very welcome reviews, comments on the patch.

BTW, the SGML docs in the patch is very poor at this point. I am going
to add more descriptions to the doc.

As promised, I have created the doc (CREATE MATERIALIZED VIEW manual)
patch.

-      because the triggers will be invoked.
+      because the triggers will be invoked. We call this form of materialized
+      view as "Incremantal materialized View Maintenance" (IVM).

This part seems incorrect to me. Incremental (materialized) View
Maintenance (IVM) is a way to maintain materialized views and is not a
word to refer views to be maintained.

However, it would be useful if there is a term referring views which
can be maintained using IVM. Off the top of my head, we can call this
Incrementally Maintainable Views (= IMVs), but this might cofusable with
IVM, so I'll think about that a little more....

Regards,
Yugo Nagata

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

--
Yugo Nagata <nagata@sraoss.co.jp>

#41Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Yugo Nagata (#40)
Re: Implementing Incremental View Maintenance

One thing pending in this development line is how to catalogue aggregate
functions that can be used in incrementally-maintainable views.
I saw a brief mention somewhere that the devels knew it needed to be
done, but I don't see in the thread that they got around to doing it.
Did you guys have any thoughts on how it can be represented in catalogs?
It seems sine-qua-non ...

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#42Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo Nagata (#40)
Re: Implementing Incremental View Maintenance

As promised, I have created the doc (CREATE MATERIALIZED VIEW manual)
patch.

-      because the triggers will be invoked.
+      because the triggers will be invoked. We call this form of materialized
+      view as "Incremantal materialized View Maintenance" (IVM).

This part seems incorrect to me. Incremental (materialized) View
Maintenance (IVM) is a way to maintain materialized views and is not a
word to refer views to be maintained.

However, it would be useful if there is a term referring views which
can be maintained using IVM. Off the top of my head, we can call this
Incrementally Maintainable Views (= IMVs), but this might cofusable with
IVM, so I'll think about that a little more....

But if we introduce IMV, IVM would be used in much less places in the
doc and source code, so less confusion would happen, I guess.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#43Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo Nagata (#37)
Re: Implementing Incremental View Maintenance

Hi,

Attached is the latest patch (v8) to add support for Incremental View
Maintenance (IVM). This patch adds OUTER join support in addition
to the patch (v7) submitted last week in the following post.

There's a compiler warning:

matview.c: In function ‘getRteListCell’:
matview.c:2685:9: warning: ‘rte_lc’ may be used uninitialized in this function [-Wmaybe-uninitialized]
return rte_lc;
^~~~~~

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#44Yugo Nagata
nagata@sraoss.co.jp
In reply to: Tatsuo Ishii (#43)
Re: Implementing Incremental View Maintenance

On Fri, 29 Nov 2019 09:50:49 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

Hi,

Attached is the latest patch (v8) to add support for Incremental View
Maintenance (IVM). This patch adds OUTER join support in addition
to the patch (v7) submitted last week in the following post.

There's a compiler warning:

matview.c: In function ‘getRteListCell’:
matview.c:2685:9: warning: ‘rte_lc’ may be used uninitialized in this function [-Wmaybe-uninitialized]
return rte_lc;
^~~~~~

Thanks! I'll fix this.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

--
Yugo Nagata <nagata@sraoss.co.jp>

#45Yugo Nagata
nagata@sraoss.co.jp
In reply to: Tatsuo Ishii (#42)
Re: Implementing Incremental View Maintenance

On Fri, 29 Nov 2019 07:19:44 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

As promised, I have created the doc (CREATE MATERIALIZED VIEW manual)
patch.

-      because the triggers will be invoked.
+      because the triggers will be invoked. We call this form of materialized
+      view as "Incremantal materialized View Maintenance" (IVM).

This part seems incorrect to me. Incremental (materialized) View
Maintenance (IVM) is a way to maintain materialized views and is not a
word to refer views to be maintained.

However, it would be useful if there is a term referring views which
can be maintained using IVM. Off the top of my head, we can call this
Incrementally Maintainable Views (= IMVs), but this might cofusable with
IVM, so I'll think about that a little more....

But if we introduce IMV, IVM would be used in much less places in the
doc and source code, so less confusion would happen, I guess.

Make senses. However, we came to think that "Incrementally Maintainable
Materialized Views" (IMMs) would be good. So, how about using this for now?
When other better opinions are raised, let's discuss again

Regards,
Yugo Nagata

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

--
Yugo Nagata <nagata@sraoss.co.jp>

#46Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo Nagata (#45)
Re: Implementing Incremental View Maintenance

But if we introduce IMV, IVM would be used in much less places in the
doc and source code, so less confusion would happen, I guess.

Make senses. However, we came to think that "Incrementally Maintainable
Materialized Views" (IMMs) would be good. So, how about using this for now?
When other better opinions are raised, let's discuss again

Sounds good to me.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#47Amit Langote
amitlangote09@gmail.com
In reply to: Yugo Nagata (#45)
Re: Implementing Incremental View Maintenance

Hello,

Thanks a lot for working on this. It's a great (and big!) feature and
I can see that a lot of work has been put into writing this patch. I
started looking at the patch (v8), but as it's quite big:

34 files changed, 5444 insertions(+), 69 deletions(-)

I'm having a bit of trouble reading through, which I suspect others
may be too. Perhaps, it can be easier for you, as authors, to know
everything that's being changed (added, removed, existing code
rewritten), but certainly not for a reviewer, so I think it would be a
good idea to try to think dividing this into parts. I still don't
have my head wrapped around the topic of materialized view
maintenance, but roughly it looks to me like there are really *two*
features that are being added:

1. Add a new method to refresh an MV incrementally; IIUC, there's
already one method that's used by REFRESH MATERIALIZED VIEW
CONCURRENTLY, correct?

2. Make the refresh automatic (using triggers on the component tables)

Maybe, there are even:

0. Infrastructure additions

As you can tell, having the patch broken down like this would allow us
to focus on the finer aspects of each of the problem being solved and
solution being adopted, for example:

* It would be easier for someone having an expert opinion on how to
implement incremental refresh to have to only look at the patch for
(1). If the new method handles more query types than currently, which
obviously means more code is needed, which in turn entails possibility
of bugs, despite the best efforts. It would be better to get more
eyeballs at this portion of the patch and having it isolated seems
like a good way to attract more eyeballs.

* Someone well versed in trigger infrastructure can help fine tune the
patch for (2)

and so on.

So, please consider giving some thought to this.

Thanks,
Amit

#48Yugo Nagata
nagata@sraoss.co.jp
In reply to: Yugo Nagata (#37)
Re: Implementing Incremental View Maintenance

The following review on our patch was posted on another thread,
so I quote here. The tab completion is Hoshiai-san's work, so
he will handle this issue.

Regards,
Yugo Nagata.

On Thu, 28 Nov 2019 13:00:05 +0900
nuko yokohama <nuko.yokohama@gmail.com> wrote:

Hi.

I'm using the "Incremental Materialized View Maintenance" patch and have
reported the following issues.
(https://commitfest.postgresql.org/25/2138/)

To Suggest a "DROP INCREMENTAL MATERIALIZED VIEW" in psql, but the syntax
error when you run.
("DROP MATERIALIZED VIEW" command can drop Incremental Materialozed view
normally.)

ramendb=# CREATE INCREMENTAL MATERIALIZED VIEW pref_count AS SELECT pref,
COUNT(pref) FROM shops GROUP BY pref;
SELECT 48
ramendb=# \d pref_count
Materialized view "public.pref_count"
Column | Type | Collation | Nullable | Default
---------------+--------+-----------+----------+---------
pref | text | | |
count | bigint | | |
__ivm_count__ | bigint | | |

ramendb=# DROP IN
INCREMENTAL MATERIALIZED VIEW INDEX
ramendb=# DROP INCREMENTAL MATERIALIZED VIEW pref_count;
2019-11-27 11:51:03.916 UTC [9759] ERROR: syntax error at or near
"INCREMENTAL" at character 6
2019-11-27 11:51:03.916 UTC [9759] STATEMENT: DROP INCREMENTAL
MATERIALIZED VIEW pref_count;
ERROR: syntax error at or near "INCREMENTAL"
LINE 1: DROP INCREMENTAL MATERIALIZED VIEW pref_count;
^
ramendb=# DROP MATERIALIZED VIEW pref_count ;
DROP MATERIALIZED VIEW
ramendb=#

Regard.

--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo Nagata <nagata@sraoss.co.jp>

#49Takuma Hoshiai
hoshiai@sraoss.co.jp
In reply to: Yugo Nagata (#48)
Re: Implementing Incremental View Maintenance

On Fri, 29 Nov 2019 15:45:13 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

The following review on our patch was posted on another thread,
so I quote here. The tab completion is Hoshiai-san's work, so
he will handle this issue.

Regards,
Yugo Nagata.

On Thu, 28 Nov 2019 13:00:05 +0900
nuko yokohama <nuko.yokohama@gmail.com> wrote:

Hi.

I'm using the "Incremental Materialized View Maintenance" patch and have
reported the following issues.
(https://commitfest.postgresql.org/25/2138/)

To Suggest a "DROP INCREMENTAL MATERIALIZED VIEW" in psql, but the syntax
error when you run.
("DROP MATERIALIZED VIEW" command can drop Incremental Materialozed view
normally.)

Thank you for your review. This psql's suggestion is mistake,
"INCREMENTAL MATERIALIZED" phrase is only used for CREATE statement.

I will fix it as the following:

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2051bc3..8c4b211 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1001,7 +1001,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
-	{"INCREMENTAL MATERIALIZED VIEW", NULL, NULL, &Query_for_list_of_matviews},
+	{"INCREMENTAL MATERIALIZED VIEW", NULL, NULL, &Query_for_list_of_matviews, THING_NO_DROP | THING_NO_ALTER},
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},

Best Regards,
Takuma Hoshiai

ramendb=# CREATE INCREMENTAL MATERIALIZED VIEW pref_count AS SELECT pref,
COUNT(pref) FROM shops GROUP BY pref;
SELECT 48
ramendb=# \d pref_count
Materialized view "public.pref_count"
Column | Type | Collation | Nullable | Default
---------------+--------+-----------+----------+---------
pref | text | | |
count | bigint | | |
__ivm_count__ | bigint | | |

ramendb=# DROP IN
INCREMENTAL MATERIALIZED VIEW INDEX
ramendb=# DROP INCREMENTAL MATERIALIZED VIEW pref_count;
2019-11-27 11:51:03.916 UTC [9759] ERROR: syntax error at or near
"INCREMENTAL" at character 6
2019-11-27 11:51:03.916 UTC [9759] STATEMENT: DROP INCREMENTAL
MATERIALIZED VIEW pref_count;
ERROR: syntax error at or near "INCREMENTAL"
LINE 1: DROP INCREMENTAL MATERIALIZED VIEW pref_count;
^
ramendb=# DROP MATERIALIZED VIEW pref_count ;
DROP MATERIALIZED VIEW
ramendb=#

Regard.

--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo Nagata <nagata@sraoss.co.jp>

--
Takuma Hoshiai <hoshiai@sraoss.co.jp>

#50Yugo Nagata
nagata@sraoss.co.jp
In reply to: Alvaro Herrera (#41)
Re: Implementing Incremental View Maintenance

On Thu, 28 Nov 2019 11:03:33 -0300
Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

One thing pending in this development line is how to catalogue aggregate
functions that can be used in incrementally-maintainable views.
I saw a brief mention somewhere that the devels knew it needed to be
done, but I don't see in the thread that they got around to doing it.
Did you guys have any thoughts on how it can be represented in catalogs?
It seems sine-qua-non ...

Yes, this is a pending issue. Currently, supported aggregate functions are
identified their name, that is, we support aggregate functions named "count",
"sum", "avg", "min", or "max". As mentioned before, this is not robust
because there might be user-defined aggregates with these names although all
built-in aggregates can be used in IVM.

In our implementation, the new aggregate values are calculated using "+" and
"-" operations for sum and count, "/" for agv, and ">=" / "<=" for min/max.
Therefore, if there is a user-defined aggregate on a user-defined type which
doesn't support these operators, errors will raise. Obviously, this is a
problem. Even if these operators are defined, the semantics of user-defined
aggregate functions might not match with the way of maintaining views, and
resultant might be incorrect.

I think there are at least three options to prevent these problems.

In the first option, we support only built-in aggregates which we know able
to handle correctly. Supported aggregates can be identified using their OIDs.
User-defined aggregates are not supported. I think this is the simplest and
easiest way.

Second, supported aggregates can be identified using name, like the current
implementation, but also it is checked if required operators are defined. In
this case, user-defined aggregates are allowed to some extent and we can
prevent errors during IVM although aggregates value in view might be
incorrect if the semantics doesn't match.

Third, we can add a new attribute to pg_aggregate which shows if each
aggregate can be used in IVM. We don't need to use names or OIDs list of
supported aggregates although we need modification of the system catalogue.

Regarding pg_aggregate, now we have aggcombinefn attribute for supporting
partial aggregation. Maybe we could use combine functions to calculate new
aggregate values in IVM when tuples are inserted into a table. However, in
the context of IVM, we also need other function used when tuples are deleted
from a table, so we can not use partial aggregation for IVM in the current
implementation. This might be another option to implement "inverse combine
function"(?) for IVM, but I am not sure it worth.

Regards,
Yugo Nagata

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Yugo Nagata <nagata@sraoss.co.jp>

#51Yugo Nagata
nagata@sraoss.co.jp
In reply to: Amit Langote (#47)
Re: Implementing Incremental View Maintenance

On Fri, 29 Nov 2019 15:34:52 +0900
Amit Langote <amitlangote09@gmail.com> wrote:

Thanks a lot for working on this. It's a great (and big!) feature and
I can see that a lot of work has been put into writing this patch. I
started looking at the patch (v8), but as it's quite big:

34 files changed, 5444 insertions(+), 69 deletions(-)

Thank you for your reviewing the patch! Yes, this is a big patch
athough

I'm having a bit of trouble reading through, which I suspect others
may be too. Perhaps, it can be easier for you, as authors, to know
everything that's being changed (added, removed, existing code
rewritten), but certainly not for a reviewer, so I think it would be a
good idea to try to think dividing this into parts. I still don't

I agree with you. We also think the need to split the patch and we are
considering the way.

have my head wrapped around the topic of materialized view
maintenance, but roughly it looks to me like there are really *two*
features that are being added:

1. Add a new method to refresh an MV incrementally; IIUC, there's
already one method that's used by REFRESH MATERIALIZED VIEW
CONCURRENTLY, correct?

No, REFRESH MATERIALIZED VIEW CONCURRENTLY is not the way to refresh
materialized views. This just acquires weaker locks on views to not
prevent SELECT, so this calculate the content of the view completely
from scratch. There is no method to incrementally refresh materialized
views in the current PostgreSQL.

Also, we didn't implement incremental refresh on REFRESH command in
this patch. This supports only automatically refresh using triggers.
However, we used the code for REFRESH in our IVM implementation, so
I think splitting the patch according to this point of view can make
sense.

2. Make the refresh automatic (using triggers on the component tables)

Maybe, there are even:

0. Infrastructure additions

Yes, we have a bit modification on the infrastructure, for example,
trigger.c.

As you can tell, having the patch broken down like this would allow us
to focus on the finer aspects of each of the problem being solved and
solution being adopted, for example:

* It would be easier for someone having an expert opinion on how to
implement incremental refresh to have to only look at the patch for
(1). If the new method handles more query types than currently, which
obviously means more code is needed, which in turn entails possibility
of bugs, despite the best efforts. It would be better to get more
eyeballs at this portion of the patch and having it isolated seems
like a good way to attract more eyeballs.

* Someone well versed in trigger infrastructure can help fine tune the
patch for (2)

and so on.

So, please consider giving some thought to this.

Agreed. Although I am not sure we will do it as above way, we will
consider to split the patch, anyway. Thanks.

Regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

#52Yugo Nagata
nagata@sraoss.co.jp
In reply to: Yugo Nagata (#51)
Re: Implementing Incremental View Maintenance

On Fri, 29 Nov 2019 18:16:00 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

On Fri, 29 Nov 2019 15:34:52 +0900
Amit Langote <amitlangote09@gmail.com> wrote:

Thanks a lot for working on this. It's a great (and big!) feature and
I can see that a lot of work has been put into writing this patch. I
started looking at the patch (v8), but as it's quite big:

34 files changed, 5444 insertions(+), 69 deletions(-)

Thank you for your reviewing the patch! Yes, this is a big patch
athough

Sorry, an unfinished line was left... Please ignore this.

--
Yugo Nagata <nagata@sraoss.co.jp>

#53Michael Paquier
michael@paquier.xyz
In reply to: Yugo Nagata (#52)
Re: Implementing Incremental View Maintenance

On Fri, Nov 29, 2019 at 06:19:54PM +0900, Yugo Nagata wrote:

Sorry, an unfinished line was left... Please ignore this.

A rebase looks to be necessary, Mr Robot complains that the patch does
not apply cleanly. As the thread is active recently, I have moved the
patch to next CF, waiting on author.
--
Michael

#54Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Michael Paquier (#53)
Re: Implementing Incremental View Maintenance

Michael,

A rebase looks to be necessary, Mr Robot complains that the patch does
not apply cleanly. As the thread is active recently, I have moved the
patch to next CF, waiting on author.

Thank you for taking care of this patch. Hoshiai-san, can you please
rebase the patch?

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#55Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo Nagata (#50)
Re: Implementing Incremental View Maintenance

One thing pending in this development line is how to catalogue aggregate
functions that can be used in incrementally-maintainable views.
I saw a brief mention somewhere that the devels knew it needed to be
done, but I don't see in the thread that they got around to doing it.
Did you guys have any thoughts on how it can be represented in catalogs?
It seems sine-qua-non ...

Yes, this is a pending issue. Currently, supported aggregate functions are
identified their name, that is, we support aggregate functions named "count",
"sum", "avg", "min", or "max". As mentioned before, this is not robust
because there might be user-defined aggregates with these names although all
built-in aggregates can be used in IVM.

In our implementation, the new aggregate values are calculated using "+" and
"-" operations for sum and count, "/" for agv, and ">=" / "<=" for min/max.
Therefore, if there is a user-defined aggregate on a user-defined type which
doesn't support these operators, errors will raise. Obviously, this is a
problem. Even if these operators are defined, the semantics of user-defined
aggregate functions might not match with the way of maintaining views, and
resultant might be incorrect.

I think there are at least three options to prevent these problems.

In the first option, we support only built-in aggregates which we know able
to handle correctly. Supported aggregates can be identified using their OIDs.
User-defined aggregates are not supported. I think this is the simplest and
easiest way.

I think this is enough for the first cut of IVM. So +1.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#56Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Michael Paquier (#53)
Re: Implementing Incremental View Maintenance

On Fri, Nov 29, 2019 at 06:19:54PM +0900, Yugo Nagata wrote:

Sorry, an unfinished line was left... Please ignore this.

A rebase looks to be necessary, Mr Robot complains that the patch does
not apply cleanly.

Is this because the patch has ".gz" suffix?

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#57Takuma Hoshiai
hoshiai@sraoss.co.jp
In reply to: Tatsuo Ishii (#54)
1 attachment(s)
Re: Implementing Incremental View Maintenance

On Mon, 02 Dec 2019 10:01:18 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

Michael,

A rebase looks to be necessary, Mr Robot complains that the patch does
not apply cleanly. As the thread is active recently, I have moved the
patch to next CF, waiting on author.

Thank you for taking care of this patch. Hoshiai-san, can you please
rebase the patch?

Sure,
I re-created a patch. This contains 'IVM_8.patch' and 'create_materialized_view.patch',
and I checked to apply a patch on latest master.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

Best Regards,

--
Takuma Hoshiai <hoshiai@sraoss.co.jp>

Attachments:

IVM_v9.patch.gzapplication/octet-stream; name=IVM_v9.patch.gzDownload
�Vj�]IVM_v9.patch�<kw�����W�xwg�`�g���$��q.����9!5X��=l3s�����[j	����97�Pwuu����d��NY�1scf���nE!�����m����&>������;����<�������������;;{{�F��w�^�o�����F����y�����e�6<t.�w������<���lnM<~���tcZ���?�y�<n�@/
C���Pp{Ze�������q�<=��0����=�4���7����z�����r�O'A����:���Q�p�N���\�s���q�-�>R1��C>h�s��c��m^@*	��GA�FBC~_���JH
\�Hi�#Ay���f��}�dK�@c1k��E[j1�5�����Z�J�����O�4g�VM��U��O[�5��Xg��W����&�����C�l�Ow&�;���e���?M�Y�<du����A�B>��9\	�
Z	)<X�4Z��"r#��Y���j�l�����>��y�����^���������u?���!���L+��1�(o����������u�a���@��+3r�v�%s���j�f��������^�mn6F�w���B�l�:����) KQ|���1�>Q��������N��pcyI���$IL������~>����B<�T�s�X����1r�&|���1,|��M
t& 91@�<�b���O��,Zp����,
����;��0bV������k[��d�v�c���g�#�-�R���ET�|��Q�p������`V������d=����4�0��{��M-`Z(��B�fm���"tme`��� m�
���V��W�n]|������O�v�(��i����N�$b[�,�.0�R�}�a�3zk�4@���zt�Q��?�\}�����K��������s�$����do�q��P�(Y,�%��
�f��ilFuD"���xZ��+T��� q�%p���KX��7mPk�����6���{�����n|tI�8�B��J*�zJ�,�673��/�b8�1b`�>���T�1�����$���4����0�������,��g���oXg�������YM��\^@�-gl���w����\��i��M�e�O&M�e��./�Z��F��B�>^�)�
��V���y_�.�(=��� #�J�*�i��O( �M���f��d���f'�k�U���c�,���0���b��
��.9r�p��������a��Z��
�T�o��i^�%+f�2�V��n���R�v��P��B�N�5�>��8���*=����;�bF����R���!��m\e�"�4
v{��\�#��NPv�zC��p~��P�|�}����K���|"
HkCi;3���0	3C�2S+,�1���)w$Q27�k��K�.?�g��'E�	���F�L@D��yUD�X��D����R�f2$�v
�I��dm�_�
�F_Q�.P�&��ui5����4�k"z���d���)���f��bx�}�T`�{���)�J�Yl)s)��m�C���l2Cq����:��kc�f�����k��<bA.�L�-*���x!f���<D�+^�G����O��3����%�y��)���-Z�2�������}����I���T�h�>}�j �=�z�RV����nB�����Ly�-�o�"���=C���EW�.?�G�v�����J
X��B���x
����y�1F@�>��|�=�N�<�Nj�{9� ����� ���4UK:m0�^�rI�Y73��]>����+{������0�=Z��Xe��:y�����\��5B��.����FFQfiR��V��(�RMk�z?��#c��dtR.N��8��Yz����9Z}�ztt��l�LA��;�#N�K��o_�/P��S�y6���S�������|�L/����)��:�����#����q+�c!���I�# �p��'��<S��W\4���Os�pl�9�^�;�|q�R
�qz7�4����\�G�$X���B�5Pf!3���A����)t���w��-G��MI��,��:��g�z��o�;n'��*KS(Wv�������
�,�����s�*�^7>
E���e���9�����~4��I��g%V����=a��w~����*�o��/� 3K6�^0�kp�w�xz�����������E�{�*��|�@S���V����#W��4��B��e�<�Zqh��5o�Yv����~Y�s4��G�n��<?�?:������E������{X��X�Se��
U,����d���*��&�^�/��-};�%�`�d�k��[��N�@����
�V�p���������3�;(7��������k�bw�4`O�m���7p����ZS�����U�x���Ax�C9D���+0tDTO[�d	3����m���s��r�)�&�Gn9�&���C�0Pv��"����)�:�h�N*�]qk����N������8��M{r�� �X������W@�=�A��	��G@���� ��V�m�*���l�EUI����q����k���r��	=�-�_[q27B�Z�!	k�i���;�Q5�)�����7��^$^��~�����@�0R����*�2DDs������O'� ��A�_;�	�"�`����Z���R����h���c���uj�z�S�w��������EJ�i*�z@�#(�����b��In�*���8�9 .������n�F�ybq*��:��#�Q���zr�T�MxPyNy��W��Z���(�CFu����l��U��BH�?�Lw� �����������(��(*Y$�����������/��"��b<��o-����� �E�5�_*����i�����8�E�z�i6���,�J�*���kHG]?^�3�_�^<��0l����������R ����+��r�bk/�YeH%�c�����$=��������"P�U�]��5�qW<�\�_�	�	���`'�V�������`<�BZ�p��k�r�
$c�����z�:I�5Z�>��
*�8d�{&
m_0����a��a���.�*��"��xY�>:��%�V���� Y�-#m�q{$�v��7B��\���;�eB���3Q������t/6P0����J��Ip���
N�Ff1�|`���nP�1�ts�A����^�"�6��1]L�DWI���7>�A)�5#�u}�1�w3	/`���_&�}�4��}e�Wc���Hm��.&�6M]�� o%�D�?��v��pi�>�,����������Yn	�"��wq�r���<H�
1��i�
�m;�#
AU��6�y4V5Q��.��\t���`!�q{���W�1�sD/���G�T����5���i��+�zB��
g��Rv����������f}�;C��P����>D�k�X)���=��z���1��'�s�FoL�����-�w�+p�A
�B'�_������|���8��#�M��� iQ�X�R/+�:���xT#�m������HW���a<G�&^[a�'�%9�!(�?����4ch����~�Y���c�H���f�FyR!������
L�����: 3Ed�b��<k?{<�0o,�|A�Ex�&c����H�E��W��������y����C������3�6k��UmT*)�@����3�K<Q��n�T�����E8d&q���]���3��a?��JeD�]��Qc��*��Q������`{!b�	x�kzrl1��OL]y!�_�����e��o�����~�����"�n�.@��p|
I6�6������s���3x��A]���5���d?���QG��/�`o*N�!QQ7�"�2���:7#-�ac�:{L�O����<mJ�}���E5Na�a29w���$(EH�w����-�]X���&�lsE	zM���n��,�v������nS.F*)KC"��P������ ��lx-�����,��r�q
�6Y����� ZQ]�*X'b�h�� V���T�����7u�(��k
J���-e����D�g���\���~�."�T�va�+$e���P0����.�?���3�lK����bu������0��	B1�!���N���0������O�i��*��<��pC�+5���
^��:��?���%M��5Vg��@ !t����Z��K���:��lZ0v�kWa
(��m���W�-������� Y�k��.8�R-��Z�h�H)���MjI*�a�o�^��6����^f3�x"�y�X�Q��z@[\�&���<�D_�:�i�3����*��������j- X�T��=���������W��i���)#z���i�5wf3��+k9
��K,z|M7Zv,z(zb�Y�_�d���8�&���������g3�0���+;;������u>Dl�~�l�!*��a�U�v��1�K�@�F�?������)�R

=��q{���@v���,g�Yme4����b�H5�mq�S� a����%��d2���K�=o[%�a��bSB>�'�0����ix>t{E��9�S6Dp/�|�#l7H�>H/?����F�&SO�"{QGX%��)�Z���/�I����Zj,�9�,�~
�P�^�6&���fS?��j<����/@E��e-_v���K8���bC�:4��l�%N�|�����	x���
_���
� ��%s��S1�d�$f��
�A��;��fa*H�s}t�
��qZl��0��O�
������%��D��T!�_�"�!8����=l����
`������z��Z-d��j<����}�cJ+����1U���/K��B��Mn��9�8 �d�:�����b����i��)$����4A�piX`���R���Ul�x�4N��i,���$>T�@e-�#]CR!��\���[����e<�)����S���)��J���^0�����-���t�6�1��7<]G
�����!`�����Y������IV�����N��)
M�� CRM$��nA	r#�`8Y
<,���c�G���co���P���V�H���\D(����
�9"0���=�����Wr�����w���B���
��X���_7*��0'�j&�)�|R)M[*�@��@�'�Q�q�i*�?�h���#t������FA`��4��gN�Ej�O?���"�[�l��q�ZqE�Wd'!������l,�6kP�ZbV@Hu>hQ�����L�@�mE|�����J �Y����
�������� ��R/R�����z��T���y��
C;=`p��(K|5W��c1����&��9L�#�oT;��F��=G0E�����Mz���0��l�����R�S�24r�[`gv��������J���!��r�8U����5���"����`�*�B.��Z��=�)���V9��PXh0������(YT�I�R~
>un)�_`�����r|����������(�'��ELZ������x�}@W\�&��	� �a)�\�������;B,(2jGQ��k{$M&'}��{!�����o�����v�>�g�N�A��X�j�a���5(=�gBkt�����A�&����~�n�G{�v�&I��p����w��$�r����;���9��D�Y7|,F��`��n��2���x+F��`T�� -fC���N��3���oH���	��y`_+��W���{���y�1d*x�gh�*�
�8Qs�E�U���c5T3m��I�^���:QE�8�Pt�X�����"����c#k/^n����WWwy�B�b������i���������`!��E��%�y#K>r��<<F�������k�u��EYr������������������n��H����c�zQ����y-�Mu���7w�O��~��cg����	#��r������HtH����r���6�&���,��A���f;��A:w`�rx�����U�8�,�
�P����h�;l��6���E��uQ�c�7����+~!���Qm���X������+����&������IK&�F&�5���_����?�������Q#��������:Y���y�\l/'���������F�h��w'^x�N��G�#nmQ�)_�%��7�BX�U�=p|���NjGX{@�}��B8�����h�w�����td\U3$\=��|��W���u�#��&,p�����B�3� l�Pk����M�^E�K�2
SO�X�n5�M�h|Z_��:Z[�HpP��/�����*L��I�?��;)���k���=),9�0?��*_�Z����nP������(h��{t�K�j�������qD_,sJ`�������n���5�����r�g�
��d���][��
�����b����M>@�H���Q(����Z��5)A�-H��mk�N������d�E��b��ZQ^t*�U*����B�RP�""P8D��:��)��`lj��x,y�qhf���L��v�x�!wQxE�;84
j{y��ik����E�o��I�Xf�%�jJsO�K���.?-n��g����{m��:����
�IT��$�e��~�/���f��.���y<�^X�3diwR/4��*w/��<��d��4�E�2Th�>��G
�'��W�#S�v�{�(���U�,��rJ���b����S*P�.��Imby��dJ�c��m��T�}��e��|��.a��
�>+\qU�~�)�X��]��6b���z�L��%|Q&���g���j6�^�<
��[���Zg�]2+���;��16��u`�f�_M=2���9����52|�1����;|>��Q�����NGI��n)-��1��1�����f0���<����Q��C��r�����+�^����~��'��U�������R��/��gq��`�u���h��9��K�&�����M��c
��7|��8�3(�/��/hekYQ���T}s������R�1_�����0&uf����);5�"��"�����Nf!���h�������)U�������VR������e�w�s�i
 2�Qg/@tz���^�'9D����\{�w��������I�PVV�������x-����s\,�x�]v����� ������Z��8������Gb79��5	:\=�(������1ph��
E�6�����m���*�>�����������_j2��P�l���n(��e'��h�����C3*J<3����<q%he�3�����](��T�����{7i������.(�� 
��T�f*_���h0;>�JA�F��/ZW���5j�l���pM�-,�b�1�������������q�Wie�XO	�����ZY�0j��pI�%��L[Lgv�{��A=��M2xlr���M,����.�D,
}�[\��)�I�f["������fO���~Z�W�'�'�_)2��*�������>�j��PP�,�W��V<Z@Q���F���q��H�~Xb�@
K��������h���X��zq����~+5�F��z��U����,��efg��|�����eZ��"	3�{�(N�n��:��7�m��5aJ#R(^-�BQ���|B"�������R�2�~����,s���|��xk/����(>������O�:
�O�y�(
������G�.��$O���S������;���=�59��n��4&�5[�<�?�p&�x��
P�vL?/��}m�����*�����I���>���RP�6Wd���(��V�S
p��-%��f�V�6�mkm����z���������4��8�
���vit��9)v
e�^��9z�i�*�'�V�zH��'f�� s
�������Z��7���jh2��/��}���%�N�^Rd�C���������'�^a"2��*.�`9j�1�M�^&��&�x>������k!�K�Rt�Hen��5�M��<K��m}�<<���/���\�w�@����&�����t���O��k2��x�a\��8cm�?�MQ(;����t�3*��������5�<��x~���G�����G�!�DR	�T���{�B�u<���=`]�[{��wU����t`����|,�:]cfL3�^N,�:Ax3�X���Rc�N��P%������'�L�����a`�.���.BNI-�~��"�]J��j�u�	t�5��5���A�������e,�?�c�~F@/ev[��b�[E���T���U)�D�<�$U�
����q>R�U�#,�P�<���QO��F]3P�?�%C�L�F�N��bE�1bg�4����i��8E�����4�I��z�@?5��������������&�t6044��Y[o��9�A#�'In����`7��k���?�F�����17�����B�h�"�4�J\�
�l���_'�b��T)�������0��Q��wR�R�;)%Z��l������kV�c��^	1����F���z�����������{����S������_��1��+)������~�}���cd8��((7��\=�/�O�
��O^m�X�wR��h��fm��J�AJ��\"����������5<��N��(����7���8r}!��T�K?%�Y?��9*KZ��~���s�S����>@Y�1:DK�r�������1��u.�e����������[�b]���!�$,����Iz/��������2��[����k�_y��������JZ0��������$`�cz�9���[e7%�T��=]
a�	��v�"Y��`P����x�<qA�,��RuU����5����e�
����[8��1�������nb�_��v�<
3�]�2�����N)x����i?{�iO��41&��)�q)�C9��an�t���8�:�vF��eB.��e�[����r<ln�41���7���;���-�����|��!�9�5���*�Z�n�Z�����=.pwS&��o��Iq���+������9���������V/���^�������4���U8��4�`1d<3���SD��1�(�l����e(���8��x��Y?�7�to��z����_�\y��x��m��h���V�w����Qm����|[?x[�=WN8�5��\�b^���r���8z��0x��>��u����#����E��?�L���������c�I��a� xX�#��T�c�0�^la�N�6����^Q��'����cN�+�������a ��)�����������% ����c��'K��CE�?�XX�!D��!��kP#��n�8��5	];1i�6q2��%������>�c��:��H����^�����<bzysl+I�y2��� P�_GR9>S���3��[m�����}�(i=�
�G��Pe�DX]�;��l��A;��lhW�f��5&��.��i� I�`6�l	K�t]�v�N42}Hpt�/E�w������>%+2M�#&Y���8�P���#��P[��2��1,�����I���"����QA`�d0�Ll��~�PT	.�>�rb}
[_8x�a5j�T;8���S����\��j�oU_a=l)6�	�/���p��a[�@o
�,��1v-_��KH&�7��/������L��'�����E]0/�N����	-jj�03�1��-wu�"�����_p�������AtY;;0�:o1���s�v��D�3=�\`�;u�n�����5� �!�f�W��/���Ec�����^����^��5�������|�36LN�LX���!i���48q�	 �.��R��uS���2N�H�,mP�%i����C1
Li|EIeP�;�|��6��������iNz����B�c����o��FW#�S/��bG*��OCoCE(��g*Y%�g�8��b���%��I�U4K�cT�������xd�cKu�q���
�+��.^c9H_
�����8�a/�GQ�?�Qzy��m�-�k��Y������������DO��Y�:d����85�	I$�����C�W�V;2�����z�L���t.���/�_��A^I�h���:����`���W��nQ��R�3��9`���6��;��9��g��F0��MX'���P��M�m��n7Q�,
�$@���2C��#8#�������U�{����'�e%�5l�F����r��bZ���w�X�����)5��k�s�����d ��-�*��"���.<a�L��J�\��SE%�������;���Oo��� �a���������(&`�Q�k�.��Ty���8=��m��[��S�&j�b����)nF��]%�q�
O����+����pl�tE��'r
�����/\2Gp��Z�m'x��|+�e�qZS�+{�����u�j"����=Z�E����q�){� ���KqF��:��l,���J ���A>7��@I'RR'�fM�1;?{x�01�|����C���5���q�f�j����O6a~&:x����Z;��9�������3��l��@��4���L�bG�JU������u��|���7e�W�[}DBr;����������4��������@����������z��2�{��m���gl�I�����`T�$�z�q�+�{���c�t3I���Z������]�fk���sH3��4\U�"����w�S�
C�&�1�:�w����zIz&W�8!:/[�+*�������F�����=�gW�3�0�[��!�A�!�-]��
��XZ��K��/[�Sk`�*�4)\�f��"�����v$�_��G���l�G"l��O1���!�C�L1j^Z�k9��Vw�%G>B���l��_>�u����U�������L�5y(�
c����<o+���1A%l|l�������/���Q�<-X����FE���d��n�x����i��?����I?�R3sZ��A>E������
��Y:�[��?{R��������Q�b�Hd��#�T�B�����q.G��Z��V��`����2��Q{��O�e�s��;����?�0|����Qq��9�hY�+�5�j���Z����t���C�D�)qw�pC�/yx���2�y@��N����I�_b7�xdd��z��Z/F1�����aL�x���N��q]\q�:�������Oq+�H��Ma�a�\7m:�x�O������0�{L��*��np]�t���*����F�
;����l����L��'[�8�lXQ��Fq�
��CP�T�q���c:�5��_{�TV�k�����nCu�d�W(��w�F��H�'�OTy����
n�k���A��a^�r��v|kaa^�sEj>iz��,^����o��$���}����� ���V��#�!�=�&�=��:�����[ih�%
P!�A�q�yz-�s��:��a�)���d^0��,�����
��M�3���.5Fss��)>�0���D;�r2�����h��f�$�e�5�p�!�����5UW&�y��@��)?}R�smu�����s�.�d4�E
�{��{������M��YH�0��'GG�����*��o�<{�FU����I_��@���-��
�`���.�R,�^~�V��T�������f]���+��2	���m��,�Tf����� aa#�������u���x�*�%u�pE)�Y����Z.�,�e�^�F��?1^��C�H������?+?���B�OaL��U�v+/���?](���I%F�r�Q�V�i{����}dX��_�G�]��9�-������[���r���a\��X������R��1K��R�����ZuO�����\�}��K���`-�G+_�X����R������J&�s���RHF��<4!kXG���*�����t��sZ�W?b����f@�67�J�e�vE|���n�UX�O�X�<���������%I��{�	���Qxz�%g�qh@i�#Cxl +slL&/�!E���7�d"�����k�F<�n�"��4�S ����cF������-���n��&]��O��);���E�����Y�h��)r���c8��E�MX�5�Xi��I|}��[�+��5���7E<���
Sao�������)�=�5K=�k$Sll?C�ok��\!O:�q��6n�k61���9-�)�M���f�.A��m3�b��B����(�h
�x��p0r���y�fq��Tu�<�T�������*m��^k_����a��#�~�g���Kv\�����Y}n����"�Z�SZqJ:�jM�d�8,%����S)��\I�:>�lc��r���Xy���������Y��kk�K�=�2�����r�:��>�y2Z����� �7�j���:\�V��_?X��*Qi$R�
�,4`�ws��Wg������p���q��@���Wp�I?�������m�^x��M������g^��3���W'q���E��q�jw��E\��ET;SzpM�MSQ:�?V[�Z�����&�>��v���r��v���a�N�,���e�i�mr�e���j�y��R{&�3i�p��k�j�I�c�.��j�>J�\��}���W�������]�=���>����l@�����d�����q�F:HQ���71SB$��6�^�����WQm�I���5��R��p/`!'j��g�b���qa����)J���~k�C�`���Nbi���L^L���P!e�=|���������K/3p��>/220-!�Y26d�����8���F�bX�����*�V��]���<h0�c�����Y��3h��h"�"����s����5�!��<���(>�'�������a���,Xg2g�#;���H��QS�bH�����:h+���2���4
�����G������������/zpp�[�����M>�Y."��J
	(Bo,�+��<C-u*�b ��^</PM���O��9�� ���d�'���[�v���O]N��;������a���I@����u�B!�����#��8 k/�]�ab$EPER�J2��.�tFJ��L��Pv�B8�_��]�ME���(j�����&x�|.}�������Z��A
�)��9��iO�n~��V�H�_�0G��l7c{� �b2�Ig��.��sH�[���0�!u�&"4���D��~�"F����^�����u�����G����������>\�� +���J����I��gJ��s����
�zrX��#��n��S���[�o
&�T4vc�$�U+7���zh3'�`�
`���o$L�r9��W�+L������<���c(�6%Hr6�
�0��q.[9�	TI��TO���
.���Lg�0�&��e|0h��e	�w��IL�f�������H����x�b�k/Mk���[���lL!j��;j�5��l�ZCQ�nrO)���Y5��q(=;���,��5��-s �i��qN�����o����5�9��y���Or�V��,.@�&Zyr�)�J2��M�t���g$f�0���Np4�M���PEAL���b�Q� ���iQ_Fhl�J�6��d���d�9���2o4��F`qG�v��������|H�?K��Q�R���U����%$�iS�/�����v�M�u��S�X��E�d��a^0|fZw4.����%*ia{��b'�HV�1Rq�al��R;}���Y���[�(��3���_z�*��������t����O7��_	�z���
�����UW��^��j{�_��I�\(��A�i�����\���2����f�4�%'4���i�K���������D�`h���z�DWA'/}t�7�sy�ZjA��Z���w�#<���n
>��u���X�q_���6~���Cv�&�W�T����"��F|z&��*��I\[R����#�[!n�X���2�������k��#)N�Y��k��,z���(��"��6D*��G)�W��&vF�������z�zZ��P[6j{��[X�y
��Y��f��Z�����T������Y�X��3~q��s�[���H_pR���x9��������K2�W�RP��H0��@R�J��|ut�_���3�zs��k�����AXGU��#E��i��b�$�@Q()�Y���H��J��Hm�~"��M���#_�8���\�J������v�SI7v?3�I)v����j�����D(��<;��V]�2Q��Q�6z3�j��J���B��k��
�+���UH���5���X4�����vcI\��i@����r`*Bu���0����;�5IXu�3w��]�u���������r��`�;i�)�R�y�^\��O{�ZY*�o���5n�'U#�M%
t����3,�9�}�\C�k����h�C
�o�!��.DC�����j(���'�B��W�����cI�a����Q=��
�@��CJ�U�J��=�#��p�]_�`�r[�0Y�L�xI��(������:�������yP;���w3y�i��@��z���m����n �{��"/��j�h��T���8����4�w�����k�����Q�������<���4f;I=V�t���TXH BF��{����x������1����n����}g�Mr�6O����C�]:���(|�Y��v���7����v�EQm����f�XU�V���N�c�'�s&)��w�3��-�����0�*����U��M�
�P�$���:�����5^����h ��t-/�9�]��@��V�y���5����6-s�nq��������&i�Ri�0�O�?�|�A�l��^n���R�b�\��T�3(A����6%���pn�|�U3���D�$��aO��n��x�
'Y��|������C�;�?����]�����9K**V��5Vox�l�������)K@����%.�N�D���$�\�F����&������u��o�&f�V7��/y3b�.�����{�-dp����d��+�(�^Y;3����3���������PQg4����uS�C{^y�]�MevRe�K����z��4����tR�bNJ'@P������X,3zG�I����X`���Y5�;,�TF�H�/�"�S�@���i���R�	�]qH&&Z��:��0-�>��PA8L�
;����X^I���E���#��&�#��C�cy���8�C�gXQ(��{�0�"@�e���q8�8�#�e@$��17��t����+/��K*�0��jc�MbZ�b���{����p#o��'���i��U�,�7�G��InoB1��(����wp�:H�6L�:��od�5�*��������������X�7��q[����,
W���bm��CX�OB1;��%GF��HR:�xb4�iH����:�*)�P�YA&t<�\V	��k�`&��:mn���p(9�����#��t�u�+F��Bm1�A
��j�2����iM&-gs#�Fg�'+��r���K9�x�v��.5�[����_m�������T�dd*���6Z���TeC���T����mD[�/�S��D-2���B�2U����d{�N�1EY�X}�,�l(�������(�hw
.�&���X����X+�9&�x�������sF��	� �����Y�"����#�N���3����%�Z^�F�%�����	��zG;��R9�:JFy��0sFXP\���9�����o�#�M�H��5��G�b�cQ5�����T�m���-�����K��D�3���ip����V�������$l+���	y{��O�4�\�RK�(�����~5iesn�l-�h����+5����7���:�������P#�������1��	����s0E8������(�Y�����Q��C�j"G�z$�eX�v�||�
�T�eN!���%��T�L�xIs���g�X#�4F�x�����J$A����n��K�TRO����(J�X���y��T�0d��|�!=�`�P���^�A(?o6�RvW�����E������.u��+0��b��M`V��@�dNTp����e�y�2e _����r�����q����.�9^X�������G\�)����y�I������A�!��O��a�����������h�M�mX�Z�`��H�:�-*�<'�XI`)7�����U�Da���tu2V'l��a�2x��X
���X���%�Z���Xm�gz��&b5��������x��X�Q���l��eT>�m��*��k�������dHc��$�b��� �>�5�����;��'M<�$�-/$j}�Q������������).(�A\J�����@��$��e+���������w"������9��Ue|���s/�Z��(��v�$�h~}���BS���(/Yj
��^x�����T?Po�~�Pp��E�K{��9��������^0���?�q��4����0��i�\b�� W�'�,����
�tE��?E���sR��::|+~y|����P��y���� ���q�5�?�Q�<���H��e�?�D�`�i�oU����� #�U�����
�ea����z��Q_��)���X'��Q�����_rE�]x�h�0��� �x�\(H����x�|H?v&~D��������`H!n��g�-�H(���'Vm�yT�	��S�(�\�( T�n���a����0����$���6� �<V&-��B���� �Q��9�BH�H�����#bW�!�3���a^��������P�'��n���(${��"�H��1����X2������O���)�\>�J��d?C�&K�s�Z�V1M�*�]�1�������k��i���Hr�����^_�IHi�������C���IE�1j�����Ag����3�,s�w2������u�{ot,	��gwi&�-;����@C
��SZ�`�;�(���J�]�C�z�SL8<4*�m���N����a�n�b=��P|��_��*x::�ob����~��
_!�
$=J�{���pd��@��?��X�.�������{�"{����I���%�bE��5���Ie����U��Opk�s�����x����c%-�W�9�`�o��l�������=�p
r�J$xRnCC���,�������a�r6��x�C�l�#��o���
�`y������-�Sj��a�I�1��
L8��Q20G+aT�*C��BX�����-�C�E�k�����uZ�D����M7u�X+���2�O�*��y���D[>��wl1b=�j.so0<�6e"�R�u�����~�U}� H&��qu8��T��S3R����C0�U�t�������d����M,)p�g��d�d=���K���"b��;W~F3�OhY=)]U�����7��MDY&����K�<��$4��Y��K�;����]���u�X���t07D9 ��uD��y%���.�"��+]F���d��Q2\��
�-������uK��`��������b�����,3����59v����q�h^�k��b���w��]�r�����3J+J���U>����63�o��r�YU�&"��^<�����J�`�����E4�GLCv�De�C-������y�-�s��XD�����
r��}�[{�����/IOA�
;�I�
5|j?o����z�P���~��j
(�f��|�������YJ9��&�s���x��?��(��yXz<���Jj����9ZW�+d��D
�n�6m�yHa��X�N��x��L�9�<f��H�
�b��/|N��pY|���
�q���z�Q���J���aIl��u�T��-�d���T�t=4�5���{�V�OS���Zu��d7S���Un��Jq%��u�]���������������g}��)�A�h�5L��me��_����6`i��x��S3y@g��pS<w�������	���ZS9�xjH����M��PN��Xi��&�����z��0-$�?80�[��A>��!��i�$��m��]qc:�����:�������&���{m�pb�S���trH~p{�#���\f�0(�.o��~?��������������yS��g��Q��dY�uFZJ�� ���
0BL!���]:),���Z}w��)�o8��?%�KZ��a�#���Ye�JT��]w���������D?����0����D<�s�,h���K�g�|n��B��/(����=�
c�>=7�bTE<"Ls���"
���R�M���jz�%����
��9&�P����dL���{k#�����_��.N��zHa����P�|r>����	�����;���+���$gO���5Q�\�S��w��X-5y��K-Q �T�F�����L�#/%���U��T���
I�z��Q5��Ye�Grqs	�l#��rxHi2Q���ZJ���G
�Ok�HE�	�e��3��ct�+tx�,=;8l��F�nt\�7:�����/�����o�U���#�9�V��@���?���&����C��[u�������A�����H[12�+DU���"�N�u�����)����_��
�j����h�z�f�U�r&4�z����J�
�x��iU�HG�����U]q(�=��
��9)h���l�Xpl�H�/�����~��W�EY8���2�	�"#%�J?����B��������,�Cu�5=m���v������ls��I��mxf�@znb�Ee.w�����I��x�!�{���aLp1�ZN�P�<�PV��z�i��B�!�?v����bUvMZb�j�
����jQ//jwwO(�:�*���)�g|�p@-x�z��#C�-N���n]����[>NV���1�J	v�:i)[���oB7�-�(w
������q�f�6���L��:K���Q^�����t\ ����S!fg��3�*�PQ����#���tocl��U�1�&z�EP�T�V,�����j�����N1{u��O78���*�2�����c�L�F~K�#.M��������6������)2�q�o0������x������
�XLYxI����~�����M�
��x�7���ULk��TQ�4qQo���Y����M�����������W(��@��8��
��tB�8!�D"��KF��U�T4�E����V�&y��!c���P�j ���'c�*)�k�:e����������Uk6;�r62��vb�+e��$��T����i�S���e�	\R�@�0)T?���AV&@�{� �#��F
�Nu�����H�(�!To���bxPV�r����w���>���.�y��? _�c��A�P�;�r��]U��Yqk�E�^_���x��c^�3�����%�� #� g����P`�E�X�����I7��	k*p���U1^�L�`GQ��ez\�[��'����CY�������������P(��M~G�g4�����J-B����`b��j��i#?E���"�+W9O!i!N����15�^S��x��5�
��y��+���93zu�*g��N2a;Z��u����Z�)���1�C�^��+�
-�������=�����&����t��K8,. 1M������ �5�]c
�@�?e�u0�_V��#L����{�Q���e2��?�I
N.a_�;Ol)�tv���E5�����G��l�����+�7�&�w���;:��o7I�����������a�����	����U���x�	C�DS��iXa��"X��)�7���Z^b��06ha�3�r�^�qQo����X��2]��7�6� �p���2C#g����n�aZI�1@��N���_����o�����	������V>�%/3
�D��Y	9�\��okdkd�6��V� ���6�u)}:��v`���r��x����J���|h���hK/�����O:,DS\U�4h��
W�:��=�K.�����hE�Kj!\�����y��
�X�2*p��s�%Z�{��;$�������V���X�Yl�Qp�����M��&��!��@��GHNL�#��9Iv���
�U����_��2+bpg�2���b�?1}�gu�<7
��`6e���Uc�o(�]j��6���]i��
A��"j�h�OE�8�?������xxJ����`�8���X}o�����H!�F�7D��=T��Ke�6�Lqf��������y�59�cL�7�H�����U�����S��7V���!�c; �:�V�=�8H\	U'�k�B�F��^�&3�:���b�[����<�X8>N�&�MR��j$c�Z��d}q�yga��v�,.L;���s+;N>>-�H����P�R�*��A�b�-M:�'x+����;�/sh�#��}�A�]�����RL�G�-����W�~\�(l��q�����d���;�@!�����k���/���8Aa)�@N{����=[2#j��([������5a�*\���56��a�"O�HZny3����Xvuh�D|���Wu8H��8�����1��S����:=���.)6k�^�v��\bG���SZ���^v�t4k.'���1x��Y�dA���b�Fz���&K�~1��
$��Fp��u�C��H�@��Cl�����H��0�x�������YSQ���e6u,)�����xM�����
�Ay
���{��U������_�W�}�*cXw��\����Az=)�kw
���F�t��	�A��Z��v��OJ,����O}�Xs6*JtK�[��,]�.��� �rC��8�4�g����}.�e�b �]B^���=+��V��cd��m���W
i��k���u�����������L�R4��W�����z���]�U��8���Q�r���8��s�#�r�!������ �G����?�K@'�i��3T��9���cQ�z�6��9������6f�^�
�K~��LGhr����
�UG,x%����m�����}��H������"u8�M���/� ���e�'��APr[T
�MG���q
R����9�������7e�W�!����=��IK��S�Q�x���z�7����A?\=���5P����qt�~O�3:{��g�^�����8^*�7���G���H��JK��I2?I+���>�?�bg��@Uv�����.������M��bG���D���7W���NE��K�"��ne���E-Ec&���i���������y���=0	I+b0{	s�����2j/��3:Za|����R��~V�����q�A�e.%�XHv��|����<�bO�5lJ�k0Q]�)`K����`��TMm�"2j/�9HI/�FA��j�
k!�[���4l3t.��&}��z|���a����<�q�=<���;(/�\��:���V�^[���x���i�2�����;�G��_��"�������9I�b���_����-�:��Ze���'d��������>�t%nw�`��$�b�j-����"����Vs"0�9h���!
����,������S��1�~���u��51��jEb�lD��J9j���M��f%v��*
Y-���B���SE3Z��\�<�)~?&��4u�����(�
�_x�s�S��m$�*�v��c����a�z]^�s���9YT�q�E�����z��*t�Q���u��|635���SX�}&+��;S\!��ik�.C5���Yd�A�v{#�x#8g���^��72�?�?������0�H;f�~����k8�5�9�8�����;5�G��m����i�tF�Ze��������wX��x&�/�K����A@)1+��-Q��"�����e������sor�3p�m������%��>l��[T��
�O��I����I�2"��u8]�;�B`g��@hN�����G�T��+�%�I�
_z/}�*r��Q��0��?Q-j��7�_$`%y��Wr�\�}e�����H]c,��(&���-;�!���ZYl�#���#�i)���J��OR%b�u��R��
�4����G�D$V�td���e
{��ki�5KKf�X]%�I���:���S2CpIA%a��E�����9J����9[����N^��(��&f�g��#�|}����(�F��=���0��������C�E���5)-��/������OK����wi������,g���S%��;��y������"�(z�p>���A��P��*��r���y]�7j�74��j��'�����>�a�^��Wv��|��C"��V�|Wq��yMp6� sWR�I�O�Y7��������!�����	2
����H����������V�7����c�N�0 ��1� �_#���@�gB�cwQ8���;><�E����<�������k���n���Sg���LD6����t��f��L���Io�����.�8-�����|T����������y����
D;}Q9�Sz�;9�L��O"�([�^��
�[��0$���V�)5yZ��Am������d	��� 
/�*���u��.�A
AFb!)�#)2J���{�,�2���@�C-Bj��~�����M�r���c������bP�u-�exH}�
���9����������i���G+'�e:����5�L��0���
E"QB����q�P$eq��[`�k�+��y��J���'��{��y�o	r����{$���LwX����@�1������s��Z$-K,�@�4�u�3I���Y��JX8�9#��[_%�HE���(]O�*��*1��"����x�������9h����������f�X�^+�4��J��p4V!te4�����|j��&�7���yu|V����/�����m4�=�S��v�9!������yb<o[la#�#vx�������S��J���������RzF�
���Bb���������?:���_P
X����Y}e��D��"�S�T��T�.�Cy�	��:M�%�Q��^�~��9���t0�/)��t��q�SI�n���i�=,a���3��d~�Y`g��g�nB
�D^�)r �����o']�dD���L��������]+�^��v/	�aq�uFd���e�����kE�l6[��J��2s�������|���.by��8R�/��;
��h�����@:�.vn�b�������V�,����HgB�f�v��t�RZ�:r�j���N���m���1[�Y���b�W�V�J������{��f[��	0�-�	�=�pBp������3�[���7)�V�)��Dl�(���+]X�$=�}�lq��xJ�%����`P�����u����3�ZH�y�bAJ�y����^R�����ZF��VcSy�2�a
(��@$�$})������~@�Q��P161����7�d!����l'�
�n��0nR~m���;���W�����,���@���wFO	��G�Gh�����F)�O-�#�����{���
(���q�q�B��[���P��e1�{\2!�-�|�9V��N[����e��vZ��A��"L�7^�\�!�����B�H��\�`:aw����h�M#�����<���%��
�;�niGF��95���Ml=bj,&G�!'3�(�%e�L8����e%��F�M�7m�?��=~:+��]E)V`���=���!0����v�C�lX�%PmS����k.;��SK�S�OO�7�g�D�S�*s�2
l�)S-[bx@v
�����>b��8�w��coJ#�4��i���������H���epq}e��QB�j�Lj-9)�w��++;�����q���91�=�k�;L�&&�ce����i���8����'�p��|�Q���
f�gy�*n��*������;�S-�]1���Z���f��K��f�F;��`����^��������>�n���?���~�3��4?*���!^^Y�i6�l��<@��0:�5�C[�
.�|���������TH��IcLR������q�U���mG���y��e��Xf��������i�<���g�1�H)��G���lB5b���`�S���Eh�z��O��Gh��c�Q���	�KR�fe ��:B�I;ct�m�M*vf��\�HP�5�^����9*���#�U��ZC"t��Ca���q7m��TF�Dl	a�������&���q8$��"i��z0f��p��d�a�P��=J� ���
Q����������'�����1u�������z���y�8�>�<�$�������I���+!�K+O�4��h����l�z�DD��1�����e���	�l����#k��_q�������S����lf~���[m��l������6�"�i���_EOfXG�hz�4%���P�����3|SU�+U%�P�j�I�0��,@U�B
����$r�=2�.�+<�8���.A"
7���"i��Z-T�����7?2��������nsfd���N�%<�vq[�hnr�������4�.�S��)��<�'���@���9����V
@����9q�����Z	 ���?�X
�b+4W�5N����N
UQ��c���,\0C�i���� �nz�$?�wc	?�����M��!��0fG$��(u�f����c�vs��
7�o��~24�6�����.**u;>����Gr��ur@���v����$�,V^�X���?_L���5)J'�_+���6[�h2��!|)MR>�����5�k	��i�K��L�B�4Y�S����f�S?�XY]g�ev��5�Qv�K��O�_���1<��}����M�VO1<>Vn*�� �����O�D����l���
���s��$�>��������Ey8X��E&�
2xl"EYbD\TnN�n�\�`�D��\��}�0Q�Lv�������i�B�H�#��O����|�D���V��<<����G�'���p�B����Z����L�c���2
��>c�	e\2�O�X:Y����J���7��=�������l���m���z������s9i��C�S���6�M���h��
3�>lr&�d3��n3?��b�N�����Yw���S�}jR>��f�C����������E��3����0��0iv�@��) {n���s�
�&G�D�S9�$��J���'�{J;����b#���v���m���M��?q�����5�K�^�M�I���\$n����YV��(7�l
�TY��A�,-��n���A{�.�:��!���8~W'k�����9O�-�a0������_���*j�s4Z�Y+��������O����N�Cc
��4��i)��zP����j��N�u��@&yU���1X�<i���
i��'V���g���c0i�7���l���LQ�����/"����=l�z���P�k��
�-���������F����@:"���G���������5]���u��=ae�-��Y�_���x#�H�+���h�,@�c����}���K(|A��s��<�R���"��@�����F�yL��������|��s�n��������$���8��R�T������a�l9w�q�%6H�zVF��B��jnl��G�[�A�3��C?v�"��UDF����=x��#�������?��D�1�O�0�����%�@o�
j{���f	�����q�l7%����p���LQ��t5"��:���o4`�xf��P=U����������@��.����\���;@YlD�x#���a�����09�JQ�`�0W��%�p��Q���a?�5(0Wy�J�x����3J��FSn
�����M1���Nk��5b��h|=9D�MkL=�v�.����>�[^�QgUX�lQ"y����g�0<[]���<�;������^�z�	���A�CI9w����'|�p�j�.��<��x`g��l/h��-p)$B�j��j(�#�@����;��i=�nm�*���������x&>x����������������i�S4�
��[I��Z)���y���~�7������7�5OAa
��)��9�T��Wr������1�V'q �?b�R����P�hO����c���0�<����)"������}$eJ�����SM��kF�^B�Q�n��k���4(5�X�L`�����Hm�P��>���P�s����H�Bk'x[p6�����x���E�����fp��j����{M���Z��k����g����E\����k��f��������q^=)�L.ctc�>�i�}o��S������;�@H}��Yz�auk;��J)�U��T{sho$�*i��D�g�\:Q���+����q���Cl-/��G��GY�~���:L`!V� 8��{	t��ov���R��Fj�[>[I;�|�K����0{�H*�pb���5��o��D�������N&���
U�\�,��U��djL��G��!��sz���_���n	���(%2K�
����N�����4�\�&c��bN(Jc����os�7J��G<%����3
�0�����B��4!u��|,�s�i�yL�0�y���)�beE���_��	��\�R����e�2��`�,���X�W}	�2%��p����������M�9��LkCbFM�5��������/eF(�R&�>~�2%�Cm�"��[�zs��sd�AQ����o�"UG��8�:@�����]E��{:��LEw*h�h�-�2��%������2:{l����Y�s4�dK�q�P���U���R�@�t�1����I5g����G�J<I��M��6�$������c�7���0�,��D%�h�##
�^�k��0�;������4���C#bx=&FNH
39:8�D\��������$I�����N�p��(<��%[ 3#�B�Z�ac9-�a��V��b�*i��1)���9�M�{�C!�~�7���Al0�,���s��@�c��i
E��P|��\zh�o<A���W@^�X8���Ir]��{����4G�oH����NiP'Ov<�8��Si?�i2���;l&����)�@%�v|����<,<�����r��V,0dL��2�hY�K�Y_l��>�+��KgRb�@�`������@(;��=�q����>�0g�d����XM���n�k,p��N��?T�����0Z��me{��w?���j�����y+]�=vO
/����C�Cc<�L�KE������#�h�BR�6Icp�p����=���$�O%��$u=(|[������()}hx���<�Z��V%zsp#��`*��2*�v@VF�u�UM ]5������������n[N��9J�d��5�l����v��m���r��2KS�(�H����'�,U��k��%�ptj��fQ�Q������Q���)A>����j�9�J�q����P�Z52��B�8-���Q5WE�����0�p�������l�.�:�4���q���r�(��#��87��9c�	�����l��m���v�a�@���;���&�sA���p�g	p�E�$����r1)�����s+47vZg9�-x���19���J��	�O������T��b_������������I{�H���bv)�s1@hvc�]8������~P?~�wu����K"O���Yg5P'� 
:�	U������e�[���%#�R[�*�@��p��'�hC;��n|0��A'���I!e������<@SES5:/[�+����{�M^�'$H�n��\��S8���?�� ��AH���b�nF�qv�.� ��U�1�yr~IO��5*Eg��2��z�'��6�k�
��	�Z���Glm��Q�c���B��9H�
��!���,I���>���IZ�<������v�>���?%�#�L�a�fc~�����:�_R;��Pc#��Ef:�^eb��[/�4���e*C=���Y<\{(�����^0������^Pvj���+���:\,XzU��P����t�UL+?88M�CgF������J�P�q�����Q
�J���^�Q��U�- �qx9�^;��SS`_>�0�c���q��B�
�U�h���`h[@��Z��}C��O|����@&��)�	��x�x���c��[\j��h��I����^��+���$�[\���d�����0����9�W��(�2A�y�pK��ROy���_S,�&������#x�����P�[� ����"�5T�__�$y������Jn0^&VC^���P���L0���OfW�I�,��{E[&�%l�}t���Wl��1�'���w���MJ��kp��?�i=����`&�A�l�x0�����~iu%�F;���I�����Eg�0�w���&�����������)�Khh�������(��B�N�j_�a�Dt���O�$=N�=��?����Z<b��e�h�����~�-wy��M:���;�p
��\�=R|���������-yS6
KG1]������P��Ar���B3����l2��A6��0�n������2Y��OI��'�&(���
�L%�� �
���&+JV7	{�T���eTY;���9���� ���`"X��zQ���r�<S�n?�v�D'�����BH��rR��
����R!�F;�=7�������^�E3�7����l�Al����~'P��ycQ�US���`�
�y8�����V,
]�����d�q��a"N����o'�%g_�&�=acMI�m/�g��^KM ������ ���0���pq�4����Or}����4���!�B��Im�4�1f����7���2:��J�t����g;���u\�U��L���J���������d�
�����_�i��>���#��i��*Q����>��u���>*Q�R%�k����T"��.��&k���&�z4�9Q,�+���y���^�����h{G����;n�w��:�U�����A~$�a����q���8��/U(e`��z�f��,�P��2�Ds���AQ�P�a���n_��t��x/�o-"��a�������0�%�[@V���n��|���}/�C�[��!�d�~Po4)S-����_5�O�4�{+������r\����TR��lp6��D������<3��)��|���E��I�}�{�%�TE1*q��y�G�1��
Vu���-c:)A�]E����2Nonw
��%����������s�n������L���ST���q"S��-�U�~&�����c�T�F�"���IhM�5�]�>>]hO��s$����R�u���z�=Q�S Y�Zx����X#�.������������a<����/�!�$s��DCn�B*���E�h�uOu2��$�
�}����Z���Ii���F��L��X�#D��r�N�g9�d�����(O���^'����!�)�Cr��s��@�G��L��z��<g�xy����>p��O�hxq�"9SF�1C����;�K���j��N��q�5Kq���)�pa���$�9J���I�T���F��R��v`F�	3��uG���LW2����".�Y �
�C��P�>@����M�U��Z�~/Xw�O�
��fTP	v�0$���u,��������O�J��4�#d��+Dd��c|����xyS��}E�(� �����};B��R7%8n�\:�8��OQ�i{�Q����?�a;���%��P��	l�
[;h�����I4V��������p0��!�.�z����gdrb���Y'"��J��j����m���e�,����*��nJd)1Q-8�.�?	9L�E����.�������C��)q���a�L?�-�Ux	����.,�K
(�^�������f���5�����)��&@����	8������*p�����>w�n�J����[�r-�E�/��u���A��W��Qm��8�s��|��3�^n��ez��6q��t�I��NY�����K���9X�PPI�eN`�Ts4�94���6�lg�;�B��k��X��L��/3<�!���7����'RV�Y����\/�
)r��n���F������|4��1]�XN����}�$��I$�3.O��^���&[T�xeS��2�J���)��{K�Q���r�$����������}������ey�{Q0V�O��{VW'1�Io?`�[��9���9T�����k�x���y�P�)�+�B�KbT����A�c������������%F:���._qp��q�d+���zcf��D�x����_���#����K6\���^+\`\���B�.T+.����J���n7P�!�:J��y��}M\cT��F���;�x����	�p�����.I���������}����-�����1�g���Mq6tT6�*�3?=j�M&����XR@CY8��&�����:�,#2���G��y�8�J�$��/d���f8
���b���bH-�F�^Qp�f^���K��*[�p'��|I@�V0a��Z�����|�H�@	Hk>:rS�H���K�a2��h�����;�Q���K���EA~����f��`�%��h`�W;��m	'����� �=�����V�#l�����% O�XrY�mq:�����	>���J������,8�,=k�r�����`mmmbVWW'����)�6��*�m�J�O<B�
�3�XEN\��125D�yF"q;�X8�
���C1��u����Z�S��Y�D��d��u������Gy�0 B����U���������n���$j�������y����@@��� �����%��Q��t�9�CK2��`�u���������?	rXED\Bg'���R��s�&��_�iU9��AC����#���4������D���r�����HN���j�������(���xB��#��1��>q�/�)���M�xx
��� O<r*jb���p��J2H
��_g���\�<������-\a�|R���%nY������u���O����	������PW*$��aC��p����D&�6*���^7�6J��G8)��$��?Nh.�,�KK��F�~�(8��E?�����-N��Y�nQ�Oy���P���?�5+nQjP���a���G����9��M��)���d�<�q�\gY��5�������/�j�+�U��Q�dXG�|����3����O���b.�Y
��<fn���! �v�������C,�<���dZ!QS�$�@��	� ;e�C�aO�6i�=��F4�.�Kp�~Z.��E�w���o��-����1�;Q��~9��xOv���	'UL����A-B}_@4<�>���k�*
8�ngC�c�����wzP2&Mn�=�Yy�X.t�mn+���ym�|��F��0���j�v����������Z��>�M�Q���AK�
j�gC;�8�-]E��4"`H���\�y|L�B�~~w5�i/�*g�����R[~�� �iGt�xx��L2�`��$(qZY��Y.��-���y[V��z��i��"Z��J�C�������a	�A�Q�?O��Gk�����g�.1y�'��t6E�w���:�Z����"�H��K�
��v��f1E5���� �����Q�EU�}wIY����>s�����[o
��:���R�������C���x~���)��o�����F���Z?h����	����8�q���v:<��8��UFF8zZ��<��a}����w���5�L�S"r����+g��'���zkk{����-!�[H��2�1��m�����g�8Z�;HhrP��[s�������~����^��C���a���}��!�0S��p�&��n]dTPoa^]z��PK���{����� ��A�Q#��A��������]@.l�U
��������N��������u
��no��M���Tf��m)����S^����G�qk�/�Xv.4v������j$���2P�Srz,	k��NK�v�TY_��O�=�L�����������E/}��C	w�������5���1��vT�������5���W%���*�����o�������GP���2}���B�	����q�h����d{3�8=]_��(����I�h����v\����6.$}�Bbw%������{�kDo�-�BT�+(��4�3hE �=>�Z���	�9�����JO����y��������.n���������������{|�3�V�GH_"�9�x�����0s����
����&[���0�g�k�2w86��)�rE��X�/QW�o��lK�l!��a�4��Ve]�c�V�M��;�Y���q}��f0l������^�}���������RMb��%�n�(��&y���	�R����
t��r��n>��no>]�so�{��#���C�L�5��7��{����lS��4��?�U�����Z���o'|}Y�n��[o`���������?������?�vT{M�������v�oj@��'>�7����?��~���Z�hG~����M����~px�e����k�P�}��m<n�0~#h�^����F��=A��{G���?� ]���I6��W��X�2���[��l4����
.9"�^R��l�����[���s��E\W��h�p.���#�V��j{�}��[j���� 6���h�Ek~m�$����Zx>D:�1~��%*n��T�kTKJ�D�
{�>�H��xKj�����U��94~,�'xs����k0{��[
���|���xw��5{����,��n@����T�-��n�[����e��(�O�[4@�3�Z��8���U&�H]�B����S�W��c{\}S�����G+��P3��?P��r'����/8���d\%�e���w�}�����oa�\.���i:��~|~v[��J��B��S���$:�M`�<�����8
�������A��U|�A���X��� �E�usRIx�Fkl��D��F{��u%��,�6������='�X���Z3�%���3@��E�tR����]�Uy��B�	���z��NmB~A��2|�_�����w�?=��p�\��aj�>����~]��Y��j���t�=Y;��5Ul@������V`���Wy��l2~����n&1<��~�y.�Q������AS��fjt	`j���c������}`~�[L����������Y{}}��I��iy���j'S�H�$����
������`����a����J�y|�k���UM�Z�n���F5�H��Be
~����������&��G�-������P�������d}|@�:eG����	1�BZ|���Q�����L����Y�������hIF-qs4p��
@TFM�nv��`t��r�j�2�9�� �Fb�tmbI�Q��������������`t�j�T$��l���	�j�-�n���~"IKQ�Pd�?j��$^�re��ge���'	��t�R�"��)�M�f��|��7����UJ������|lSZ���_�����g�z�^)���.�h�&8�ox���*X��h����������.�L��G
shU6�$�W6�^�7�Q��5�n�&	�&��lk��t�%���KRA���z+QVv�tl�&D1\� �.O����p<�/��<����k]��VL�����:9��
��,����g��'�
7-�(��N^��DvF%n#J�n�@r|~(���g��l0{�&[��O
wo-E�x�6�?��;�p@w�������R��YO.1���fL}VM:�n3R�]�am�6���&�P
������g|�T�����F"����t�jt���g�w��y�?�t?���\�k=<:y��/�Mt�a,W��/K�4&wM��j�Kr�>��K�����E��r�]^_��x��*md��Y�������O�jy?����F�G�n�f�ayFD�e�h�TF���������?
h �p*r8<�HY���xa�'	V��5T@�3
��ZG`;�������J�����V���3��/z]G���0
���[�A��58����A?J�C�v>�"z�w�>�~���_�kJ��^�)u
����yj{������*&qlcc38;m�����K-`U��1��D$3EYE�H�%�X��8`c�^��;V8��j������������)
JO��-�������"xD|����{p���Y�����O����Hy����K��[a��������y�
}�/!G#�������Z$������^���f(T�1leEbl5u^�/���7�S��F)�2��!���H�R���Uy�e`
k0��0$��{
S����W��m����s���p�h��8m}b�e���O$�����>#��^{H{rJ�=_�'��
�O��[��p�Y�����������9]5)�b>B�%1-S�$�[A�Tc:���NY�D��ds���t�����5Z�W3�3�K��ul�I�L/����60/�
��
q�L������5`��8�\�{n����\SC#�%*b%�>*�}���dz���)���j���a�/�bU�\���<�"4��2f}�J#��0�b�m+�c
(0�v
�H�����MI@�A�g02p������X���s���SN�����vb�Z���p��/��K�1����Q�9b�a��q^JP�����o���3,}�2�]XJje�o���S������W�S:�(��Km`pxR�h�.��H8����3e*T�ObZ!���.$�
�}3��������2_�7O�%$1PA�-S>�����ep��)�Y�DJ>�F��g%R���mo0��NLG�#$�q��Uv	�w�b��a��c��p�j�T��\���(���}�I��8�����U �PAG�o	8�s�����H_����v������FO���~��.�
���PNy������/�V����BH�����9�}��dR]`����\�L��y:�-~0��f�R&�O@�=�w��fP*�f��Y�������iO7�|�>7���8�XJ��C4]PA�[��uqX�|�Gn�X!
��2��Md�&���%���=������

Z��r�FCL��>����.N��\:��y��s[:��nN�'�RiX�5�#��</����q��d����Z����E9(�����c]��7�m�����F�d��P/������PL��������T0	S���&/�T{�O�����N���T^_�lO�7�X�f43~CXy?l��?����F�
r.V"���:�	,JSE�(���N�|�kL��~j]�@q:������*��]����0H�����{���{��O��-��U��z�����~�z'���u���A���x��}����S+�o����������z��*o?y�AO]k�=oi�7���o*��*p�����`��q� ���A����	�s�v���1
j�K�D�[����tFC_|���9^J���^H��K<��r��"��X�����	�c?���|�=������R����0C��g2`]��<|��a����#=aDSxz�zFNq5kwD-X� ���_����%��z:"T,�Y���i��5�_M����s����j��wX?���Q��3��-�$/?l�����iV[h�sij�.�	�h����>��6�F�/�_�{��!#���&�!O����(o��T�{i���tB���_��z�B�7J���'���HA�]���V���Fv��0��6�y�/
����pH�[��g��a��l�X������w��}�2:;���S5�+|�$.��E�C�"3y�G��=Z�����l/?![ZV�!������s:
��*Q�L��Y����%8�����=��	�Z��1#�)����sq�>�Hu�|M�"�xa�G
�p
���_M+�[y���T�R����V��������9^%hK�Je���i_2���^�0_M��&��`+���t:(!������Z����Z����/E.vr����D	)������^��++�>:<y��+���A������)��D�?����k�'�\M�EK�J����'��5�X�����]��uMZTz�V��G�����'M�����&��V��#.xS.6�I6=��I��b8+�����0Om��z�M~�������djiW���Qw$1
�R���[a����P{�xJm�rEWEA��-`��9�'F�G�^c��������&5�w��]?�_��)���D'�_?n�7���LU��#�������a,�
�7�	�
�j����by���8�����q��
������&����g9�*+$�N_Z���q�)i-��������9K�9�����Wx����vJ�G^�]�����(�_��M���D���l���]<�x����)<�j���L�������5���G/��QH�����w��%�Xq��w�s\�^<;m�U�n��?���f)�^�m��c�1�������"ii�R<<$�+���w���Z�l��	)u�Q�aQ�B �����@i�C $��Si���e#Q���,�+��2�9�x<����1����=]\I�NQ��A�s�ml<�����!%�q��,/���z*A�jw�'gq�g���~���?�S��@���D_�\�|����
Z���[���r����om���|�rV��+���r����?�8�,.�<�V���xAk�[8�A�n�y�����2R�����^��"��[�e�j�[Q�
G���
�
/�������f���a��bu!9�����6���mml�m���]L��]�Z���9��l�v������bE���#s��o�����:��[��;�ut��7�{��\��0��S�DG����]b��KUUY�I���w"�F����E�"���9���(����>�2�}��R�WBt?��<������%\
'�W{U=�o�K��O��������*���Yk��Y_/UJOZ�t+�������l�����/�
yTC!�|��hF�>lp|CM���\�rY��
I�x��`X���Y�Ai������6T%�7��]��+�9,r�	�=l��^^�,nr1�U�H�W����u��d�Ix;�y8��=|R�N���^���h���w�������* Q<���6O��;�����uLObW.G��(o��=�$<��R��:���M�%jz#m�{�'�(b�_S��Y��a;�j��Uf�=
����l5��d�[�@t�m��|:��0h
fQ|,
a��Fg�h�k������
��%��^��vb<�G2<�l�#&l�'��>eW5p'Liu�*+H���������Mu�����4�cjYh>�*��i�?�j��4�����S�w��3��)���7J6a�^$Y���#������T�7���3%�dnn]@�ln�A���lo<��\a�ib��6���w��4Lt�nGe�u�G�NZ�D��A�U���6�<��/1=S��L����!�{���%Yb>�q�����:�v�lD��G�J�!�2W�����!}M�l��\�����v�x���r�l����Dze=��eE^Q�3]8�(�6l!�����e�KU>���9^Y�`'���i2����H�y)�KS�X%������1�1��%�e35L_���$n*��}���_��6g�L�;�iv���d����G�
�*����L����X�P�TQs�[���d�����-2&xK�4[���ml^�	C>�ew����%J|�2TpA��G@1Y�\���������8�/<�n~M������r�^2�U���R��&0������Ae�V�Z��IV���P���&�N
t����4Pe�FF�I��a�������&��hvL�Ds�5�bR���E��"����g�JG�����>����J*/00.cg�i%<�R���/Y�
�����9|���?~;<������"��+���q��}mO�,<H���8�)������������������m�����^�k���O�'�����>m� V5j�S���YQ�L�J�e&V?�^E�|
�E��g�g�gc��tiK���)l�Yy��-�<vr�H[;m����$R�f���#~�2����[������	�w�]~�P�!7y���vl��u�����?����2�)Z�w� �ii��.Z�>����7�<9}zZ�x�Ik3(=s��u���R��'d.�O=g�����ltH%�#��|m|�D�|�22/�>(���c;�����i�-���������H��>�<�Ah�����@{&u��x����m���F�g�y��\���b����e������=����A[����p�:0],��r�����)���^��xlm:<�W�Qkm-�l="b��%���r�R�B�l"�)32�x����0
����<�	@��jE��c�������4�`����&�4[!D����dAGqmZ��W�Oj����W}�4c�J���n7�������U������Ou��u~����{/�*��riC�_.mZ���U�n��L�'��
�����@�%(.�q�����c8��!v(�?�u�e8���/�r�R��r"��L]������g
���U�D����*��?7��������m�n>X�o
�j���%�6�|������^�&@��\��+[�m�n�<��<O���B��:�5`���p5�Q��<����WC;'�N��a�c���(7���mG����Tw�#����C��T���*��� )A�OJ���h�
��z[X7{g�b�1t�C������`����iza�o#%�Z83v���x���B�py��
�����O~�,��5�i:H�=a�����@��2��f�d��������!�O��E�{xr���=������'9gvKy���%���>?�N�B{('W�p�a��i�:7}�G����g ���ZW���!���t�)bG$�1}Y/%�IR��F��x�!
b"��mx�!qb���mz������*��+g#����\R6��M��5����]�{&�Q)��Tgq�0�:�b�JT��(���b���z1&��������{B���
�n��]����-�g��K�^����"
������E��17?�O��W�^���;d��
^]f�o�P��B)����.�u:��#��V��N^��|�}=q�rAL��9��8��oA�L�(j�"�H��1����N(
����u�}�EA�������h,g��$k��Ju��-����T�C��P*e��V1�@�z���!��������]������{r7�f*�Wegj��N.�w�������J�o��gH�����e�k^�>�D;� {����Z������>�����KWI,�Fb)6 O*D���(����b�.�|�;�
z�s�A���b�4�|V2���UI*�omT��$U��`�D^��@8��Ts-�6����9���M8~N�0�4N�ef`6	�KEZ���6}>)>�>��$�#:%����(�r��q��B

S�	��6�nV��
1�*'�R��1:��l
#�ns��4�|w�C	z$EBm2Q)�.�Y�-q��+h��R�IKp
��WE1��_1�����-�[NA	��"^ZO�����+��W	h�������t�����W���F(�Uq�;&����mH��+xx�����^Tf?O �*��c�8���)%f�FrH�zqsH��y����[�'G������ZeL5���j�A�I=�48.CI��t�CY�^�q����[S�S�?s���������K`v��j�b,1��
F+��R�����C���^��lL�;9�O�����y����4���L
Seb����XQ��I@<�`!k���������6~�~�Rxo�
x4�v����T�d�_-N�20G����e�����.�
*��v\l��X�^K.Zz�����t��������2a�7�����uD���V^��-,�
%���V�����H��(�x�_@va)�!@e<����V�w�{5|"A>���0���b���F�P�_h��:
�!��0�CC8���<���KCyTc��L����{H��b��Z�����k:���s��Byh�>|����&�h,4�vFW3:5�I�+��r�@k8�F������1�	4�ao������B���_Y��4�Q�;-b�N���,:y!��i�����M��M���(>8�4N��w�������q��Z���������pg���g���g�6e��g�X
I���>J���7\��W4���q��AW]7������z�a�/���*��J��xt;���<������HvR�m(��7����U���7�{���,�YF���N��!�_�����N���)/�H��Y��u�����=Q��d�<�Z��w���S^�mg����"�o�w���B�����N���7D�`�F��W�rc�2��b}+��8_�������,�~6�y���Cr��!�}Q���(L�Q����=GT�
�=-Ae��x��8X'�&�(����-@op��F�iY�Q��)����LOi�a?nQ����13��YSs$Z�R�������=F�vw����D�i�2��7�"��g�UHsK�h�r���:;�'���l������0:�_xU3�iX�E/C������z����	jf��������
���	���X���R�9o�ce�k��S����K�1����a��"�VO����o�����JQ�M�4�)]�A�D�r�*x�c�����{�cAY���T
��W�jV?�c%��k��vO�F<�e%���}b��|���}��� ��������������n�]CT���yUW@�r'8�*n1��1m]bcx8��C�#�{1`�;���B%��.��b��X����PJ��F���I�Gaz������P��u>�?�v�]�K[�W�K\8����x��!>��/������z�����l���?��a�5Y5�^�G+Iw�zB�A+]�2�'�����jG8�o����Y�a�}��!�qt���/\�0�7���^N�����"%-_��g5��	�
���/�fj����Vj�S-��|�L��,��l3��m������-�(�f`^��fM�4��t-�d^p�t��;L��D-O����'.\�>����������S�7�����'�s<�S����6��X\_�Xw�����p�T~3N
���IK�07����|{r;8^(f�[��=���BpjNxr/�y��������>��s*�kOO+�>�?J������.%��y��+B'�r������r�k������5����x8O�I���RO;!�/u�n�����qK��W#�R�������S��8���t1p�{h<=��������Tzb�/����8���q��������/�&z�y)��:��������o��;��~�
�|��.X��� ov����uK
�i$���kN��-����GZgu���OJ�l�rN�V>��jZG��uFwL�1�>������H���l.R��-�Os���fi����L|�Z�;1�o�#����dc{{��;8�E�m�)��4���f�Y����5]J~Q����f��^};m����6���~{m����ES1/�s���K
�R�p#d��iaU��S��w[�_������N������]���iR�����,@z^��u[r��f���q���",G����4k���+g�rh����0\z�,���Hw�sg^b�V[,�+ga����p3a3�O�����'��t��7�������uNF���}��7���wff�-���y��-p�a��o���+�����z�,�Ir.��[����I[pT�Y{���;���9|Va�v{���T�-Uj��Jmh�}��f�v��\���g�I�7K�[���Z�E����-v�wX�����x%�R�h}�mE@�P�,:���~����n����Y,�����M��ot�E���1����[A���Wt;7@���x�M|�9?U���8�������]</t�K���d����t����
��
r�=�n+*H����� 3w{��D��[���p�A0��X
�K�{���#����$��eAx�*�.��~y?~� _���/c\����]��^���6�\����Ils���ob�o*0~�y�����������6�������e��������$�]�$���2��}��'�]"�;��������������^���
\�X`����;�]��yM��9q�]
(&�g����]9^���-pq���Q3�R,�,}���q�����f�o�B���~��������/K�_��R�w/�Q�4�w
���*�;�o��j���^��n���^���_��i&�W��-�w�_Y*�fB��(�n+�H�h��Gf����+����n�3�p^�w��^P�	�����������')���������'�����8�����N�3�F	�os��:������aV��4�VB�0+��i����d)he�~��B�F����XD�������cY*�J�;!�.�3������%�H��wYWnsRwI_@��[��:��=�g��sI����4�Ty��if��>9�LP���F&�g�4����`�)3�Z,S�NP/L��$���E�	[w��ef�M|��%f�U��~(.f��-�-S��������0�}���������idq�Y�Q,�R4�,
��������P,�o�o��X�d�T-��,u=K]���[,�-�_�r��<���}3
�������,�}_&wM`^j
��)���Y2��CG�������^-y�}/&�bF�w��6���p������e��q��8���1n����3��������^��y�}��2��=���"�[���px!'���Fq0����m���p8R5w��^��W��[�3��z%��I��b}�W\
17��\�:���f��.�W�w��r��fJ�Bt�O���gA�H33
���g�y�5�����o!+�'�t%�yk��|��'��o�Z�������7���� �q$x� ��3����*ET�0GQ%�;�D+���G��i�k^�N�mQ��DxR@C��=����B���j����_��q�73����������on6���R�����o[�;�������B��&���&3��et��S�#�h0�GC��������X[��@�/Z�� ���N�������.�6�����[���a""?��G����A���(�P/N�sP���%-�/����I���z���P���2�i���K�@��C���0!f���"h
!�gbx��uH��$�&����(�����[���MM��Fqs��F��_<*@f�,0����FUM)�����C<q��`�
�!�.�����-|��b����<���������V��r�-����<m�A�%Z�W�zz*Nq3@���jGG�G��
�)������77 
��������1 g>,,h�������������=g�^�k��(N�O��x)���G�B���Z�)�>�u�)�V�kHi���%��/eQ���%Q;���s�]���%w����s1�s�v����Df��Kw����SC:_��8���������f_0Q�e����|�������z�nw-
>�	�s1��N�n
����|������(���X,r3�.�=�y�I��&��:?��s(���M��*���c<���E��O�!����@2i8�a��L��xt��(����Yq"��
% 
Y�{���O�[!w���q�XO����8���������SM��z�:��_���~vG����^]��0���|	
����k���N3����!�?��p�\zx��,���'��������3��6��KY�To���O����3=��y�"��C"�jb����l%pD��c
�5����a�6�{&�>�m�f��6��oy���0)$��`����IS���
����jBJ�������$��#KY�P>a���T�f���o�����{hx�Y<��Y�C���(���[|�RlY�R/g���HI.����V7�7�������I��$������8m^�P:��8�8�����(@ ������������4C�h��=<9h�����ma���h$Z���g���s����g��D�,Q����E6�[���:8lX�0��u�CZ�op��m;	vi�I��>�	����)��
���z����D��lT��Ym�V�w�{5h`X��A���?a+n�: H
�bC�	^�S�b�mT����)*'��,?XM���db8ESr{g�23��ggbm�<���8j?yp�6A���A+ju�A��/�����)
={����ll�6���z��f�4��/mon>XC�r�fVWW���?�S�=yZ|"V�/��
��i����5�� 
��n�W�a/6�w,��=���u��:
���v�\	��?����_�/�y�
�*G�����x<�(�F�t��������5���^[����5�S0��Y|�k��iKKO��4�����31;�=5���=Z
��>X��������nS�����JQ�H{�A��5��V���)�������YlE����.L���5�c�/c;QD����������z�uV����F2�:YQ�\���*~XHm���|���z*B�T�_U�xV�_u����{��f�����~���z��?��=���s/��?����������&�(%�������yr�E���\=F�W&��t�P*����fe���*� ����E������.���)�L��U+
�`���<
{-�R#I��!T��($L�M1�����<��o$� ���n7�������U�*v2:?����i�����\���Q�S��K��;��LjVf`v|w��m-������8���A'l�t�^���=�K�u�`4�����/���w{3��ZC����/��V=�A�*:)x�A�)@{������
���;���vE7l�;T��E�*{��"4��;	�zgD4��[X7��{L3�����M?�ch����iz���o�N���mfY5(��Z>*T;q#:���V �Y}��Iq�.�%�q�����A}~����"�PHB�H�Ys��]�/nNZl$����h1�<TJ��J����qd���V^���_i��<Vx��;D@�����}P��v��R��t,��1�Rd�;'��l���;OcI��Q��^����(x.~�z��1���Abr���P�C���_�V��#�C��}@�7=cZW����XR��$�q [�e�3!����g�UCH_g�u*z��MO[n2�����~������GI|�E�{ )|v;� {��1�Z���������a��|�{��g���>+F�G���d4~��/��|bFIC������if�c��YN���"Ao��M�O�O�7�iv����S�S��������XN�?����`at���+�j��*
�W�Rb��t;qEq��+h�*��7,�_���~��^�*�������gC���D��Uv1��w�A6��'9�!q�W���QY��^!_��H:���a�N���T��2K�T-m%m�~����&�K��$�**uD�N�;�u�@A��,n���l�d�s��O�Fi�u����:���<Z����2�F����@�v@���-�J��z��qv�x�y�-g�����Z<�O��M#M�$��:��e�l���1�N��g�(�{���Q�k�S��Q+�0;S���E�����z�����G
a�#"]��
&t�������@H��;&|"� �d!=���q���MK��0r�I����V���=���1H-Z�y�i�@�?]��H_T�C
�%�N����O��P�F���EFyQZ�c|�!��n��o������M������fQ4����R��0#��Z�6��$1�YQ�KI9}����N�Q�'i��NN.�:�I���������_K�b����t�SS�$���*g����S����M)
og��������:���Ce���x����M(�W�rc�Dl�	1���������j����yy|��>��p�����
1��O*�QL����-����5���E�6�V��Qkf��K��5����c���z��,�F�d�Nr��> �A�y��WbC��6�� -�3+G�>=B��q��g�!��b�|<F0��}��B8n���-.��-�)%)�R*?RL�Y�D�z��Nvu�w�������0�L:��O����Q���gz�#��H����	�������v=�`�A9y>|x����������k���=�Vs��������������'P��r&4pr���"Z����i�Cs�x��D��}�A���C�W^�T_���;8���*�J�Y9u��'���e%#v�y��+��:>h�}7�a��O|���Y��v*����tL����b�`_�eb�D�&��S�CN_Os�0}�3��\n�~�nU~�fg��~��0n��	�0r���:�����2�g5N8>k�7M!�<�����[p���p��(3��Yt����]�q�3ny�-������q����q�S�m�q��.����<��g�����8�P��M��
��M���.���<������~�[���8j|7k��ny�-���<�����n�2��E�������[�q�3ny�-�����������f�7q3w�<��g���[�q�3�;8�H����8Kr������]�q�3ny�-�����q�t�u�,�:n�n�g���[�q�3ny�}Gg�m]�e�6�������1�<������[s��1w[wrY���/�f�wy�-���1�<����wp���v{�r��r��r3w�<��g���[�q�3�;:�n�V.��Y�����.����<��g�����������:m}+7{��cny�-���1�<���1gp�]M%D����1S���.��KQ��bskz��j>�"�[9��-�j��Z��J/�:��*��/��Q"wC�f�[���RY�C|�E_"�����2X0M�������z��R���f�ku^�\7"�78aG�
�7�I�9�[��?���u�
D���;b���l�|�<���F'�o��h:
1�b~���Y	��E�P�zq��{�:5���H��;4����v��u�,z�3�."l8��*�gbx��uH�{cn�f���1�n�+��3h��n.�g��dZK��
�O�F����C�4�����-|��0{/����p�1k��Yw1G���I&n���,��P����e��T�P�8mo'��PyJ�>B�V��70�����Q���Q��5�)b����a�\��4�k��-B��`R�~|)3��9�n����q<I�������9��?G�l�U<-x�0�_�2�������o�����U����1��;�p�7�v���C(�;�(����0��J�
���S���@;�1�7�q��<
�B@lBS"d�aE�M�SD�+?Z������d�������-���|�����O���R����j����z���8�iEl�9]��QD���:���;����CxuY*[s�C��%4��eZ��&+n�I��l����v��p�fc�a���'�0�/
Vz��f��4�c^I�J.�" ����!
�Bh+t�������i����qpas��4�m���Y� b��O�.�;q39�bj��Hk9����>I���DY��>.���T����{(5���~��4��<
q|�_�[���.�`��(lu�Y����s�������jP7=��������'	�����f���t�]���g��f�/�{xr��?*$�����W�
x���r����(M��mi������*�}X��e�S}F_0�j���~�*�nu���}�2��?6y��
#58Yugo Nagata
nagata@sraoss.co.jp
In reply to: Tatsuo Ishii (#55)
Re: Implementing Incremental View Maintenance

On Mon, 02 Dec 2019 10:36:36 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

One thing pending in this development line is how to catalogue aggregate
functions that can be used in incrementally-maintainable views.
I saw a brief mention somewhere that the devels knew it needed to be
done, but I don't see in the thread that they got around to doing it.
Did you guys have any thoughts on how it can be represented in catalogs?
It seems sine-qua-non ...

Yes, this is a pending issue. Currently, supported aggregate functions are
identified their name, that is, we support aggregate functions named "count",
"sum", "avg", "min", or "max". As mentioned before, this is not robust
because there might be user-defined aggregates with these names although all
built-in aggregates can be used in IVM.

In our implementation, the new aggregate values are calculated using "+" and
"-" operations for sum and count, "/" for agv, and ">=" / "<=" for min/max.
Therefore, if there is a user-defined aggregate on a user-defined type which
doesn't support these operators, errors will raise. Obviously, this is a
problem. Even if these operators are defined, the semantics of user-defined
aggregate functions might not match with the way of maintaining views, and
resultant might be incorrect.

I think there are at least three options to prevent these problems.

In the first option, we support only built-in aggregates which we know able
to handle correctly. Supported aggregates can be identified using their OIDs.
User-defined aggregates are not supported. I think this is the simplest and
easiest way.

I think this is enough for the first cut of IVM. So +1.

If there is no objection, I will add the check of aggregate functions
by this way. Thanks.

Regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

#59Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Yugo Nagata (#58)
Re: Implementing Incremental View Maintenance

On 2019-Dec-02, Yugo Nagata wrote:

On Mon, 02 Dec 2019 10:36:36 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

One thing pending in this development line is how to catalogue aggregate
functions that can be used in incrementally-maintainable views.
I saw a brief mention somewhere that the devels knew it needed to be
done, but I don't see in the thread that they got around to doing it.
Did you guys have any thoughts on how it can be represented in catalogs?
It seems sine-qua-non ...

In the first option, we support only built-in aggregates which we know able
to handle correctly. Supported aggregates can be identified using their OIDs.
User-defined aggregates are not supported. I think this is the simplest and
easiest way.

I think this is enough for the first cut of IVM. So +1.

If there is no objection, I will add the check of aggregate functions
by this way. Thanks.

The way I imagine things is that there's (one or more) new column in
pg_aggregate that links to the operator(s) (or function(s)?) that
support incremental update of the MV for that aggregate function. Is
that what you're proposing?

All that query-construction business in apply_delta() looks quite
suspicious.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#60Yugo Nagata
nagata@sraoss.co.jp
In reply to: Alvaro Herrera (#59)
Re: Implementing Incremental View Maintenance

On Mon, 2 Dec 2019 13:48:40 -0300
Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

On 2019-Dec-02, Yugo Nagata wrote:

On Mon, 02 Dec 2019 10:36:36 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

One thing pending in this development line is how to catalogue aggregate
functions that can be used in incrementally-maintainable views.
I saw a brief mention somewhere that the devels knew it needed to be
done, but I don't see in the thread that they got around to doing it.
Did you guys have any thoughts on how it can be represented in catalogs?
It seems sine-qua-non ...

In the first option, we support only built-in aggregates which we know able
to handle correctly. Supported aggregates can be identified using their OIDs.
User-defined aggregates are not supported. I think this is the simplest and
easiest way.

I think this is enough for the first cut of IVM. So +1.

If there is no objection, I will add the check of aggregate functions
by this way. Thanks.

The way I imagine things is that there's (one or more) new column in
pg_aggregate that links to the operator(s) (or function(s)?) that
support incremental update of the MV for that aggregate function. Is
that what you're proposing?

The way I am proposing above is using OID to check if a aggregate can be
used in IVM. This allows only a part of built-in aggreagete functions.

This way you mentioned was proposed as one of options as following.

On Fri, 29 Nov 2019 17:33:28 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

Third, we can add a new attribute to pg_aggregate which shows if each
aggregate can be used in IVM. We don't need to use names or OIDs list of
supported aggregates although we need modification of the system catalogue.

Regarding pg_aggregate, now we have aggcombinefn attribute for supporting
partial aggregation. Maybe we could use combine functions to calculate new
aggregate values in IVM when tuples are inserted into a table. However, in
the context of IVM, we also need other function used when tuples are deleted
from a table, so we can not use partial aggregation for IVM in the current
implementation. This might be another option to implement "inverse combine
function"(?) for IVM, but I am not sure it worth.

If we add "inverse combine function" in pg_aggregate that takes two results
of aggregating over tuples in a view and tuples in a delta, and produces a
result of aggregating over tuples in the view after tuples in the delta are
deleted from this, it would allow to calculate new aggregate values in IVM
using aggcombinefn together when the aggregate function provides both
functions.

Another idea is to use support functions for moving-aggregate mode which are
already provided in pg_aggregate. However, in this case, we have to apply
tuples in the delta to the view one by one instead of applying after
aggregating tuples in the delta.

In both case, we can not use these support functions in SQL via SPI because
the type of some aggregates is internal. We have to alter the current
apply_delta implementation if we adopt a way using these support functions.
Instead, we also can add support functions for IVM independent to partial
aggregate or moving-aggregate. Maybe this is also one of options.

Regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

#61nuko yokohama
nuko.yokohama@gmail.com
In reply to: Yugo Nagata (#1)
Re: Implementing Incremental View Maintenance

Hi.

I found the problem after running "ALTER MATERIALIZED VIEW ... RENAME TO".
If a view created with "CREATE INCREMENT MATERIALIZED VIEW" is renamed,
subsequent INSERT operations to the base table will fail.

Error message.
```
ERROR: could not open relation with OID 0
```

Execution log.
```
[ec2-user@ip-10-0-1-10 ivm]$ psql -U postgres test -e -f
~/test/ivm/alter_rename_bug.sql
DROP TABLE IF EXISTS table_x CASCADE;
psql:/home/ec2-user/test/ivm/alter_rename_bug.sql:1: NOTICE: drop cascades
to materialized view group_imv
DROP TABLE
CREATE TABLE table_x AS
SELECT generate_series(1, 10000) AS id,
ROUND(random()::numeric * 100, 2) AS data,
CASE (random() * 5)::integer
WHEN 4 THEN 'group-a'
WHEN 3 THEN 'group-b'
ELSE 'group-c'
END AS part_key
;
SELECT 10000
Table "public.table_x"
Column | Type | Collation | Nullable | Default
----------+---------+-----------+----------+---------
id | integer | | |
data | numeric | | |
part_key | text | | |

DROP MATERIALIZED VIEW IF EXISTS group_imv;
psql:/home/ec2-user/test/ivm/alter_rename_bug.sql:15: NOTICE: materialized
view "group_imv" does not exist, skipping
DROP MATERIALIZED VIEW
CREATE INCREMENTAL MATERIALIZED VIEW group_imv AS
SELECT part_key, COUNT(*), MAX(data), MIN(data), SUM(data), AVG(data)
FROM table_x
GROUP BY part_key;
SELECT 3
List of relations
Schema | Name | Type | Owner
--------+-----------+-------------------+----------
public | group_imv | materialized view | postgres
public | table_x | table | postgres
(2 rows)

Materialized view "public.group_imv"
Column | Type | Collation | Nullable | Default
-------------------+---------+-----------+----------+---------
part_key | text | | |
count | bigint | | |
max | numeric | | |
min | numeric | | |
sum | numeric | | |
avg | numeric | | |
__ivm_count_max__ | bigint | | |
__ivm_count_min__ | bigint | | |
__ivm_count_sum__ | bigint | | |
__ivm_count_avg__ | bigint | | |
__ivm_sum_avg__ | numeric | | |
__ivm_count__ | bigint | | |

SELECT * FROM group_imv ORDER BY part_key;
part_key | count | max | min | sum | avg
----------+-------+-------+------+-----------+---------------------
group-a | 1966 | 99.85 | 0.05 | 98634.93 | 50.1703611393692777
group-b | 2021 | 99.99 | 0.17 | 102614.02 | 50.7738842157347848
group-c | 6013 | 99.99 | 0.02 | 300968.43 | 50.0529569266589057
(3 rows)

ALTER MATERIALIZED VIEW group_imv RENAME TO group_imv2;
ALTER MATERIALIZED VIEW
List of relations
Schema | Name | Type | Owner
--------+------------+-------------------+----------
public | group_imv2 | materialized view | postgres
public | table_x | table | postgres
(2 rows)

Materialized view "public.group_imv2"
Column | Type | Collation | Nullable | Default
-------------------+---------+-----------+----------+---------
part_key | text | | |
count | bigint | | |
max | numeric | | |
min | numeric | | |
sum | numeric | | |
avg | numeric | | |
__ivm_count_max__ | bigint | | |
__ivm_count_min__ | bigint | | |
__ivm_count_sum__ | bigint | | |
__ivm_count_avg__ | bigint | | |
__ivm_sum_avg__ | numeric | | |
__ivm_count__ | bigint | | |

SET client_min_messages = debug5;
psql:/home/ec2-user/test/ivm/alter_rename_bug.sql:30: DEBUG:
CommitTransaction(1) name: unnamed; blockState: STARTED; state:
INPROGRESS, xid/subid/cid: 0/1/0
SET
INSERT INTO table_x VALUES (10000001, ROUND(random()::numeric * 100, 2),
'gruop_d');
psql:/home/ec2-user/test/ivm/alter_rename_bug.sql:33: DEBUG:
StartTransaction(1) name: unnamed; blockState: DEFAULT; state: INPROGRESS,
xid/subid/cid: 0/1/0
psql:/home/ec2-user/test/ivm/alter_rename_bug.sql:33: DEBUG: relation
"public.group_imv" does not exist
psql:/home/ec2-user/test/ivm/alter_rename_bug.sql:33: DEBUG: relation
"public.group_imv" does not exist
psql:/home/ec2-user/test/ivm/alter_rename_bug.sql:33: ERROR: could not
open relation with OID 0
RESET client_min_messages;
psql:/home/ec2-user/test/ivm/alter_rename_bug.sql:34: DEBUG:
StartTransaction(1) name: unnamed; blockState: DEFAULT; state: INPROGRESS,
xid/subid/cid: 0/1/0
RESET
SELECT * FROM group_imv2 ORDER BY part_key;
part_key | count | max | min | sum | avg
----------+-------+-------+------+-----------+---------------------
group-a | 1966 | 99.85 | 0.05 | 98634.93 | 50.1703611393692777
group-b | 2021 | 99.99 | 0.17 | 102614.02 | 50.7738842157347848
group-c | 6013 | 99.99 | 0.02 | 300968.43 | 50.0529569266589057
(3 rows)

ALTER MATERIALIZED VIEW group_imv2 RENAME TO group_imv;
ALTER MATERIALIZED VIEW
INSERT INTO table_x VALUES (10000001, ROUND(random()::numeric * 100, 2),
'gruop_d');
INSERT 0 1
SELECT * FROM group_imv ORDER BY part_key;
part_key | count | max | min | sum | avg
----------+-------+-------+-------+-----------+---------------------
group-a | 1966 | 99.85 | 0.05 | 98634.93 | 50.1703611393692777
group-b | 2021 | 99.99 | 0.17 | 102614.02 | 50.7738842157347848
group-c | 6013 | 99.99 | 0.02 | 300968.43 | 50.0529569266589057
gruop_d | 1 | 81.43 | 81.43 | 81.43 | 81.4300000000000000
(4 rows)

[ec2-user@ip-10-0-1-10 ivm]$
```

This may be because IVM internal information is not modified when the view
name is renamed.

2018年12月27日(木) 21:57 Yugo Nagata <nagata@sraoss.co.jp>:

Show quoted text

Hi,

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.
IVM is a technique to maintain materialized views which computes and
applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018
[1].
Our implementation uses row OIDs to compute deltas for materialized
views.
The basic idea is that if we have information about which rows in base
tables
are contributing to generate a certain row in a matview then we can
identify
the affected rows when a base table is updated. This is based on an idea of
Dr. Masunaga [2] who is a member of our group and inspired from ID-based
approach[3].

In our implementation, the mapping of the row OIDs of the materialized view
and the base tables are stored in "OID map". When a base relation is
modified,
AFTER trigger is executed and the delta is recorded in delta tables using
the transition table feature. The accual udpate of the matview is triggerd
by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS will
be removed since PG12, so OIDs are no longer available. Besides this, it
would
be hard to implement this since it needs many changes of executor nodes to
collect base tables's OIDs during execuing a query. Also, the cost of
maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without relying on
OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4] on incremental matview
maintenance. In this discussion, Kevin proposed to use counting algorithm
[5]
to handle projection views (using DISTNICT) properly. This algorithm need
an
additional system column, count_t, in materialized views and delta tables
of
base tables.

However, the discussion about IVM is now stoped, so we would like to
restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at
least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables,
but using
logical decoding might be better from the point of performance of base
table
modification.

If we can represent a change of UPDATE on a base table as query-like
rather than
OLD and NEW, it may be possible to update the materialized view directly
instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes on
base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on "change
propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe this as
SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify
which row
in the matview is corresponding to a row in the delta. A naive method is
matching
by using all columns in a tuple, but clearly this is unefficient. If
thematerialized
view has unique index, we can use this. Maybe, we have to force
materialized views
to have all primary key colums in their base tables. In our PoC
implementation, we
used OID to identify rows, but this will be no longer available as said
above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate (eager)
or deferred.

In eager maintenance, the materialized view is updated in the same
transaction
where the base table is updated. In deferred maintenance, this is done
after the
transaction is commited, for example, when view is accessed, as a response
to user
request, etc.

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command
to perform IVM.
I am not sure which is better as a start point, but I begin to think that
the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

In the eager maintenance approache, we have to consider a race condition
where two
different transactions change base tables simultaneously as discussed in
[4].

[1]
https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/
[2]
https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1
(Japanese only)
[3] https://dl.acm.org/citation.cfm?id=2750546
[4]
/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com
[5] https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

#62nuko yokohama
nuko.yokohama@gmail.com
In reply to: Yugo Nagata (#60)
Re: Implementing Incremental View Maintenance

2019年12月3日(火) 14:42 Yugo Nagata <nagata@sraoss.co.jp>:

Show quoted text

On Mon, 2 Dec 2019 13:48:40 -0300
Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

On 2019-Dec-02, Yugo Nagata wrote:

On Mon, 02 Dec 2019 10:36:36 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

One thing pending in this development line is how to catalogue

aggregate

functions that can be used in incrementally-maintainable views.
I saw a brief mention somewhere that the devels knew it needed to

be

done, but I don't see in the thread that they got around to doing

it.

Did you guys have any thoughts on how it can be represented in

catalogs?

It seems sine-qua-non ...

In the first option, we support only built-in aggregates which we

know able

to handle correctly. Supported aggregates can be identified using

their OIDs.

User-defined aggregates are not supported. I think this is the

simplest and

easiest way.

I think this is enough for the first cut of IVM. So +1.

If there is no objection, I will add the check of aggregate functions
by this way. Thanks.

The way I imagine things is that there's (one or more) new column in
pg_aggregate that links to the operator(s) (or function(s)?) that
support incremental update of the MV for that aggregate function. Is
that what you're proposing?

The way I am proposing above is using OID to check if a aggregate can be
used in IVM. This allows only a part of built-in aggreagete functions.

This way you mentioned was proposed as one of options as following.

On Fri, 29 Nov 2019 17:33:28 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

Third, we can add a new attribute to pg_aggregate which shows if each
aggregate can be used in IVM. We don't need to use names or OIDs list of
supported aggregates although we need modification of the system

catalogue.

Regarding pg_aggregate, now we have aggcombinefn attribute for supporting
partial aggregation. Maybe we could use combine functions to calculate

new

aggregate values in IVM when tuples are inserted into a table. However,

in

the context of IVM, we also need other function used when tuples are

deleted

from a table, so we can not use partial aggregation for IVM in the

current

implementation. This might be another option to implement "inverse

combine

function"(?) for IVM, but I am not sure it worth.

If we add "inverse combine function" in pg_aggregate that takes two results
of aggregating over tuples in a view and tuples in a delta, and produces a
result of aggregating over tuples in the view after tuples in the delta are
deleted from this, it would allow to calculate new aggregate values in IVM
using aggcombinefn together when the aggregate function provides both
functions.

Another idea is to use support functions for moving-aggregate mode which
are
already provided in pg_aggregate. However, in this case, we have to apply
tuples in the delta to the view one by one instead of applying after
aggregating tuples in the delta.

In both case, we can not use these support functions in SQL via SPI because
the type of some aggregates is internal. We have to alter the current
apply_delta implementation if we adopt a way using these support functions.
Instead, we also can add support functions for IVM independent to partial
aggregate or moving-aggregate. Maybe this is also one of options.

Regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

#63Yugo Nagata
nagata@sraoss.co.jp
In reply to: nuko yokohama (#61)
Re: Implementing Incremental View Maintenance

On Wed, 4 Dec 2019 21:18:02 +0900
nuko yokohama <nuko.yokohama@gmail.com> wrote:

Hi.

I found the problem after running "ALTER MATERIALIZED VIEW ... RENAME TO".
If a view created with "CREATE INCREMENT MATERIALIZED VIEW" is renamed,
subsequent INSERT operations to the base table will fail.

Error message.
```
ERROR: could not open relation with OID 0

Thank you for your pointing out this issue! This error occurs
because the view's OID is retrieved using the view name.
Considering that the name can be changed, this is obviously
wrong. We'll fix it.

Regards,
Yugo Nagata

```

Execution log.
```
[ec2-user@ip-10-0-1-10 ivm]$ psql -U postgres test -e -f
~/test/ivm/alter_rename_bug.sql
DROP TABLE IF EXISTS table_x CASCADE;
psql:/home/ec2-user/test/ivm/alter_rename_bug.sql:1: NOTICE: drop cascades
to materialized view group_imv
DROP TABLE
CREATE TABLE table_x AS
SELECT generate_series(1, 10000) AS id,
ROUND(random()::numeric * 100, 2) AS data,
CASE (random() * 5)::integer
WHEN 4 THEN 'group-a'
WHEN 3 THEN 'group-b'
ELSE 'group-c'
END AS part_key
;
SELECT 10000
Table "public.table_x"
Column | Type | Collation | Nullable | Default
----------+---------+-----------+----------+---------
id | integer | | |
data | numeric | | |
part_key | text | | |

DROP MATERIALIZED VIEW IF EXISTS group_imv;
psql:/home/ec2-user/test/ivm/alter_rename_bug.sql:15: NOTICE: materialized
view "group_imv" does not exist, skipping
DROP MATERIALIZED VIEW
CREATE INCREMENTAL MATERIALIZED VIEW group_imv AS
SELECT part_key, COUNT(*), MAX(data), MIN(data), SUM(data), AVG(data)
FROM table_x
GROUP BY part_key;
SELECT 3
List of relations
Schema | Name | Type | Owner
--------+-----------+-------------------+----------
public | group_imv | materialized view | postgres
public | table_x | table | postgres
(2 rows)

Materialized view "public.group_imv"
Column | Type | Collation | Nullable | Default
-------------------+---------+-----------+----------+---------
part_key | text | | |
count | bigint | | |
max | numeric | | |
min | numeric | | |
sum | numeric | | |
avg | numeric | | |
__ivm_count_max__ | bigint | | |
__ivm_count_min__ | bigint | | |
__ivm_count_sum__ | bigint | | |
__ivm_count_avg__ | bigint | | |
__ivm_sum_avg__ | numeric | | |
__ivm_count__ | bigint | | |

SELECT * FROM group_imv ORDER BY part_key;
part_key | count | max | min | sum | avg
----------+-------+-------+------+-----------+---------------------
group-a | 1966 | 99.85 | 0.05 | 98634.93 | 50.1703611393692777
group-b | 2021 | 99.99 | 0.17 | 102614.02 | 50.7738842157347848
group-c | 6013 | 99.99 | 0.02 | 300968.43 | 50.0529569266589057
(3 rows)

ALTER MATERIALIZED VIEW group_imv RENAME TO group_imv2;
ALTER MATERIALIZED VIEW
List of relations
Schema | Name | Type | Owner
--------+------------+-------------------+----------
public | group_imv2 | materialized view | postgres
public | table_x | table | postgres
(2 rows)

Materialized view "public.group_imv2"
Column | Type | Collation | Nullable | Default
-------------------+---------+-----------+----------+---------
part_key | text | | |
count | bigint | | |
max | numeric | | |
min | numeric | | |
sum | numeric | | |
avg | numeric | | |
__ivm_count_max__ | bigint | | |
__ivm_count_min__ | bigint | | |
__ivm_count_sum__ | bigint | | |
__ivm_count_avg__ | bigint | | |
__ivm_sum_avg__ | numeric | | |
__ivm_count__ | bigint | | |

SET client_min_messages = debug5;
psql:/home/ec2-user/test/ivm/alter_rename_bug.sql:30: DEBUG:
CommitTransaction(1) name: unnamed; blockState: STARTED; state:
INPROGRESS, xid/subid/cid: 0/1/0
SET
INSERT INTO table_x VALUES (10000001, ROUND(random()::numeric * 100, 2),
'gruop_d');
psql:/home/ec2-user/test/ivm/alter_rename_bug.sql:33: DEBUG:
StartTransaction(1) name: unnamed; blockState: DEFAULT; state: INPROGRESS,
xid/subid/cid: 0/1/0
psql:/home/ec2-user/test/ivm/alter_rename_bug.sql:33: DEBUG: relation
"public.group_imv" does not exist
psql:/home/ec2-user/test/ivm/alter_rename_bug.sql:33: DEBUG: relation
"public.group_imv" does not exist
psql:/home/ec2-user/test/ivm/alter_rename_bug.sql:33: ERROR: could not
open relation with OID 0
RESET client_min_messages;
psql:/home/ec2-user/test/ivm/alter_rename_bug.sql:34: DEBUG:
StartTransaction(1) name: unnamed; blockState: DEFAULT; state: INPROGRESS,
xid/subid/cid: 0/1/0
RESET
SELECT * FROM group_imv2 ORDER BY part_key;
part_key | count | max | min | sum | avg
----------+-------+-------+------+-----------+---------------------
group-a | 1966 | 99.85 | 0.05 | 98634.93 | 50.1703611393692777
group-b | 2021 | 99.99 | 0.17 | 102614.02 | 50.7738842157347848
group-c | 6013 | 99.99 | 0.02 | 300968.43 | 50.0529569266589057
(3 rows)

ALTER MATERIALIZED VIEW group_imv2 RENAME TO group_imv;
ALTER MATERIALIZED VIEW
INSERT INTO table_x VALUES (10000001, ROUND(random()::numeric * 100, 2),
'gruop_d');
INSERT 0 1
SELECT * FROM group_imv ORDER BY part_key;
part_key | count | max | min | sum | avg
----------+-------+-------+-------+-----------+---------------------
group-a | 1966 | 99.85 | 0.05 | 98634.93 | 50.1703611393692777
group-b | 2021 | 99.99 | 0.17 | 102614.02 | 50.7738842157347848
group-c | 6013 | 99.99 | 0.02 | 300968.43 | 50.0529569266589057
gruop_d | 1 | 81.43 | 81.43 | 81.43 | 81.4300000000000000
(4 rows)

[ec2-user@ip-10-0-1-10 ivm]$
```

This may be because IVM internal information is not modified when the view
name is renamed.

2018年12月27日(木) 21:57 Yugo Nagata <nagata@sraoss.co.jp>:

Hi,

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.
IVM is a technique to maintain materialized views which computes and
applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018
[1].
Our implementation uses row OIDs to compute deltas for materialized
views.
The basic idea is that if we have information about which rows in base
tables
are contributing to generate a certain row in a matview then we can
identify
the affected rows when a base table is updated. This is based on an idea of
Dr. Masunaga [2] who is a member of our group and inspired from ID-based
approach[3].

In our implementation, the mapping of the row OIDs of the materialized view
and the base tables are stored in "OID map". When a base relation is
modified,
AFTER trigger is executed and the delta is recorded in delta tables using
the transition table feature. The accual udpate of the matview is triggerd
by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS will
be removed since PG12, so OIDs are no longer available. Besides this, it
would
be hard to implement this since it needs many changes of executor nodes to
collect base tables's OIDs during execuing a query. Also, the cost of
maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without relying on
OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4] on incremental matview
maintenance. In this discussion, Kevin proposed to use counting algorithm
[5]
to handle projection views (using DISTNICT) properly. This algorithm need
an
additional system column, count_t, in materialized views and delta tables
of
base tables.

However, the discussion about IVM is now stoped, so we would like to
restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at
least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables,
but using
logical decoding might be better from the point of performance of base
table
modification.

If we can represent a change of UPDATE on a base table as query-like
rather than
OLD and NEW, it may be possible to update the materialized view directly
instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes on
base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on "change
propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe this as
SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify
which row
in the matview is corresponding to a row in the delta. A naive method is
matching
by using all columns in a tuple, but clearly this is unefficient. If
thematerialized
view has unique index, we can use this. Maybe, we have to force
materialized views
to have all primary key colums in their base tables. In our PoC
implementation, we
used OID to identify rows, but this will be no longer available as said
above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate (eager)
or deferred.

In eager maintenance, the materialized view is updated in the same
transaction
where the base table is updated. In deferred maintenance, this is done
after the
transaction is commited, for example, when view is accessed, as a response
to user
request, etc.

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command
to perform IVM.
I am not sure which is better as a start point, but I begin to think that
the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

In the eager maintenance approache, we have to consider a race condition
where two
different transactions change base tables simultaneously as discussed in
[4].

[1]
https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/
[2]
https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1
(Japanese only)
[3] https://dl.acm.org/citation.cfm?id=2750546
[4]
/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com
[5] https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo Nagata <nagata@sraoss.co.jp>

#64Yugo Nagata
nagata@sraoss.co.jp
In reply to: Takuma Hoshiai (#57)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

Attached is the latest patch (v10) to add support for Incremental
Materialized View Maintenance (IVM).

IVM is a way to make materialized views up-to-date in which only
incremental changes are computed and applied on views rather than
recomputing the contents from scratch as REFRESH MATERIALIZED VIEW
does. IVM can update materialized views more efficiently
than recomputation when only small part of the view need updates.

There are two approaches with regard to timing of view maintenance:
immediate and deferred. In immediate maintenance, views are updated in
the same transaction where its base table is modified. In deferred
maintenance, views are updated after the transaction is committed,
for example, when the view is accessed, as a response to user command
like REFRESH, or periodically in background, and so on.

This patch implements a kind of immediate maintenance, in which
materialized views are updated immediately in AFTER triggers when a
base table is modified.

This supports views using:
- inner and outer joins including self-join
- some built-in aggregate functions (count, sum, agv, min, max)
- a part of subqueries
-- simple subqueries in FROM clause
-- EXISTS subqueries in WHERE clause
- DISTINCT and views with tuple duplicates

===
Here are major changes we made after the previous submitted patch:

* Aggregate functions are checked if they can be used in IVM
using their OID. Per comments from Alvaro Herrera.

For this purpose, Gen_fmgrtab.pl was modified so that OIDs of
aggregate functions are output to fmgroids.h.

* Some bug fixes including:

- Mistake of tab-completion of psql pointed out by nuko-san
- A bug relating rename of matview pointed out by nuko-san
- spelling errors
- etc.

* Add documentations for IVM

* Patch is splited into eleven parts to make review easier
as suggested by Amit Langote:

- 0001: Add a new syntax:
CREATE INCREMENTAL MATERIALIZED VIEW
- 0002: Add a new column relisivm to pg_class
- 0003: Change trigger.c to allow to prolong life span of tupestores
containing Transition Tables generated via AFTER trigger
- 0004: Add the basic IVM future using counting algorithm:
This supports inner joins, DISTINCT, and tuple duplicates.
- 0005: Change GEN_fmgrtab.pl to output aggregate function's OIDs
- 0006: Add aggregates support for IVM
- 0007: Add subqueries support for IVM
- 0008: Add outer joins support for IVM
- 0009: Add IVM support to psql command
- 0010: Add regression tests for IVM
- 0011: Add documentations for IVM

===
Todo:

Currently, REFRESH and pg_dump/pg_restore is not supported, but
we are working on them.

Also, TRUNCATE is not supported. When TRUNCATE command is executed
on a base table, nothing occurs on materialized views. We are
now considering another better options, like:

- Raise an error or warning when a base table is TRUNCATEed.
- Make the view non-scannable (like WITH NO DATA)
- Update the view in some ways. It would be easy for inner joins
or aggregate views, but there is some difficult with outer joins.

Regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v10.tar.gzapplication/gzip; name=IVM_patches_v10.tar.gzDownload
�@K�]�<ks�8��*�
�[7K��,Y��8�l[It���Jf���X	J�)����w��~�
�)J��e���\3�4��F�d�����������v���?����n����V?����~�sxt��=�t{���3v�GS�����{����V������?���A�����F��R�o}��C�����V`�V�x��e�Y�����m�R9
����Z���q��(������3��3��\���p��}��z��I�����i����]�;n����~�����0`�<���9���H��hN�o�#ve���b���$��$i�a�s��~n������&�c���_���9�vO{l������f���NO��&gX�p���Oj�4�d��LSKFj��Z��e���E�~>�����&�
�f�����
%��= K�s�dQ�)w���9������:L�N�-2?�p��>��ei���$Ss�&I�0�������w�0��4w�v�g�����=�q�{�s�D���c�9g���q�T@S�qY� I�hI'[ G��<����f�rO�m+`S�$�Gh����03�0v�����a�^'��/��N��d0�
cA�p|��M�-[53c�f1�XXK=���f<�13{<1��"\��f��z������!��W�{�����)w=�RA��2�m���MA�,:��9���2Opaa�
5��h�L`VFf4/	��A������O<�+�a������*0������$���G��D��q}J'�:t�>b�'�~��X"u�}3�2�e0p��y��U�rb�Ja!b��:�.�A�[m����P����q���q@L}�E�&���H�l�c���A�}`6�W�7�+�h��il D=-��w"wk�+��:b���3m��*��>�C�� {]/+C�u	�"��/`��f�+$���R�#m0S�rO������xX/�F
�V
�9@�����p�m*�X�X�f`�����Sc7�7�h�g���<�y���z&����t��n�K�� $�����A�6�D��QRwr���G`����y�h��U�z��_��%�I��J����-,�#>9�,��m�4v������H��j��,����e��}��y������
�l�`m�7�8b{Ep0 &�>�J0��{l�~Zk)��c`P��?��*~���������|:(
�G4�9��[^��_@��������o�9[Y1���e���]���{����?����7X*P�zO����E��9<8N?s�>�.�g������n��k��P���e�������sh���~���f�?���
�8�#w�&1�Mvx(65�����.k�f^�����4L������wb�z�q����!P?8��a���l�����[���
k���l>����:��d��d��l���T���f���e%����{�����q�N�@n�b7�����O"�p���=\&�e�)��	���>��F��)u���=��h������g�����"����@�C,�}�!��x,�4��0��3@D{y+����w���B�}�*�n}#�F��kJ�2Z`��D�r�h�.&����.�~��/��B�_�`�{������z?~h���D���3���"����AT��9Q-S�ev�W��GKgcS����A;��7Ig"���������Gjk
����*�2}
B�������������;�;��{��]X�iuVA���:(,�<8(m�,�|��~I�e�_��=	�h1���n���}��1��6m�`��l�Kc^�\�I��6�WPV���0�@����YHQ�
#��<����o����R��cbCv&�F/2���b����*�]g����mRg�$%��};y!�;n�=1�� �������C�	��[l�n�MHB�t, bh�T�����"�����	c�!��4�����c�hs���h�54��d�����������&��&��u�,2� 1~�x��v���Mv*�v?��
b2��h>0� ����, _����W��3,<�!#Za��������!�0�\���4��g���<AyV$��V����	IQ���]�<w1��:PQ�H�"�`�5���D�Y6�m�|(5�b�4�<����_a���%���#%��&�J.$'��r,��|.��O ��K��C'��sJ^�G\;8z!|�"!|�� 1
��T�l
��A �����S�& G�� ^�A-85��F$�f��H�J������AH o��cR� �s��D����[��=��@��i����0���K��8����pX�b�-��t^��+�0d����`tE���t3���vC��_o�j9�@t���HL���iQ
�j5�;0hyI��KnM��OK�i`�?��6��O�����>�e��������"��D��
6�7��P�q���BL]{���=������z��������������MQ�3'����D
�h.��h0��|���z
�!���%rs�o���IE-��~*M�E�x{���d��6u>��.���Ph������ ,6x�����o�����5�S���C(v�k]���VW��
`��f&�"��2�(A!BI�_�gT�0�<�p��3�W��
���D�tD���aA�(
�	(~h!>f��X���c]}�;Yd�*�
&�������P_Tn���d��J4�f�d��Z�����
��!�-�Y�N�a�;,���e��u7*��~�SI�����*�=N�=�h��9
���6�����d5`�~��K�Z(�cO�0�Q���J�_SI�-O?���- :������d��7�P�d��*�+R1�	X��!i�dG���JA,����%�!h���,W�$r�|$�-5���U6e���
�/�s��:)�F�4�l26�_����5���94��O�@Ll`����`�����@`�EbB����p���Oq.���{��=�qI������7H
��VF\�J�Z�C_�*'�WB�
��q�/%�(
�Ka��?�nA����y�y�z���Q�l�^C?f��S#}pL�5NM��6���k�/(���������N�C��Wl<����`2���\<�>Gqb4�S��b$���s�F�!+�X��r2�?���n����v8�������M��`'�?��������tz�p���dD�W`��m"jl}�N����SIj�K�Y�q�u���pf���qR� ?�F�e����_��&���u�p"2�����1
Y�E��	���1o��"Q�q��p�\-lxTl�at!>���yj�?>��~��r���]���/�C� }�qy���������>D?k��G�	�Z��{A�X��������DA��:�f���|�j�����`���|k��J�� 1c���\��3Hy%�l<�f����!�����1�����n��t�a
��j�*(��X"S��RY\��6d�ZL���`&���s+�2B�������8���� n/��e�F\Z��<����qk=.����M57��*��$Q�	���i���%�����./���`3E�w�n�H�E
��<l��p��8Dp���B�C6�c{�����AO���*V�X-��E��(f_�T#�h��j R���7u��q �(6\��^�t�q����8���1C��c�(w-�AT��d;H�`�D`�P����ol
��$�m���d�����[D�
NE�(���{�M	�#��'�N���"��h���D�������A���{��%VFCP!�*�'^�1�%��6�PETp*�����EV�������#M/H�)Go��NW�,���t�S����j�D\�#)s`������LD&?��,r�Q�]k(s@O3?=��(eEE�Q�P��[*O \�E+`BwhkVu6��=A�dy
e7���J�\q�z-�����Y����K���^� ��O+fH�c�M��r����}g
�B�f��U��*t�=���t�	�}xa��{�w<< ������O���s�,yI*w0�ZZ{�a/��5�J��%����Pr����E5�x�#/�S�T9��b`�he���^����&��(��.q���F���5 ��������G6�G�^F0��b�����*�����������]`\�Y�������u`�$I����&;����Wg�����k9xC�;u+2i%a����a���(:=��gt�9�/]")��"�M��"�axG�gq�s)�Pwme�kgU���g��:��*�+�LG�9����b��^�Y�������3�bk�F`3���x�I�,���C��M�&;�D>�M����c�.N9=/���1��tye]\�S�#T��~n������>�8�a
�a�it������f�eO�<��Z{&�U��Uju�	�����j*�Xk��<���/�������m[����i4���l*t� ���b^���UC�+5���h�Zy�V\��������:�#R�}S����r0AP���TK��x4����\�c��q4�V�/�*�9��;_��W��Z�GJ�������")���@
mU���z�9V���Y�"���G��
�gYsBF �R%J+Ck|���<��2[�u*mC����1���;�&'zYs%k1U}����,/�����E�K�:�Yu]�t� �k�vkZ*��Fl�`I�L�d�!L\���c#k��V��P��d�g��WQ����J� dc���s��mL����
CE��]1�.S"����X����WXD�Y;����@N�*��,��9�o4W���\Z5C#�y����H�m_U,�mN�&Z\�����,�/��W0�~�,Q5�]�R��l�^i!��2?��vxq;T1��3�jvJ�F��]8�/�Wr�jG��S�q\������r�������S�E���WS�E6hT�Dn���������h������^pwZl�{tz�`6��Joy�� ���Ro��������������>�T���w����p�vA
����	����<!�����Le.�}�k��WKkn���R�Q��t����v�d����vO�-.�/m���W�QHw�Q�{���5]w�:�%��y������k�%��,�@=�d����Q��{�&�j�v���^����^4U�6�Z�x�Uy��
,�����o�ao����/�?u�?�U,����t~�����K+��j��t^u���<N�C���r����NgV�YB�`�5���Gt���<�;�e�����oa�����k�/�RM�O�a���|g�cS*������/=�c�����hb��q8������-�����V�Y��`%sJ����>�A�����Nx�(^��G��nY��������sE��I�cz�Ka��G�����sq�5Q����zI�o���.u/onI�����d�p��
���W#��)�R���yNN=�����yp�!x�;z���l�H6m�D�D�0���^'�
V����������V�'4�|.���bL��<�i�^���@�jYT�J#�W)��J���#mjG]��R���@�,+$�hU!�Oi��Ii��xX����������CS{���E<�G�K�����O��y[�	��D�=��2��j,�\�|��qe��������x�u��D!pK������$��_��iwI�:_�Wd�����.�n{����;���p/w�N�T6[S [w��������%��Xd��n�t��5222"2�+4<���������q�9�h���dR[�c���x8��n���V9=!�P���a���)�t�/�R69�D��'l^RC_�R���C�#X��O�|}��@��~a���^�����v�,z:���p��M���R �btE��{	��ZJq�����M�����s����6�#W��;�2�����8���1�&�����R����{0swv�f�&��[�?����||�����t�:X�Ts���3�A\�����w��C�w�����x�0@W6v:���w>c1�t�n2���~��08���XYlgc+<;4�j�5�Z��^��s��5������M$dE��r����t�r���SH�&G��`��������YW�X8���ZV�6��
�g����A���k%y�V��ss�xF���7
b�}�X*�����w\���]�����sf8��BC� Z�Kv~�����'��
��+�!3��M1��q�f]k�����B�]����3��
f ��0��(���2��z�B�'��w����fbudT��{�|i��cw�]?Lg:�Hh.����*
W���Wdv��o��������99>��T��*�!3<�w�
 /�w-����K�3$���S��#�G�x�|���zc~d��C����]{���5.�z���nm�����Ce"���������1L�Ac���@�����,X�������$-�Co|���YL1<G�{tH���H_H�������x2�a>��V�I���_�X5�RQ*E
��Y��e��	��|�-��h��$���8>N��N���78>J�9��O�-�����������0b\�Wr�#��M�3�O�TdtL)���N���Y���������O!�F�@�d�'�=�j+�l���KM�T�,��p�2�����M5k�
B�h
�����;PX�	�UH�������pw�C:�L��r4I<-.Zb���s�n�h}M��g�8��rS2n�� 8�(���9q>y���.|8��[%G\|
X��7�1vt<���2`�-��S��r���������W�����k������4G2[L�����GD��u���K:�5�d^Lf��Z��x�l_����U���s��b\��u�4hF��8'����g�����"]�aF�
8v�v�����F��"I��:��C��8�����0�'�"M���2�ST��Yr�{2d�������c,����&y�"������.���3���9=�7	��R�)A?M����g��{�@�i�@
�7q1���uz���?e����#b�)a�%���A��V�%V_li����>�����[���7;����th�de!����N����7O�'��������@���F�!��:�x�h\��6�\�B�,�4���I�x��z�$����v����M%m���`�a09�������:S��v%����������CK�*`(�#fI����}*����-�J��c����bDn��{t���Y�^�@�������Gh���0*H��sO�R&�J���
��'L*4�g�����������6V#{��������~"�����
Z`�RC�����[�����&���1i� �:*C����l<g/��f3E�{�i���#A�+�a�@\����f��Oi
��j{J;N������uY��b��M5������_��&3�g���\	�]�`��=�HP�9�2v�|�*Rl��ZAR:
�`������Y�����-���X���W?Q�����]HZ���),��{3��E������yf(���Cj�v������Ph�;5��D���5t=���Mr��m�m�J@��+n��<$�^�=�R�WK5�j�1_�H�46�?���w�c����A���.�4Q��\E��R�������v���EH{�Sz��4��K�2=Nsx�#����5K�\5�~�6��B�:\P�?��b���������hed�R������O,��&*:���r,w<�'��t1G�{�����3�,�����~tS����.3��7h�^���5�[{��G����q��-����kk�7���UG�&
�Fhet�Wv8����:��GO��9~���*e~3�+����r}�K�J	�"���{��'^=�������gOy��gy�dy|F�d���SA���7�o�O� /��$"����^Q���������7l�oK������{`n{=n;(���	�v� �YJ�T���(c��
G�&��;n;v>���7�i"�������,�����W�s+���f�����cch\0��r�?V/����� ��e�,*�0�RVN&��4O�&1�^�6+`��1�CU������MlP����)M�H�����q]�m5Fk��v����R������y������1�4�_^��B�U ��B�K2�B�3�EVzH�
!pC%��^OP���B��6��\�/�	@RRf����T���eG�axs�)�j�.N���L3��i�rh��������2h�8F���?T�Z��������z�j7�D�1E�'��8����X��'?7b�^��h5x1s0�v]�8o�n68��=�m\H5*�an�(���3��`�2CH���/�����������>�j�x<��i�&ro@>���	%"������`���&��cu��oB�V���yc�^�b3�����#��(NgWk
���/����,i�-r~�ne�=Y�]a�i����]O����9=kw?5Z'�`:�����8��O��/������_��7��c;����n���fl�]1D-�)`f��*j����Ih�K�<��7z�w�2����!�EZ������o���]Y������P�v[4a!��|g�o��w����yN|G��DP�W���d��2�P�G�|nX��]���<1���y��177l����H��\Fz�������q�1���'QE{TTM~/f��h����]��K��@���8���A��	��?������!3��V���O`R���)�����F�rV���L����|Xl�����w��$lK�kC��7��r��^����Go����2�����������V���{����Y$�
��Il!���`d�^$�G.������d�hm����M,o�&��3Vm�$�U+���zh�����,0�"�t.gCm�[��������O�}*Y����9��z����;��nd�������q�f�5���![#(�{E�-����O���o"�[�W_���/MAl�W��~������]3�w�	<z��|��+A�.>��V����Sd8��w����`<O�i>��af����C!�Fq$\ ��!��_�S�v(��G������9E���		5���w��	lg|�eX�8f�79�������d���������Ob��[{��H4�K���i	}��w!%����^H��*�j�����4��B���D �y<P�	@\t)A.���e�����tA8���4��r����M���E_�*:0`l���P<��Qx�E���>no�QM7T 4]=�xf|������w��������h��Q��U�����������A(�rE�������\�����8��:�q��qh�u����9�X���X�$:�sP�(�kXNwv��3��z3;���Q�����E C2����nu?xs��Q�Z�����H8��ib�o����WHw�����i"��|��$z�n3,��z	N�B\	q�>��d�����_����M{�V/��$]��g!
�T��Jm�`��	�C��1�G�Y��B���j&����I�Q���C�J�v�� ��h9_�HB��������q���lB�cB���������r��	�I�;�����x	�#�p�nu��I^�J�CB%nN�M:eI�:/����yj����h��N�-T������c�,���f/�g��,#)��ero����#rG��2����D���{/#��vP��Cj�6���t�^�Y�b���K>�Y�;�����Z{�Y����4���d��+HI��:S��/�p�)�+�f�1�!����JA�f�sf3�������c����J�����������s���Ws���t>�P�P���g������� ������ln�=f%��WG��BPa��efCe������EY{s�.��V���O�] ��wq	��2g�ee%"���wq�<~s�j��J��I�
��<���8}�)m�������.����X���M�`K����I�n?���9���9�j�0F����y~��h7O���v��k+�p�7�9�����5�CD��3�������s�����������f)�Ttf����Z�@�K��@H�(L"�� @���$�0����>o�����._��� ��BWI�������Jr�Wa���B���|���w������9�qF/�-�yZT��J��Jqb�,���(S�|�:�L�N��$U�����,U��k	z�8�z)�nRa%yRZHU���-p�* ��������0���h�����h�:���?y�f1&�<��H��2�^���B��cy���>�U^YqV�����mh�3>AG	�Kv�;��Fz{GzS*�!���>�14�e�`����&��|�x~�s&�v��@��:�B�)&�+�)�a�n��}An#���{����������9��X;����+��5vk��Q�����}���Z�"�W�P�F���$"��<���3�����L+dE����2�Hn�H2����JQ@��*~��8[��p(bGg.��@o��${����m7�g��T�<��vv��7�`_�u�1[� ��W��*;���QS	���>1p��g}�A_��9[��
-F��o�����&%'��4�R�P�������sTH�w\A���4}���da��/>�W�h�S1�?ASI@1��K+��k����QpQ�iu���[�,���"y���s>���>�I~��������=A�*�%5&�� �S	�:q������@Y���)A�#�y�bc�y������	[�rR$*bTCDhm���^���a������ ���F �q"���	_
��0Kh�y�k��D�VY9�������UN������<�>&���A�2\��Eo.W'+�3^��84
l��!����XnyOl�b��E��Dd[�s�E<��c����f��+mu�I�m����_�v�|��F�40]��#� 8L�����[~��u�E���z
I7�q�r����f)~F(���%iK�>-N�	������S����#C���R����9D/��v
r�3N�J����<�/O��a���g�~AJ[qa��`�{�0!���dV3Dv����,��SB�|r�7D��i�H��7�O@>>;oF�r����]�����)��qJ3����$�C��_� �c����9�.�'lw���B�J���2le�:'�������N@�z������=}�e2�S��Mc����j�]�.�juk��c5��k����x�����-����X���]5m�&"�%����&�0j	��i�a�J��
0x�I�v���"SeBGq��e������u���r����j�>s�?Q��x���V�-�e(H�A����?$`}�G���d�q XB0���|��#�+��HK_�k�Z����/�i@d�7�k��U~�2��r���MH�v���P���2RF���t���rL<UoP]���ID��L
��D����/
�d�T�Z��Co��=�:/�|~�J�$�=z#�qd�K73/|�3+��u��1*y?���g��g���,���B4N�C�=��j �}����DBV�<J��j�:(��l�@���5s?q�'����NE��D���%����C�D��S`������lo��,]�6~�U~�x��m��!���M��2�+�Nc5}N@��3_��fU��z�Es���B����dz�V�C�9�������\���3<Q�5�~*$���R���:��G���T�ng*q=��xK7Z��$=Yg���z�E-��	��R��$�(i����g���A����3��)R�5>v�����?"R���l�S���x"�����	��U������e�5M*��Q?Y(O~#��A����{'c�����:s���7�9����������B��R��s�S��9���Q���
�2��ppb��v1�
p�#����S�\��1&�M�y��B���(���O
�G5�.�"v��n�4O�mNZy��^S��A
7�������u����Bu�l�4����V�w���<&8txU�3=���.?f[�a��o,�F�����I3a�
to��\w�zse��X��r�=h�r�+�8I� V��j%B�/���=��r��8�8�'�9�78�C[����#��]�M6-����f�f1!������~�����M�?�1��
�7�dl�$����o3�Yj�/����@��A��%T}pp|R���L&�~<��
o�H5B�^��3���2�X^�c$�"���Q&��;�_9~�R&��)i�����HE�w��IG���L���U��g��l�f��P������D���c���7��j�$����d���F
q7�V>m�/Jvi���ml#u��(0z�ve�f�����2�Gz�����V������.u�]-YS��4����l��d��b/t���b�Y+���|�hg���6z�U{��:
$Zj2C�/�dxb�@����������I��]������J��WQFw��I�fDt�h�����]o2`�e�������X��,�����
z���V�����'�����U�&�3��I�[aK2�[����I���FcL��^��#�%�Q�;�����c���/H#�����	��t9O����Jv�[��4;�+���b*�X1Z��G@pz���I����P
�s8%�%���.����H�j���N�o�d~c�a/�$J�.{���h�k��b�������P�O1���/��������u?�_uzzU�-��%������q���������X�TR@b�����;\+,S[�
2��A��n�����1/7z��MK'|#�w�ea+.g�4�E��tL��
��]�;�P��<��[V��,k$�E]'��*�`0j�~lM����D;+����D��=ps�^�t�����,�-;��m�<S�|��
�{)d���X7�<n-�N�b�72aKk_���gM4Z�-L�Cx�����@-�������)K����ik�N��lq�4���L�lN��J+4ufs�
��p<��S4�2%�N�d���
�a�����"f_*��3�Qt)

|�z:�l~�������uq���N�S�`; ����1��Q�C0�%���]��I�|�.I��dUN�&�
2��T\�n)s�����)����2������t��NO���h�$��L��gx��?72�bCOc�}k��]r%��F�8m���?M���@u}j�@�{3��8�����v�����vd�X]N�1wh+���M���,w"
�j���n������i����n ��u�Y�8h���)��6�O���Ol�������vk!���-d���g5V+�<��)�<�h�o�_������}�fo��3������@�����������|e]�\#1�:]�s'����+���6B�{z=����f���dB�bx���h��.~�'���������b�i���}6X'�����]	�;,�vY�Q�U^�db������7�����ei���d�l
lU���N���������1�����7�����,��5F��0�����Vn[�%�%6l�����H6�	O}���t�&��"�/�m��h�����U+vUT\;��T�D��cBS)�x��T�N��C�������Uyh��`D���| �O�Ts*.�l�a����.qt��� c��\�:T�E����5��p|5a�_Fg_�[�i2z���e����n���s�-�	tz����8N�O�B?��)�%�5���`���M��i��s��<]*N��Q�X�tP�"��[)!���8,9$�(sC���BZ���������\��^"�,�%��(��\4.Df���e�^��g����V��L=B��ml/��`P�+��Uk2+��?�8T�������g0a('8-<���5����p����Z�����������'�]��7��*��
��D�o��z���es�
����P�b�E��[�Piz�Du����5������4�m`8��h�zz�V�������53�e13K�����7���b����5c%O.?n#F8�������zj� 5���"�L����PCb�,�du��Q��I�ns�����x2��o�'�vW�d�*��
�LY��Z�r��m
6����A�m<���6<�C��f�`�q�������'��*W�����-����n�D�gtp�Z
�J�+��5Vf�AD5s��&�\R�$j����3C�.]��1n����N�9���$�HQk�E-���qR�Br��G��&Ce�����P$������U�W<[���
���'!��]�QiB��������aqS�U:��>��?z���!�oL�	�sW��P��#2?�_ZmXY����J�8�O��un���({�sV�&��+]�XJ��\���G�"2��n8��S�r���k�L�������'��7(�����	,�H�'8�t9�qR5jc,��{��'�<K�w'v���GN�X�V!��[���$vp�A&��y���
Q�@����2G����qX�hY(�2�"�9X�u�}#cn~f���������]x�m�����l�{��=c�)��b�����V��t�.����:fm�Fl�1O�t(�-��P����X�gS��xe��"�e�lN�����\�}��}?s�<��V������g�u`C;���Q�a�E�p�8�E�u\�aB���	����������D1'dd���|) .a�������F/)8[�uLr6�f��
��c�f�^��#���=�'G���i��!�#��E��v��
;i����N�~����Fw2�c9c�	W�I���5a.�dp.��	�`/
�yu��3��t.���\��7�����Z��G"{L���2��o�����[N��A9�l&�����2V�fuj��WF��|&T����\� ���Z�����'����3�v���	�H��\/���;)l7�^'����`m"7��#��:�T8l���7�����%��R�D2���x�|u�S��������x�\E�L:>��X��Z1"��z!c���b��=�{U��h.zi,�����/�sk9����,���{����s�.9��ewi3e�6��m�V����!�Y�+��6�N&��,��wo�{�]����������j7G]���
~�K�����Je�tr������TH��	+�tc��$�����1K�3�X�{����V���Rq���C�8�!
���;�2/T�R[�}�E����?q3�I��\���!�TX�E[��hmF�f% ��hE�|%�S{�=�u=b&SE�@�R�^a0��Zz���6�����f�"B�t�h`59L;�����Tz��%f/��z�0f4l�G2���HrY����(�f7��wT��kn�p�v���^���R�X�V��|~eO���������"_/Tsb_~��2��m�tML��iJ'eW�����b����`d+��(dr�L!��X�ew�~G����"�g��-e�(�
�K���v0���/�cl�z�|�j���W��i�y�8=nf��6�M���&������F��@��~������+�
�@�w�2���d�AGa�	Bd?�8��o��r���v��B���+�Z�$�M��(�D� ��+�Z�.������EM����=��?>{�k���q���n���vU�[��8�������@�&���[]�8%���P=�0"�^��.���V�. ����R�T9r
��J���J����
4����B���Z�?y���+�����./���������/Z|.�t����Q �HbZH@������d1_�	����n�����l�Z�=<:��K�Ux`5��V��J�v�8@��P"���;�	�+��3����������W��L'��*�d�U���N�V]���?��'i����}Xh�b)1���������^u+��Q�W(T*�R�7��W-��N�j�ep9K�z��>y9�Hp=Y��P����+Eo�-X�NT��)��:����C'���UB)�b��������
�z��<8,u+��Kj-�nI%i3��Ux����dx���{C\�a�j/����{ ��/�d�|�o�iFQ���*T��Y�x��������������6rS���@f
����D0K�'�ShU��e�7!C���eP|!F��t�7�7���~=���p@J��w�`1L��)'I�+'��6S��C~����W	]�2�1��]�����H���@7[��w����t1
� ��N����3�!�f������sg(�h������"�r���
���aTgy��6m(��,&9�]��p���u���AL3���r�q���&�.�#���L���t���w�Q^}�%�,1�,'���d� �'�V�R:��^���-��@�c����0���A�6��F��,�	F/Il"���U����@��0h@#H���QB�VysI�C�(?u�k�Dz3�
�l;���n�xsz,��,�~�~D����W�iEq�	��U/��v�6�G�gc6�6���CY�4D8��&�9�j�-o`e�f�Rg�}�)/P���t�Q�r����<<�\�MO.���Zg�}V�:�nRi��V2
�Y��$X����<��;:bQ�X�D��p,�����������#�*�l�_�/{��-u�l��1���J8o9��R7��r�h���xL�5�&s�2���|N�X�kM(������1�
rF�rXs���n)�kMj-��&�D�8 ��>~"f�/��b����,����u_fT�9��D�r-�u���
gc6���#�9<�7O��$�������,j"���������%�l��2j���G�5^������\�Bs,B����6u�N'��� �ig��S9��c��Z�?5NZ�;g?�7��}�����*kh�����g?�&�����IL�T�������B��Wt�n��[�Ls+Q�E\#�v�-cE$���M���B���������/=���7�7[O�"Op2�4��sr�OD���&��7V������
T4�|�:��pd�if��vD��R�X���JSX�s�q�]��*�
�h!�P��*�Q�g��"to�s�x�uEK"�e�8;��P���$��j���_�5)��H��@�|3���E����7V��3���;xJxi�&����@������(�Tx��Z�Ba�S�sW����CRaP����,J��S�9@_��!?�~LH�L���%�5nukG��f������ad3b`4�rG����k^=�L}|<�C9��P9p���[�%�
��N���0g��#9t�D��u�!�+�~��T1N;>�����:�H��5��������O��	�X��D����yl�Tlt��0��P�n�X��u��"v�cv�i�t2�e����3��a6�X6���&x�����O��O%O-��������O�
�Z<�40xjYh��������~M��e�<�$�Cy�&w���7��U=����H���Q�����(������sV�d�`�s~�8:f,#8�#�M%�3�/b�6:���7�Je����kW6�q&GSN�^E7
jLF���L����K� z�Vm�Pb�W��O�����~E98^im�����;^W����BH������99~��-dY]b$3!(��J����-y�&f]E:'��%%��lu����z���r��qk��r��]���w��"�*TJ�* })U���,�-q��X����ui6��`�8�������K�5=��� ��K?�_���s����� �(ypX�>�~oF�q��C@��ng��U`�h���Fn������'���\�::��n20��$b;�y��<|�`+Go��x
vO4�_�^��A$�y�����X���Z�P������[�.A�hC1x-��e�1!�������7.��$�'S��@�x� i^@�� �S�A��������%�~N_���9����f�?Y@V(��P�<������	A��S���tL<�q�m�R��T�H*e��0��9m�N��������=��_��;A`M:}�*+H�L�����y���jn�Wt�/��gI-��U���t���{��l3M#R)�dLHX�_�(k X(yv~��5q��%>�t�5�E����z���K���[+��Jy��Ec�n��B|�N�>IuRE	XM!R�~K�g��FLC�w������SPV����g�~���� %:�4*���m1�����]�]�,E��;�����W�c�e�K�-.<
%��������l�2t�Cb�c&t�����b,y�"�P �X�4^���H*�e#�J{b�����}�+w~5��leZz,F����T�AD���
oQ��OP� �|���9����7Z��t���#����Gu�}���D*���0\����A��N����!���r��;����b�x���!oQ�<R��E;�RE��O�S��a�ZH��������Z�����r���R�vpP��������J�/�xXP
 �����$?N.���W��
�{�j��^.���j�R���n�X-����A�Z;<����x������(��K��f�_F���D���c���?s&@{�����4��c�b����=�(�\.=�'P<*�bS��n����G���)W����RXh�4�HN-�i��x���t�>@1��d�0��t1W"<t��4������k���X���(����*��%��U�x��m/� e�Gg��0J&������DMG��3�l:�*$f|��7���=��*�Jd��S��q&|1-Xw1o����IJU]N�gc�Jy���~���3r80�H�Sw��|L��$�b��Q��G�O	<�G�-�$\�v��)���g�}J�W��R�����L&=i�T+��j���'DwZb��)0�+�9c�j�k1����911^�i��q��+�\l2������<�(a�|���F�r��^����`G���r\�9��
����c(*�������H�h�_�:����]�4_����NcK�{xY�a(M�;��r�+ ��4�-g�7
9h�R
���k|ux ������IB��c�t>z�O��)If��]
�
V�)q� �RuXS���k����=�
�&�1�TW�+:��Z��7�co!l]����vH��?��y�a�n4e1:K)��� T�G�P�[�����g�����`"�w�b���5l����2�����b�V�O8^L&slun9a�

�l��7��s~�K�H)a	������\!�Z�vKF
S������ Pq���@H�E^���U����N������)��b�[���0]`�kq�	��2�R�+IQ���b,�^B�q��@��~��`2����
x�J����8;o�^����^ =��1����*�O1�bL��C(��$q/��l��_P�uz|�|�<m7NP�n��'��i>�+�>��6%UQ���[�/;�gr����n�@�rL���_V�J����4N_�i�l��e\����
��l
����5N�7g�=8rh����U6v+K�-D��Q$��b�P
u#���-��`t�����]4���yz���SK�G�&@������!�9=9{���|��r���F��I����m j�X�����H6���Q��Dq�w�����r����u��Mv8��_y����M��I��.���P(,��nG���{�8����7��0j|�JK���*^��8:��]|
_�U��\���NI�3f9�*{t��~i���D�ELIk����NI-i>������g����n6O�Sr<���&+M�6���O���P��D���d�3;~��E�Sx�����4�Wb+����;���-��- ���������E���������Lbg�Nw��/S
.�.���+�zX�W����w�G��b�_�9�Z����R��;��k��j�[f��xp�u�����j�+I��RI���h)-�*����z$V����2V��K������Fv�����#�:���BE�f~�j�R���%���>�~�R����V��a�aIk�
�%��.y)��Z�~T�U+��BapT�����T����`C$v��O2���W���A���3N]�r�����'�m��M���?���]�q�JZ��{
��=[ppd�M�0�8Y���5r�?���.t0y����'��4�=_����6����T7����g����}M*�X�'z��9�����P�r��j�<�����	S1S�x��L��v��nXA����n�Z,j�b������`���c0~u%�{�3@yt������{��� �C�
���7������w
�^�|��'2���v���.��n���Rz_��-���[����_"(-����_9���Y�oU����������)
p���g�+5������Um`0H��L��QxlOW�@��`��)	���W�$���jb���;|}������'�e��kp
����>���!�C�����D���,���z
c!Y[�#Mq:���&�:���\e��S�1g1��P���7�7V_�������ZK�������4������<)d����[P��7$+S3����(0H�8>Z��\T�\���/��aQ�Cv]�d�P��u{d"Bia����-e�������R�`��c��Ts<_�jg��+�����=t���D����O��>L��
.t���#�f.��c�)��P(��'�=#[/���^aRn��	��1���%��FW�0���Et�IHTW����b�x,3���cMM��<OO58"��V��{�3��x��D[�F�*���>��
�Lh�����1�,�e�"�b�����F��t~!����lr	�F7�4��R5'��h\(�.���7����;�������>]��]���A(��[�����Y��H�:'��#�ihGKZ��Xhr�"��
�>]��z�GNMp<���SU�p�f�r���d��g��a��w�)H��~����Wn����j�}�$qC�A �2o���m-O��u�1k�I�����,�g1#F�����%�`��w��qc�R��	P�����U�{�v;�$T�	�)���4f���a~�������tj����-�-���J�b-�����Z��
rnwbQ���~�ni:�_�_84�m�a�:����Q�]�6�D�O��Y���#��|'�:�����,��v���U�xe�����d����/�g��9��*��>�t
o�\t��kM�{�b�"�������iE��
9J"��~1]��(�22�����tK�`���
�D�����=���t�/V,��rQ���	kkxadQ''�����D�u�8��x��$>q~l�i~G �A��1pg�M��&���qk�8Y6���6`v�R)Ds�D2��$��b��H��,�%����C� �n�6��TIw���8��z|����y'�nVB��>a px���Dm��3j�	Q���3&hw8Y�>NZ����O�'2���4Z��!##�/���eN��1��|��:�����Z�/��XF+uV�bP��j�a�=�z��h��Z�n����f��lt���^�Q����8�2F
,���X��b����&�V�B������Cver>,�*��S������n}��9�s��������dM�������R��z���*�._��)����K�]��"6t����3��������h7�	�K��g��N1�~�b<#��_�8��U�}l���
R8=l��"0��������;��y��y���(a��
�M9���g�ul��9=F��<)�9e�%�r,��i��I���
Y���C�J2*�|����Z�a7���t���d�ny"�#�#�������S��^*>��_{�����N�Of�@>�\�5� �<���6}^E;��4��G!C2�Yp3!��V!j��� cQ6��oT�!X�z�V�W
�^�[���2+�B�9�y�I�����
�+H�C�#F?�D�y���,�
�ybG��u6�����8i�jK[$���no^"�������O�hy@�=���Xf	��:`q�'� ���M�n�1�|�H���c��u��!Cv�'"����r���z��*�+?~���L>
,������g�wz���f�������,�2�wn��c�Cn+&j�b����H"� �[E{�l|��7�����-�1���X�9s;s�x�P��1��������x�Ih6=�A������4��������^���#fS�B�p���g�HTY�VP�;�9K#h/��x��� �f�+�ld�g|L{#:�Q7OnH.�����a�8D��U/'gl+L1|���L��4T�gxD���O�Y���9������u����U�F�|��^x��~��69�"������8���zIf����������9L�o
h1�K{2��z�0����i�_�_�(�3c�I�~.�nP!�"A`DO���B��	��6a��
,�e�/`�X�_?f���*&���;�"�3�8��r�J�
�����|��B66<��%�����������C�4N}q#]g-fQ����$����@��!"KcD��u�U$7���A~`sSG����B�i�4�2w`vs�,Pn�8�r�w��rK�I��uT�Vv����_b=uTt��@>�Z�7*��4h�/%���C�L�,I��5������y>�g����q��y!2UTg�wEl&���t(����\����*���F���	��9
@ ��/�y�1x�������F��h�����-7D��q��L�'�
��F$�Sa6R�9�/7�����&0���X��������=�I9*�PB}�`A�J^�Jx�v�u��HK��;
e��Y�
�}���(��{���a�������~x@��FdEk�5�oC�mRe���m�����Sbyl-�i�)�6d���%��%��"��6[�*��.iK�7����_�	3r��pg1��V�a��1]k�f��	50�6��c��aE�y��^e�����>	�_�n^v�����~�,u�������oXL�����U6j�}@��v�q#���-2]�U�������y�����3��.CJi%(7��
]�oW�C�g��i��$�I�O��6Amje�"
�vF!�MXy��
�����q��\4���)*:��B��%�`��.2m�GGGE1��C�|�f!��XL5]��*��Q���L��~�*����7���w�����-Im"8JE<���C`��Ri�""���H�J�*��nw�B�l%K�B�~HK����@
$�u�3��C��3U�pPsCZ�N�
4y�0���[���)����y����Bs�TQ=�F
R�\I<�\{C���4��-�`J)������9v�b��a�P���)��$i��H����o2z��)&��s��P���KR=+16G1��:�e�$��d������DrS�B��������c�
��i��h�v��@�N�:}���A���M����q0f�����]� <	�f���62c�l�\sqsQ�#�k������"���
8�����:�T���7N�����.	�#�����5m��5�d���E�X���@��LT����)�.����`���sf��Hb�j���g_�nz������"�r����QXn`�!�� ����������Vw�;�����:��J�1�LdF)-�u:�u�t�c���6�X��}(��� �b���/ ���Q��l��:��3�����7�.�~A�R���6����L|5�./�!�F�%��H~ �-�(�`J�t��wW����%Yb���1h��-���P��u�:P�[%�5��?I
g��$�{��qO�?��s�;1���!���eB��u�2b1o���B��!u=�c��!�0s�����W:�	s�.9��%��
T���Bp�^@�J���HV�����}]y���F� �$P$�x"S���x�T!N's��n����w��1p�3Bu���[��[E[��Z�B~@���TT�$�_#�R�<}i�h��w
����@_��� <j����I��M����O`��n�m�g�L�
x�&�y�`�Ea,I�k�e	n4����0. ��5"����3���Y�A
���sJ����|��C��]�i�0�������\TK���t�,oF����pq�x��a�a\~\Xn�~_,����U7������J����
�����0�h���4	��bbROA[����N�>�Vg
� ���H��@���n.4+�,�#�Ju����&d+������������,��A�������a���`���h�����m=�:�3�K�Y6XN�g7���vx&�j�3Y�Go2S���$	��Z����)$<���tET�G!Te�����0n�����F!d��D0�L	Y!4%�~��$�O|"z~�FI��@aCWj�>75"�I;;����B!ku�<��a�
7���y�v/ryQ�o�f���^�+�*�Q���Nj��&rd@y?N��!�����nr�m8�M���'����k��}���$��������2=����!��Q���A���?y��:�)8���I�3y�D�9�z/��g�Mu�C��u�A���I����C����u�b��;�� y�"e��G=w
��*b�s�/���&����R����[�JIo3�Qd�)k���$�k��a��!�l���+����O
��pP�N��!.�M����$7�t-�_�+i� �0�U�~(:����{BV]�����B���XL�6�Ri)��H�R�T�K)RFT_*>��`.lJ��fTM`�F����b8����&���b
�/���M`T�&3wB���C����t�K�L�����4\���/���?�s6Q��7OV�������$��N�E�����Z��c.^���k�8���z^F����� ���r��&��;�F����R:�����l��k�	]�����k����h+���`���F�������R>�o�U'�\]\�HW�7_��3^���#�(��5	m`�cG�*^9��dA�$m!����I�Am!$u�sw��t]`bmP,�b�\����$B�c����y���N�����A�R�9�L�����B�X(�K�"�V?��u�v����r�F�����@���~	E�[�e�R�T~�Q�Z�P[�����X�9c�c�:�����X�������2$������#�mh��^���h�{�01��F��3C�|q|�n��;Y�C%�����^����o����4g�����b'��������+}�dW���krx1��Px2����D�kNj���fB���&�z�YA����n��������u����<�i��������z���]�'����*\���2k<�������"�d������x.��^�j��MK���]�A��M)�i�U�X�o@�9YLZHD�4m�@����g��_7��
G��S]H�T,�[��g3L���t�R!�J�N��������.��1�o�B���7��R���cy34b�A9�/��R���X����{ptE��K���E�����TR��J�Pu�������_����*O�y3H��V��<Q;������������f�D>����������?�J��A8�S�X���t���_*��/wjG�z�������R>*w��J�)+���.�?k������&�p���vH�D/�t�
�O��t[�~��Ll����;8��EG�J��!�[���e��4��P�U����[��Y^��{b��wz��q	;����]-�P���J)&�����s�����(}+\aV�����=�{O!W�����*�#�J+���N���NF�������j��
��r�z���nL��6�,���1�G�\���ag >E^�!rJ�.j���C���i��2��Rrkq�����^�����u9&(��+Yu|?C>K�M�mw4����P_�G�����Q���h��;��Je��(�CY�M����]`u�������G��X��X�;M����>HU�����R�-�	����A��]9hF��j������ ,Gp�W0ER�z�+��~l�>?i���g?g�<��?��<���wJ��z����^�^�?'�4�����ENg�^�a�����I����B�^����S������f:��o��&)�k��m^���@
����@N~�o'��&k?�C�6o&zzJh��������.��P���q��h��:�����95��	}����>�~:�@�_�Up�����	�e
�j�����x����PyD.F8`�|���G�����@�	��S�f&��O''��8��gD�	����)�s�E;��'��9�S�����s��T�ao��S�pd"E(�0�a��3V�=���r�(Eu��a��*^"���aoRH��2mxG������O���f��wCe��T,�P�G�������<:��Q����1SQJ����h������������������>G����&��aV�������L��Q�>:�B��������������7q@����J4�2��w�},��O�og��1����0�K�x|>�+|F/�OD��5��0�������5��(�@9��
��Z*�rGp������c�c�\���]L��K�H���	��\W�	B��:x���Q��&���^U�f�KFUG���\��L/k9������ZNP���
�8e����� D�/>r���G��^�}��*�mstp��q���������)��y��5���'�����*�
�h	��;H-0XY���T+3��_$���\����~G�y,�Q���������6S�KSco^�_��D��da�����3�E���wz�B�?�����(*g$�cd��2�,0���Yp�Uk�����M�(�0��`.vV��L�7��r�"�H�����6cy�+)��#�Fy"�R��kj�/J�yz&�7�
�\���TLQL5��~��G�Q�B������r�E�a�\a�_?,�n���%/b����Kp��C�o,�����4����yf���H ���N���s�2����8���xs�Vo|�k��Z�S#���-��~l=gh�l���'�����ny�W5�k��L�Y]�����:�����U���,��xz��D�y����$�K4���B�[/�k!�o�V)��������������+��Q��x�+8}�X>TJ��:<��;��<.W���e��Uh��l���R�!2Z���7���:�1����C��[�.���>f���n9��	�U���=�r�i�g�0l�j�,�9�p\����P���!���A��E������&6���,�C�_56�j<�RQ���JWzB����c&s3�`Y�.�����
��QX!][WZ�������t{��Q�8(z���,����5UI�
 �qxD�6~������<FH��c�\b��5`1{9����_��b��/�3�=����� ���l�_�s���Qf��(YP%%jRs����������b�[�5��;��#��Wh���4RY�������~���y�Az��o@���YQ������/l��m5�)���������^9�[����N
�o
�S�$#�*��w��������u��CtU?���z 5�����;O5��,m�AY��_���I_���j����9����o�?��g�1-YQ"��v��>t��!�#�&�)}�Q����O���uJ�H��by�T�Pf=���R��8H�����X�T����*�}V�� ��!�	<��c�O[G�`*~D5	�p@�����*uN�|���e@��Oz�����K�'��d(�"����`�m�?��?naM��K>a�/�����?F�2������\�����~�����Y�)�]�^Lf�z�(��+x�/�������6V&��l`���?��@3Y�������nH��n)�*&"W�������'Dvk��JU�x=,���dd3�$�;��`�])
��{X���5q�jl9�[�kG���y���l3<_3h�HZA��$T;��H�Q���j��$x����>@?��{���d��r��NX�2K c��V�/ic�]�t��I�F�Zl9r�A�v��h���LO��?�N_>I�<u�x��(>I���2���E$�u{��a���/�l�;�b��A��MEU?��=�to]f�}����������7�{����U���R�'�u�w��� '[�r|����.�t��c�eJ"�F��o��s����s�����<7fP��uqs�Z=h��&����7lC���Fy��[�������F���3������A�]L`����D�\�A\�
�~��V�\�_��i�k5����\	��?>a������Z�#]����"�s�� ���c�����n���*�]�S�<��J�g8T&�A|�O9����c�:p��q��;�����U�E��[�V��_�f��w_�E����b��u����`P���Z���waS�/]�|�Z�!C�h7N�^fT�����a�Vce��hu~8;CQ���~������u��`������'G �q�^���7���&c�hU���$[X�����3#�q�H]��Ui����z��$'���*�'�c�19�y< ���5U
'��������)��j��G������9�aP�G�B�p�?*U�Gn��G��.y��x�e8� %�r���RR,�rb-m�:�$R�H�q�H��H�b���V���&�*.��E����H��v��	e�:���
�43=���3���l�;�
(
^v��g+�����(���a�\9A��;U�xX���\��-$��ED��Q��0�/���d��6�����T�������Y�1��D�,n�hk��^����-��i�AjR�Zcb�������L��h��
l�G������U {���}�yE��?�m���s�_g�/2/�Z��q������-��/U����W�T���������JG��S���u8Z��n�_���e���_���-�k��.��K���R�����H�*!�7�l��Z�����h���R��>��.�6���b�������O�8����7��CS��+w�o���=�3�����x������r����a0`R��������LM�Bn%�(*i(�h:jne�Q2?���jd
A��)6��aC�g��U|2����x��,�Le�9�"!](j(��q�|�O�h��nT�t����-!������Q3N�
B"�8d�_�VA�)x;1��tz;�7�5|�8;�7���C����������o(B���4�����t+k�&��.g�"p���f�[q^dh�@�e���:����V��BB��`������s��->��%Nk�1�U8,T��D������w�.����U��(t
�����^�F�p:iX�����zWf�!��h3�l*�1@� \4�t8�3�S��p���z���cj��(mFa"���o~"��w�^��t�kB��� ��c6o�� Q�+�r"�51�[��0=�7����#q�uJqh@S����]��
�.�es�	��^�*��5'$�K'�����HQ�����XF����V&~���r<�@��v�{���M�����v�����=�P�<�����>42-".�����N����H���e�N������c���	�6�������O���������!��qJ�}���|h�sJ|����)� ��|(�TA��I+�B�3Yy�f>d�40��������J�:C�J6R�1��3��:��6{2[�#6gq|���#iY�T\jd��$��DC�}�N9l�V*o�97���R��W�x3��8����VqJ��aq��PLK1�C1��~�L�C��_/��Go&T<�����d�����s�kt��\�}�k��W�z�f���S�)��
���Ku���w����E�l��-y��v#z���F����)�v&���;Ge�OV�����t�[lc��3|s���YT��j���j��tT ��R����x�Z-�K�)t����etYY��me��+�*��n/�Ru����s�6u ���9���!�Z:C��5���1����f?�?#>V��=Z����s���&��?'�Ve�N9|���e:�o�J�T��YG�EzO�s.����'q�Y*����!�@�{�7����c�4�ZI]��_Z�x��_d"��bH��u�����7��L<��5)��#����4�����������M����2�<??;��=-��(�}�)<K>�������{jN����y2�o��r����/��YQ+�/��%���cpy��y'�����|���g�|f�A���6FSfg��/�W����B���~N=��n(h����L��]E�6���'����������_��v�AhO���T���N�~M=C�,9�M�2�r�{p[��\���{+f{d���S�>�qX���|),��3��NI}J�(�&��r�Q���3���a���r��$_�f�����+q��8�� ����o��Z�'O=�
]]M%eS{�!g��"x����fBj\HV��D`�$x����,�;x�1�ES� ���������{9!����16Q�Z���P��r b�Y��=E�Q@��aX6�>&i��(�R�uym������/�M*\b��[�de���1��]9���|BV(���LR��	<����h�M3����,���"i�K
���`�c�B��I)��2C�Q�;�%t�^*��W������C�n�:X.���F����4'�1�0�(o\��g&�bS���������������������)HJ���d0�����!c6�|��0�gW�?�kB#�gQ�L�	h�[�������"�
�3�u&���2R����h��Ik������;�;pb
)�g?c%��V%3��}���RDa��i<���)#xt�bO�T����
4�7��������5e~�����>/���)6\�d�k��D{�q�4��a�c@�R�C,�=�� ����,y�&���.qu��.��n�0�Fm8X�0C="��.{�S��lP}�����JH���OG������m"��"*.(;�D.�h�����R\j(��Vx���^���F*��'����u����R�^������i���8�����7��1��csw��������q�qWX
����v��as��D�z�DC�n��#���N��������v�q�����y�>�c��o^u@:W���	��q�_��5�k���>^��6##��P���w�o^5�[�fjx��Z����S��#��#S�������Y�7I������?2�W�S\���y##|7;�<2 �<2;|�p�����7�]c������������{���E��������&aUm���{��������?6���RX�&�?�fl��_vN.N-Lq>�`J��dL�w�����������0'`�z�u,���XL��c��c���)���F1��a
<O�x��)4�X�&a

-S�����vk��s�H*
�V^��9|\�2��%������=�u{��~�p���N�^_�*�4��&��0E�(�m-�P���~�`��e��q��E���R�}t���EbM�������c���y��qr�,E�]4�@�7�T�l��Z�r��{�9�6
�7����I�3wJ��[b:t�Z������J�����8a����Y����� %��L��a�4�Ro�R��S2���c�����tA����A<�a �S �\C��'9��(�i�|���8�I���Vkt~�������,��>�'�k?��P���V3������?��Z���1���n�c~������J���:�9�> �����w������KkxD��c�2�����������A[E��{��tA����)E�EY���s2�t5�(Y�kJ��/F�h��xNzx�z���x�ztFN�j��s-��N��� K��"xIW��o_�Pr\���nN��s�U
��X�S�bH�Z����a0jvh�P}�JK�zq�2��3�C+���8�� Lk����d��E�&�X��Fc����~lw��]�������x���l�z�����q��c�N!�V���A}��9�\ l�;��u���|#G���B
����AM&v
t�m��_�D�F3�#���L����c4)\
L�6Yc���\�ha���b��]�;4��������r��[}rlpGS����hVQ0���^o2Tw
�.������5�^�PH�9a5@iKw1����#��i=��;Cy��z o<~I���@X���*�K.�f\=B��s>l�T�2�� ��}Z��N��j���YWD�P� L>pf�������z!+�H�]��A��a�� �f[��c[>��BY��H>W�.�a�"���a���;�����~AE�1��PZJ�"h$��b���3���8�?����N��^�X��R)H��Mi�I�7���cW@�6Y�����A�c,����5�W�M�hUV��t�%�iy�������e�$z"�9����ekb��i��r�.MlX����6���T"��vS'���c�y��G���I�$\����m����k��nb7���
�)��:
1�T��b���N�:�`��<|k��QP�����!:(��-�P|��&�Fx��6��N�����HT��,�v�%$�FL��&b��b�A������
��9[�%���ao&�+��)Z�x��!��������t�>[���~�y���S�t�//�t��\1���WaXw6������s,K�x��E�xo����{��G��[���	%#x����B�|��_1h-#X�5B�}�����c�Xd�u:�qc�c�]������0~H31C����v�f8h����)�$������/`h�b���oU��(
f�0�d��em�n#/����!~���u
���� ��b:�)gz�fZ`b�$�9�Rw���� S�rQ|#���-D�eb�Y�2���A��F�F��w�2'��4 c7	�D&j	J����%6��X�&�~b��K��$��lj#I�V�$J�M�?�Q����o�>aFBF}��D����z�������R�3�[��O���k�����b��S��]�mh�sK�$�Y�I����61f6� G�PO��2#�8�5
��y�Z�yk��m`�c���Q�r��d��Xs�e�x����0����FO6�����zxw���M���{'��2��/�s���J�9i/��Z�/�r����9%��O6�+�8sO��R�&��xN�#�T�Z]t�+Ud�BD���7���[IZw�����;=,�y��#�`v���=!%J�:����������v����AU:r//����SM)�V����qj��K��	����J�e)��(s��#�y�VH��Cc������E��h��-Ee�[Kg��
%���*2_�Q�T���h.���b�;�"Yo�D
�WVT~�)<�p�ls���XB#�p
5��H�
Q(��'o��"��*s���H�_,s�]����&m�	�UJ2�n������\y��0`�I�M	�L�J���^w1����������ud�.��Gj�����\5`rh���Z��E\4�B��������{Q��pYY]�F���;�D�e�t���^)���6�����X8$O��C����!�o����1�������riU@"��<`/�N��#F������i<5CV���I2����]��[W�0X�h�����m�-w��h�i]6�2>|�H�}����8�t$xu��Hl����\v��	5n8j{�ec�� 
����y�|�*�(�)�d�OZ3���[�[���oW:$:8�mG:`9���f��J���v�47�
���q��<�S�K<�p�]���k������t����U(��������'���= �$�JR�B��Ie|���X���!��H
�p�vtK�J�VG����aR&y�>�(��W�T����R�N(c�uc�(�^����;2�*l��<�����(�����,2 �X��e��(��A4��:���s|vz�<n�+�������)����0W:D��(W�>��P��]	��(��*{��-��ZN&�VW$X��'��	���^���P�B�.Cw���FJ/����e(^M�^E����`d��\BI~��Yl.�"&P���w	����
(:�}
����q�d��.�\F��s��F�/�V�c��YL�������IkZ�0Wi�b�B8��l��l��gJG�h��Qf�`U��e��=hr��Tq��������8����m��1����X*&��>,7G���E�|c��q��y.�K��\���oYb��	�X���k�eKXRm���2����AF��w���^�@$|�d���)I�1
�o}��7�l�B���5������m�{@�:3��l�v�4�����~��\��s�d��@���G%���`N���R0����:���1���--�(_�E��z������*m�`fO{����"���;:(D������eDI��.�j_
�c��s�����h�_����i6o����VdK�<�v�|O#x	���e#�i��2~&D�f�~L�������m��X?�3k���IO���)���*�X��K�6[M9���	sZ{AiV��RZ{M��������_J=������zt��wYt�����T����t�����!�k����)�}�X�]<�w!����������F����;q:�����	��D��vi��r�����@�����LM���to��Qw�7��
�B�D^s{l~�=���&Y0���aU��]	�w���Rq=��Jqg���O���\&cc�� �1>O�pK����.�_�PWy38���p��#���l�|[%����B��t�@������7����U	1Pi>�����Y��������������)�l�'
�y��I����O��au��q���q-KO��c5n�hH��+��f���Lm]:IxSp�+O���f��������9���(r�,G�����]a�N	G-6@MH��7�G���1�=`���X��xT�y�IJ�������q���ct"�������AC";��/$���!C���/=�:�H+
��d)�h��P���z�`��d��� 4M��l"I(l���q�?�'u�2UZaE��Y������'f�Yu�?�{L;B�l�������?A~�n�y���b� �Q
���Xi]�	/4(C���;�O��$���d��T9'$�iM�9
}���'i��j��34,��Y���f��5���N�����1��>��O�I����T��	;��>&���n�w��z����m�{��tP�\'<@�r��$���%S�y[��_�I��zCAc!�SB��p�R�A��;�0i��$�k�h�y�P�&Z�N�C��%��;fKsu�qDJ���B#O���P�KyNs\�(��0#���VO��(����F�^"��3X�'�`���TA��tg���I`�@��0UM�>�\�H-������x��B�����lC�N��(���WT�$����r�L!�`)��(e���C�&��?[L�V� ��{{�B$.�g�=�Bz�=o����]gF����xe�J6�2c��Hr�#��i�6�'G�sb��f���i����r){p����c)]�\�'����W�����}����88�p�K���C��M��c4%�,
�(��[����t���p���� !�VG' �yJe�Y(�k�'��\_��.qP�!���uT=��\�1�j�r}7���,��P���dB�X��
Y4�����u�A��Q�����9����PD���	}�*��t��������:'�Q�d��o7 �)���w���lD��^6�:��2r�r���0�GL��]��7�1��#)SI2&���,�����L�+�6���l�`���('�����9{z�V�d��S��@a��c�;����((�5�r��@2��'���o.p��R�?]9�==V:�Sz�r+�d�<��u����5���j.���a����7�7�M�?�S�C\�j`���4�Gl���Z6J�������=n�Q�F��������@�X.����\�W9�">��8^A�%���j���d������F4���bG����p,��bB��3�BR�/U��L�u����YP_��!��Ayg����;��X5���_ZmXaY������z)Pe����f���I�N6���<�����}�a_/Ur���U�^�M�[M�r�l����$'�l��Q��+��%b��G����u�Z|H=�b�*�y����T��;����7�����l>W>%n�jTI�#�&1�	�J=�>J
E�nW�0l�9:Hd$��"��M$��'+5
��� �b�|h9����
JT���%)��C~=��V��4M7Z�����s����t�FX��OD�R�*���+�z��Z��Q�n���&�@v's��R�U
D����x&=&C���S"�Y���6'S0���w|�����O����p��t��z�Sq�<��U�17���r�#�dB@.��+�$��E�B�*������6��Q&B������=���`����ii2
�-%e���K'@����a4�+���J�Dy����Q2���%� w�x�N�t�%p�%���������Z��Q�����0��C�XGJ������������V�v��St����A�����m�
�!=-#(jPH_=5W�p��:��*�D������f��]-?hM�]�-|������@���`2N��u��WfC��s�7��#}�f������d	�u��'�!Q�o�������;Y�t1ZE��O,J��T�����(�����}�)(d�%�c�UR�4Y���S��50�@d*�t�t������c��L����t����-����s9�����zc�P}�gvm0g9�3�P����kf���$��PV�B������~4Y�0j���0"X![\"E���.��Q)�cd���G6C����#v<@^��ZJ���6��#��qk_����7�(�:]�{#$3�Jx�{�
��������y������z�h�_?��%h!}����i�%#Q<G��#�/[���z�-�{�t%c*\/]�h�����c�W6�l�I�A�I��3�-��z�yW`dp6�;�_=�%�c�=�����V�^���;��>E�{2���Xf���HW�����,��,�p���%.�����"F�O=������3r�C���0���%!�;����3@���H��7��`�@�41q�E�,B�s>*0���hXP����@��d>��,����7EY"���G���/�:�-�� �O���Pv���Y:���@f�j'�hX`KL?��PA\(�B��NcC��.�M��^�Gc�*�Q����?w6!��	��+�l����ZxCH\S���2����
��f8b��. ��(�`�e
n/�[(���x7�����d2*�Sk�l� �����6�
�,Z;����2�5#;s�+��G7�G�mN����r+�r���,�lC�������^9���(<������h%4��$op�S�7y@umv�|����6N��|������Kl]@�D��� ����S��������r8a}�u���c�Sc��X<ZY���Ur�%���4\�8LGz��2�]._����$6-�@$f��$p�o��=L�sS�P\/6=f^������@�W�Fc{����b)�Pb�X�W���$�x�@�Z5F�J'�bE�}���&�@��l���h���tSY�j*�u"�Z����J2[�������hH�/������&�pJ��
Cq�'��T6e�*o�k����"�2��y���/dT?��]B�$.�l��O��:���Y6����t�Q��������|w��{��Z	5���@�����|�h��
1��PPhi�cv�o�|C����d;�����~��D�P`UU�����]��Q�H���%b}�9.�Td��'���O4'��{Pu�H��@e���I�(����s�'��L��U�f��[��p��M5�Sc��l���&��ZoK�P�AiH%�InZ.�F�nSI�]@�O\��d9�4+��k�����S����U&�xa+=+��g�7����,��5�>������\
������
)���$!d��� &��+���*N����au���_Y�!d�,7u0����������(�|��S���p��Y�.��P	�������n<�EG2�q��Ib���w��'!�����z��%���*�
.F��-mQ��f������m@�tm�c	 #�HjKL�3�&�	H"�q�lw~j��i���d=�9�8�>{k�e�����W�B��G66�HE���}0���o�����./���������Gm��Vf/ 3}��0������~h�c�t�IwF��<58�pv���%��q��:���e-d���g5���'��+��Wr�yi�����)�\<��!��A%W��XB�Zv���q��q�����=F��o�;x�N�D�������g>��<Uo-��QB��b:],_A]�[�����t2��,����IR&z�g])!m�����i���3�h�O�.���7`�_����'g?\�J����=���K�����*���*/��a��h���;���^���|����8xp�S�.9A��~r�z����M�u��op�II��N�	�EVn�����N&�w��>�a��M,51�2��[O&�q8k-V�mzG���u�\���c]�m� ��
��fp,�u\��H� ���?G�EJ[
�[k�Y9�8�#��w�s���������Q���5�R�_�O|��%�j���e6��H���f��d��Q������F�6�=L��^����q�� W��tO���oB��Q9GQg&c=w�
#
Y�6����s,?������Ra�2�r�NL��7��1+������0@S����l~u#����������N(�	��E�r���������������#����6��l/\��t�q������q0\�Wr.0]���'�s�8����?����nd�"8}���UNl���L�im)��m
!g�U� v8��&��2V�iB��r|B�J��4&d��kDd�[��Ho5a�5z�]������
S�M��!BJ�$��ys1z����8#w�b�L��7Y��������")3��a�]��i�	��w4H���2�z�)d���� ��H�a������0[�q�:�E9�JJF�j����$�
�Q����
������c��F�_��<�i��/��1��|�7�����w��1%���P<)f���a���F/72���2��O�{3\2.R?$�C��7�@z;-N���r�tP(������z�O�Dw��)��b�F�;:2�������q�0v���`����\��\�Vb�(�_�Z���A�h��r�V���T����A�/ExtP��(��������$?N.���W����b�ppTw�ji���n�T?<r���=(u{�J�zX:p�e�
�w*J�X|L��2�f�y,~�S��M������������N������X��y9�'��=�(�R�q���������.���n�����J��if�RZ2}5h)-�AK7
t$���G��#��Zw��<S�#�V��lU+�h�i��z��c�Z�8���[-�4��~��b?S�'�1�������0v��������������+g|���z�zd�����lzQ�3y�7Y�#L�\�}�*�����������_�t�<�x��y�42I/{�io�w?���+�G����X�"f�G}���J����5�\Q��r�u�{z� tX�P�1Y��`�1�a#F�<��y98�G]o�BQz����v��������qPA�
v<����xD��}f#]��3"S���h�_����Z�U��j����r�"6�yWw�nY��p�%�{�X������X�zm���W�:6N�+xp�j�����\��j���`�����}��{�{����s�<�}�:���e��)/��������P��_E)W�U���`����N\�<�����}	7u��)��2|+��2����
����*|���jz?S%8����Fn�s<��������u���|�b�K���!���5�g��S�=��B�>��I���k��<���aGO0�!����;�/����r26�������8���/��`���j=��<u�J��[m :��T$��!;r�w6�/�Cg��h��Mz>H������CL��{���<i��������KFU����T��*�S�2u��=����A����1��-������<�]���g��2��x�*������v&K�A�����
��������)I}���z��P���
�%��R�\U����k����1��3�C�k>���Iav�E�����i�*W�-�4���*W�-�����*W�-���\-���Kw6rI���e�������_*����Tgw���:�/���(V�_0�k�����z5�&�_�n�J�P���G���8�tW�8~��=f��K]���D"
���O��E��1�x���*��6w�{�y�ru�i�B���A)��]�d��i#��V���^��|�}�
����=�_����*}3����s������]{t����(�C�{���R2��.�r��M���Ui���e����=U���k����|+e }����![���������B=�w�e��Z������/r��)��{�:E��U��I��Q��Yh� ���[����t�j<
`���)Pd��KZ
-]9�D��RTC +D���(����l�.��/7W�>��*��g�\i�#�,'��m�T����C�*�U��`�NEF��@v8��Zs-m6����-���M�|��0�4N�e6`6	��9Z�z�N����`�q�M�1�kb�
������/��R��p �N��TE���Q��FWF�>��H;�F���\�i^��i�%��	��D���LT��\[��3���P���0i	�T���y�p-dx�y��-�[Y��b	��"������5	��K���F_Mg3)���%�5���J<�6P���w2��I-���k����aI��=-o0~�@U�S�>qP��S
��G#q��9�g��Fw�V$\���j�%�h'F�-��G����_`��:��\��/��j��
������ol��77�����%0;�Y�sE�o�*�V�W��[G{K{������lL�;9�O���E7n���|c����v?X��0U6!�t`�d�WT`�$�_f�����]om{k�[?y��"xo�
x���2�nS
c^�W�#����+������f�]f�cw��q�U����^��>�Ogk�$��?JEk%�d�Vo6t�.��c���q��(�3�;N�n^In(�����q���G�����y��b���cXJhP�4�d������&>�� �$�\���l.|~�-T�/��Y=�2�P/�C=8�s�<���K=yTc��L�f�Yw�������'����s�t�����
��Dz�)`
g����3-=��+/���$2���}�eW����>�?����I:�]��;�<N!lN!�*�
&�����C�V�X�\�:�,����c���N'���j`������&���i<lrc�.�V9�_)���H[�;k���=k���?���)�R�<�RH����Q������n���%-/�-��uk-jPs�V/�������
���9X�n]�J��a�aZ]��dW��C����+���!.GoJW��%������	����!�_�o��z�I�`�,�!��W)�0�.���NT�%�.m����a����t��[��-r���������z�P�a������g���t�|>a��]�d�N���X��vy�����B����� �����O�d����@�Eaz�R���-��'�Q	*�%������Z��P%U�G�����E7O���jlNi*��f}J��e����6��������"�Zi�B��\�����p������D�i�2;�o�eDD��4+��V�@�^�
��8���Xf[hL������������N��(/z�=�����O�� �q���[���NBh����S@�^$|gsc��r1A��m���k����=�h�0��*K��S��n�|�v���D*�GG�y�������D���c�b���*�����1��}�v~l`CXY����~��)�U����-���K�_+�k�|��zEF�V����Yn��k�gY-�|O]'�T�e��
��|�nk�����q�d����:q����������kj�1�%�E��h�T�)sj�_�f��#re6Q�u9�oN�1b'e�� ce�s�Gas|�-.��I�����@���87i�i��@��q����y�n
�)�B������<x �'P���������Ll�an���`A������h���N!���p����l�L���-ik��!��d����0h���o�I�����������oCF�J�=���(%2��p(�N������%��osb�9����-�%��h/0g�4Cp@��>Z���s|5�BE�
�������� �gx��!����6��t����#���<!�I���2�'�^5��jGz��=�Ys���kJ[LK������=�6`2Ho�����TzG����<�G���Y$�;2�����T�,�y�Z�E����U��d���d��f�n���*�O��F�����6k�E��<��RO��R��w:&>��ZZ�x�-�>	����������x�L��	�_�����9�]����z�4}����8�-Q�x.�U��k��$��_0�+Vn����|;�����m��W����"��\���`�K���K��J���.w��Sy[{z]��h�(i@z�{�n@�;�<?{�pU�X�B�;t����=i�h��.7���:����6�&	E�z�
`�!�^���6S��	��W?���������_��o[���t7p�sh<c�Z�k3�8_��2&3���N��a�-p�`�afW�=���V�:�/A�7�t��O�w�z��g��%w�c5_�����O���)���,n7�-�7w�C[Q����kRb%��[:��`��&���qt�[g��i2���������+��'�]*�?��ik����YZ�#�b&�[��1���#����dc{w��/p�����p����Z�uf��s��t)�]�KnG�������i3Z���;�f$w���6�-��m���A�3:{�a��0�
Y�|�]XU�������-���c����n'��d�mM�K������6{�3����;�����m��O/��;��EX2�0wai�^�n����{��O���W���������X��-v���3����p�b3���/�+�Tw�2��z��@�?����W�[22�c��y����l���E�4_��`����?�W����W���;'�����(�N�&m�Qu6��k���8v������*������p�Uj�*�?�Jmh��R������r}}�=�L�k�����B��|�*�C���������{%��>f���������u����<>C���|�^D+f���/��h-x�y�������/l�M�D���������E�f+�T����"4�<s����.�w:�{��?����e����}T��� _���]EI�wd�n�Z�_)�~n�}[�k���?���{�{���#���;I>q�����T���3������|��n|���@�����2���\X��w�b�n�Lb����v�V��sxo�_i����u����_���s�q�|v�@�58�Q���LA�>�����O�p�D�8���P���r��(�~��������,�[�����_]��mM��s���P�:��%h�5!�r�[G�;���5��f��XI������?���F�{b|���_�vpg�����{�_�������F}�Ap��P�s�����m?]��[�|U*�;1��*�c__��u&�W�{[�?�~�^��*mG�tW�G��@��<�y��?]��W_�?�-���/��.��5�r�������������{��/�+���l���)|J�e��/f��)��]N�s�Y�x�_g��/k��VB�aV�f�������X�u������

��e�.�t|���?q8�{%������|��3�$(~�b�n�|&�����r����D��+�c){�3��G����b.��w���*�26���~MN#+Bz����w����Z��)��Z��p]�^X���;�Wl���,�l��?Zb�;en�����c����/_�k����ia��i�Q�F��$�*�Fvg����E#I���8���~��/��3(7v!��+������z�u=_���>�n����_�>?�u&���|���>����#�c�||i���`����
�$��:t���~O����_����Y��(�K�������I8Wz�����������w��q�7�w���%\���0�����<�k��}8��D����o�������po�,|w3?^���j�����K��Lce��5����W���d���}���j>������V�0i�_�{�vgX�3���)��9~�6W�	��@��P@�l>���K���:i��B�$�]}��$V����t�
|i���_{�9r|�������c�e�`�;X|�\W)'f��hVN��f�������`���V��s[�#-��P�p��#�L�+d���V}�����33X�m��m�-S��f�P��"s5�VC}Q��U��x?��yi�	�j2kN�sF�]>�/�@ZL���\��h1�;cw�����]��r���/&���X� �oU3�]�m�qK1��V�Gl������J�7�o�O[�/E1��z~�^P
�P��e��s+�n�p��bg����rq���������2x�L���f ����B��@��\�;#W��{��m�Q���y�%��VZ�i�	�����&6b	���R2���a���w��Q�_{��x��.����������d<w��3�|�����������_{[�b�i;]���u|�����SO����J?��fh����?��Lf���+�q�Q�>�6�n�����x���:���xb�C[�|���b��>e	s���d��gp��\�OE������1�m�+O��q�DJs*�=+�6~)��	<,����/b~�u��3r�����\t[x��|N�����f~��Y�����!��O^Cv�]�m��s��o7_0��e~���v��G�k�8T/����}�&�/�t����3w����YN��h]�S`U,��il0����V�%a;�  �sy9s/��6i�����:���(��Y�<?{�$���Us���'@��`*q@�����f�0�'2a�P"�D�W{��?r7i]�/�dK���nYt
o���w�����Y��j��7�����b�.(p����X������4�=^���u�p���k����=�������e1�Y���r]���r}6�Pe�	���J��F	����x�����''�=w:'��&����N-�#z��"����#d���q�3���q�����83�a�
�����Z��M>pc�'����6s��	)1zm�F�:-W�,e�C�	SF~��6��^�}��<~l�
o6��O��>d�>�B\�y�/���5/�r�i!���dg��s���AI|�Ru;T��N��^+
�ng�A���� t��0�{*S��hO�:�����:C�h�8>{s��<��i�����)F�H��=�>��I�3s�2;��W�.�%�D�CD�l:kZ��uz���a���CZ�op��m�!vi�I��>��z�X�`�X�G�\�U\��C�?�4������Mh`�:nB���d
���s� H�'�"&�I-�@���I\���@�������;L�hJ����8f&}o0���7�#�{���l���������a��]�����5
��q�� ���SuK�J�P(U�C������z����l�Nc�����������a�@����+<�l�K��TJ��w�
]��������}�������t�N�"<Db�%X����~5������b�:jT�������f.4��+���{�
g���t���=�k2ug��B��g���'�]��t��Gx��b��-�j���(,���������\8��
N����W����t��3(:�a���o�b~�����f����.�C����Y��>����*"�{pX�z��a�Ppk�2 ��[����P;\�T*��b?,�6���O�����P?���Vb��_
'�w����\�������~>y�z]����b:+��X8$O�a����h�w�yi���N����n��Fk���^?B=X"��t��P�ar�j����+�#�"��1F~���U��X��[���&���yhB������	^s��w��*��x���q�)���xA���%�����J���VsU�����O:����-�����{/��{�T��"�o$�t����E�'q���m-��r�\�� ����{�G�@�;����/[�+��-c
��:|��9��������*j��Sw����}9�3 e����i]��[��	[n1���I�b�9��)����v�s`��J������ Jk�-�7�����0w�i��[��[-�9�4�l�f��S�@j�n@�XV
*P4�
o	����8�:�(d�8�%����%~�]1K�v�-���Vw�es�#@!!#K&��"|��j��0&C:��D�P.�+y�����[NZy-��~�uW�X�m��e��;�9���V��<�Z�6�c���!!c�s����~{r�X<pL��p|x��p����1�bL94t��\;�J~�{�A�=`�r�y�u�}o�H��g�s}��.���&iM��uQ6�2J�L1|�[�0��u�^�������6�H�9�Z�x��j��p���[��7I�Cp������[#����m	n�,������+�>�F�G�Y6�=����F���z���N�'f�4��{���o�fF�a9&���:�]����:}��7�iv����S�k��������XD�C��`at����8!�
��
�D���%*�t�rEq��kh�:��7/�sb^.\�@���{8'7?(���\z���F�(|�V�d�'�a7��'9�9q�����aI���>Cq�K�io�w�Q�i3"RX��,�S�����F�����;$/e������:��\��
"n5W�kj H��u@�����?	H;�"m����g� u��Q�0U6��{`�[�W;`�,�J�{�f�,��<����2;}8E*���%.AxB���tf��,���N���~����l��k��`2s��1��w��{.������b���^I��x}�z�8�U���ka�G"Z��&tz������TH��['|"� �$!=�$�9X�_h]?�2 �Fn�D�^Y��p���\����e�a�gl��{
���,��N
��@���;�z�I�KBS(u������"��:����q?x���j7d����kk��u��,��N'':�X�i��	0#��Z�7��*1�YQ�KI9}?�����J���D'W�b��Ru(U���c�W����b�i�-�5�B���RRa~],J�w;�5��z�8��O���5p~l�:�7 �x���0��R|�(+�$b�N�y]m����.GV{��c����Y���.�K����m���|U�J.�:G����e�I��NC�11��.���"j,	lC�I_FD�����[�x/�b�`�#���a�[�����p!���JbH@v��6 x��|�K�/N�P.&�A���#�y�x"���5�����5�k�u�R�/�+hH����,U*�X�k(Y*~~�����Z5_k��������p;x�)���/���<�����V�"���i��j���m�{X�X��@���5��?�*+Q�u+������/�B��!�xsz�n��Z���B���K��[���I�����@��H ����k�Aw�@��x]���7�h�C��������~���%~W����~*�z�>�����-''!��{]����lx����-ik��a$�x��D
�}�A���~+N�/�4^6�t8��*��f@,�C����;����<�I��M{]����
����DqA<���'��H��me�Y5��g���4>%����|J��5���l!q=m!;���nz�p�e��U�e�����(��-�r���&p���N`��C���;��	G�q���.���&��?��������?�����R�&�5��`�q��g��w���q�g����t������S���K�����?�����3�����q�����8�����M{�?��������?��8���%5;����������?������s���M\�Y�������?�����3����?��8g�]��%6�������3����?�����3�p��wwWq��t�wqw{���q�g��w�����;��K:lv|�q��g��w���q�g��������f�7r��{��s����1w��������K:nv})�y�����1w��s���������n��S�.o�6�����?�����3�����qwt+�t���Vn�n����3����?����?�wW�rI���o�6�����?�����c������9��{�����zCg�)�69����5�Dr����(��b[kz��j;�.�;9����jo�Zo�J��:o�*o������p`Q�
�V)'f�������D_f�����2X0J�������z��R�l�f-ku[�Y�"�7�bGm
�
7�I�����;Y�b�t���]9�K����v�'�[=�ou���j�M�<L��Y�;dV���KQ�����<H5���H���4���v��u��z��."�7��*&1�r���\��n�-�ug^yIE���JK���4�����&��AkI4�������;z�0
��M�s_;3��M�F��E��{�Z�y�_�.��s�[WC&xJ?��X�YO���w����*�e��P��'Fc�y�|���b��>89����w ������x*���B��`R��{&3��8�n����p<a������������f��u�1s�l��6��{�{�o�����
����o�1��;���x1�g�[Pp|)�3������>2n�4�;���0|3�nu���@�\^��K�[�����Y�s���:-0A��[�d�]�e���owv���o
�H�l9��-���1��K��}��Y
�St���Sx�-��(�������.F�B{��G��5<�>����\��j�l2�
��T<�����l�3l2�D�+=o�a������WR�������w+"�)�j!����EV_/�G��E���.c���S�� �
�5"za�$4���7���E��b-��z4���b�(��3��2<-����OPj�!~��4��<
q���|&j���.�`���������2O�C�����nt~��h���a�A�v�c�;�6��p��F���h����%�����3��}f�!����b��GQ�I�E��8t9S�tX��kE��,����`�Y#��?�4�� `7�7���v�k���r�tP(�����Z?��LQ�w��u���X,V���p�>?�����p2��������8?��3g����'��_��^�Z���,��U�,���'~-��{�R;8(Vk���_��z�T��(�>�B��fq9�����z�����MFbP:p��z��Z��N��+��n����[=:�v��W �]�SQ:��c�O�a	���c�+�H�: �8��1}���9�/�&���g��pl=/f@�E����G����������/���E����_���E���T�� ����h)-�%�5Z������4�pH"�q?�>�y���orb����Of�d�� [P���tg�^�4��������cw�����K;}w:�*��t��	�Y�t#�nl"����b����s���H��=���c>�W����w}w����Q����W��+��� ���=�r@���\��K�D��]�������Br�FYT"�O�{�uz�t�z�����G�p�'~�C���O���{�E���s����O�?�)�����1���������.��g���7�|�������vWp����_.���n�R(8��J�XsO��!\V���Y������a����}�<�h~3u���w��\4�����;�I�T��?q���xH8���{�$���pf34�Tm��������Vz�Z���T*����^o1��
�����qsV���������&��$wic�j��N@�K	!J�w�����
M�Y=k�����;}��cDm,�`�J����"��R�u����\��d2L�z�����0�#����E�������?p�:����<t��ua���N2vi�}��;��\�����BmQ�ov�n���ez������V.>���9�	R4(7�V��ixm��s�f=����&���^����ig|����}i�-oW�Z;��
��A�R�%v������w.�����!O?�����l���x3��G@�����O�_���
��kI���3X��;�����m��������`]���>	�w?���eL�>0�g�S%���;����F�_��w�@��s)����4��4c��/X�C|O�J�l��L����<'���"+z@@��b�{}:��=��S��0���z������������#:3���o~���8Ht��(��L��b����Q�@���h
���������|!W_���}��S�����z+����P�z%���45���
�C~�G�j`@��},�`�Z��_��5�BJt�����BM���#��x�0
�#^��zH;��V�v;�h�]���_��gp�da�."�����h	,�1�����mw�? 6������[G�K�=P��u� ����y�f����I�<����6��@9��]�E|�\��I���sT��@���U���Q</�����I����G*Q&DT�u���x���T1O�����������&�y2��I
 H�rG�������\��z��T_N������b�6��|��I��~Cg:�y�Q�_5?�,���u�h]�J����N+k��o��������-��D��s��ZwW22g�&&)d�:(2����<��h���v=�������5�g�.8�L����m���o�����AE�� h���
B�^<��"$P���������/��sw�&�!R�1^r�L����`�
�����]n+uz��������J����
�F�����R0�%���p$�4��yT�'���w�x�]Q@��N�>p]��-
�^9�T���wICF�]R1�\#D��C���.0��p�9�_,��6 g�/�n��:�s��0)���O�F�����U��+�t���l�f��,����-y|��O�h��{C����?�y%�zw�c���������s�����{�<_��M�[���VJ�RH�[�������#�o���t��~�Wq+���T�Wjn�x����C���������I���������6��h�NOf��H�d�Z����@<�5J!�n��c�2��"e����}?��sw�Z���j����t���s$S�p�j���>�D�+x:D����I�v|��19C��)��u)W^����A�/���Q������P��Ng/��p�`G�#�6c#���8v��{������Q)��.8-���N��4��G8_<����u/����E�q������b�'�{@f]���;��K�[c���oiKg�Nz�>"�H���#�Q�q�6���j��5�O.]�X�������H�.�@��HV��&��5?Lg��=��0>i�K�j@U�\m���jjA�r��B��O���v��!J�a��5��bT�~�����i���������VqJ��!p@F���S�W���[--Q�[���88Bv�-��Jv`�~X���zv=b=I���wFR�Gn��x~����!q�6I�G��+��(���~��
��x?�����cy�82
���B{��9n7.`��X"*N��q�^O�A:#�D����0R���a����|>�o9��y��|�sD�gek�	*�e[d��.�Q)w �����Z&�JB�b���&�>�������!��/�sL�h]�$��3���;n����������e��b[���?�$>�������&'N'}���9L����������Yb����{�_�������f�i�E��r�{����@@L1W����,��=0��'�7R��T�H�U��r�".n���cp�~&��p�9��h�b������6�e"31���^;3��r����S��;XZxCO2$�q��o���P<�Y�]�4����X�������R!���Y��G*��/��;�h�>��5J��8�lc�|bwr���eS8w;4���q.�#-nQ*�v�>@� %���
��9����q_��K�N'�+�<KA�|��	�����3��N��;?����|5����xjw�����j&H��
�6�F-��tr0)Y�����&�Jf����!�ISE��K)9����	�|FL����Y��t��H*��S���:�	az&��>�W��"����~�����!�`���B���L�R�����2����}���Ig���i��P��g��^`�v�N&f|x|���������J�ug���K�%rB��@���o����J������`���� �����Ty�G���;�����v���Y�/J��0�Y1� �
!����Jj��8
`��'�"��p���6�[f����() l���d1������.ye{n������\A�N��!����R%�l�,f��<���q�W����.Q��0F^��S�4��r
E#����L��
@�������y���w��;���D�n�.�2�=�pP��r�j9���$�����`�L�fF������nc#O���1�f����!����Uj�C`���'[`�H[��(0'�w��S��w��W�������t~]^,��#�+-��w�m��A�����Od_	�dt�5�������l��Y��am#��q\�����`x"�G|s��o���
!O���u�t���Bw��]������e����w6����������I���K����I���?�/3{���(1b��Z?���j�����1�9�Mr)��Ku��a�"��X� ���b"4
�0��En��=
�qJ���!�jDu����>�)�!���YZ�z9W��/�r�����2��j��<!n������f#���F��&!��53_g�Sq4<J���_��E���Fj]=@�s��?�0�q+R�A�����w���:.k~�����-����>�!���L���gq?\�?r�-�
u���������=�k���>�L]��;��8,!��T�����7��9K<v�`0O�R
��G{A&T���j����f�������F6�����o��o;�YtZ�j���o�k
���M�e~��tT��T;�.����|S�d�*\
oSU:��
�����3�u�]g*&m�
0�L����J���E,Kh�xp�����^���h=����d\
�p���\�dZ�`e���I"%`/G�O��d�uB�r�+�~��Un����x�"���l�f�����5M�����	��a�Xl)�{�Y�p��m�������O�h�y�fj ���!��K��� �T��c5p���*��5J
�5a�M�y��qp����m:��-'����0tS���*b���}.zSmx}��U�X�F���
��gf�h!P"�����7Oeo(��N��J"�4��H���	�S��%	����k�������.�
���y��"�p?�������������~�����of�q�l���|u�����N�^/j�z�V>�;�5��MCKn�M!����a�'����������gh">w����f^���xsz�n��v�//��4t����s������9}w8w|el���sY���+�^�X�����o3����aU(zI���DW��&���x�&�a�8rc����u�i�W��U:|��K�nB�P��J���T0��=���V�QY	��\����Y�O9���a��]��@|����z�Jyu����-z�=�u�4y��C$.{?��m��\#�H��!\e�#�C<?bTP1,�>��'��^�jRYx�������S!g�F�>�_uz���r���)��KA-P_�Ii�r��<*�f���_G��J]��#����J��5�F����d8��%Y`imT��f���
t��
H]:���ugO��7����	s��)� �7|���r��� ���/��]`����4))�� !�^�U��29���"�N�	i���m��V��e�{DH��w�D�(�`���]�K�����J����_���!d[��k�P[)�-�P"��U�m����Uu{���W?u�5�OIh"�"o�A�'|��0�B�C��v`��N����D���U�}UC{x��D���h�|����1I�R�������r���>:{���z?���"��'_����;3\��ra
���[xOm���&�j�R�C��',�d��s+��Z�x5e�G��j=5���*qc�Z&n�^���M5����|�wxp$�Qj@��-��������c�*C���;��u��b�3�Q{j�v�"!lZC�e�������
���]~�������G�U�T��cLl��5�O��YjU���+�Dk�#��Fv�f��d�����X����m�bs�k���2��-gb�X����7�p��@c���}���{sB�r�\�`,��L3�)��f��=jd�^��8l(V�?��E�,�+d"P����r)\�����i�l0�@@�od�d�KQ��xc>�D����z��Q�iz_��Ii5�3�-5PLa�;�a�f�)�Wy���K��j�%u"4���+z�c�&�]kT��K���		��D[+-��b�r�l`6��!�����D������B�qdTe`���|�f�U=$UZi�R0�TFQ$)����d*�S:��6�c���i�>����cE�l�u�K�#+�ea-JJ��d*����Fm@7]Pn2����������R��v�\����R�&���Ze�Gxq�,�>����pvvr�z�4}
����b������7�"���;��������Y4�����|���y`����Y;�jt��F���Qk�����dc%�_�V�����F���6J��)d�b�m�8�:�7R����_��W���v��y��'V	s��1���1����2o���;��3����i�z���E�4�5��r������d ��� �����w?���5����w4��}G����I����T�bf���fp2Yh�\/��7w��g����Uv\��*������d�'��k�������}m�SBy<^n>����i�~�
���T��������$�����6m8�(�)������U�f��)�g|
#+[��e���<���`ga{����c5���/�9`�ZK��m�mq�B��&������%:�haR�:���[&l�/��M��4A�"^���Q��0�#t��%�m�<��9���d,��w���*�f@`��&�9��v�!B�����J�{���$��;MP��)�����gFUm��I%g��L�:���������I����s�Kj������$F����of	K�5�[�,�����q?������Q��1�/�l����x�������Of�{���u�B������L3��B��uM��	2��p��i���m��K���7��m����8_�H��%G�JY����kua�a���>�,�^�7�<MU(���h��k��?A\��n������� �������i�/�UP�O7_}@������?���PT��f��5 �+uHf���
G��I����b���'�)�j�Ou�d�qv��Wm�s�B&~@BH]@�[��%��~����B�_���by�B��%�L!v4�p�U�#����r�?A���7����W���7�L�sB����i����9Bk�����ZK���8B�|kh��X|4ot�:0=%����	*�����|���z~�z$i���M��`�����O��1E��t�������t��&��t1�kR�W�.��:��\�����j���Ow�G���Z��`p�;�
�n���UJ����s��n�V):�b�7�i����r))�S��?1Z� Z"k�h����X�>�*@��U!�&����������O�J5I�E5��N����'=^t���s�s�<��u����p���.�L ��(Q ��(�gI�����+�(���{tT:<r������;(V��K���6���-�C�"��\�n����{����� �:��u0������Q|Tn����3�m����V����H��cT���B����/oLx���k����H|����TLW#W��n�R��)�^����(��������}�����4�������9����z�
�����������Q�WC�&#�������p�{�
K��CO��?�:�������v2�{�E1�\���x���'
b���<��0-�K~�����};F�j��<�����P�T�+`�K�:~���P��(�*n(��?�����1�:���,���j��/�J����.����9�R��Tz��A�T?�������aP�����i��2�WI:�u�G������19�`BS�������x6���<���^�j-t��J�Zy�r�S�#��e��=���R�2������si�{���5����;F=^g��m/��O\C$P�m��X����h*�]^Hy�/' B^�pP'���������,�X����W���	��E�Qa����0�l{�]�*,.g��j���$8g.�'�|���2�/�q0�Vpp�9�2,�W��MY��i]U)APc�}o��t?�������X�����G������:3F�~A�1�'t
�Q=s�XK]b5|�Q�����:"ks�
P,�8 ����!4$B��e�����g���e���-�'�E"�������>+�eS���^�9x=u�6����VyF�]��/$��H_�W��������?����_������Gw��pT�k�t���aJ/�+k0�|a8;���tz�L���Bq_3�����e�G��o�|�)�7�6�If�u#����k�A=��We��$;��������&�i��=���`Pu��nzXB'&;�4y���CJ+��D��
F,�������St���UQL�-��rD
����B��h��^v�S�QJUoG����s������R��D��yE�� ��Q;�	��M=�q�:�}�Q��W"�I�\���	����o-	:(o����������(
������&I������FL:T8xV���/�Fz�R8C��)?rl��W�	���`��Z���L)Nn�ZR���w��>�������?�a'[!T�
*p��K���������n�j�������|�2��^Z�5���w��m�E��"��eEM=�D���Ch�;�/1�
`����B���������H�24
�������6��YT�p���j\P�0K���j!�^6�|(��
�����%(����[�����0�������s������Y6_Jb��cb.��M����W�����RA�j���bv���6i�L�qP�!>Y3v��B��BB�F�YB��Y�|�����i8��8sR)���F�U�����&�#Nl
;�!24�
wL�F��;���Z��DVt��lcA��l�h�KP>��)�}�LJ�k�a0s;$q�uc�������,�E"��7��D8��zp���4�����u��dX����.�|$�}	����4��8�2��Xwi��e�P6��OpWb��'�V�z��'��5�hS�p�HY>����F0�N�"}�6��OF"	�qV������]obI����n�@����Y�L4L��$Lj�6P�G6����Cj�gFGK�!�l���#^�aa
���Q1	!�T������0��l]t��s�����i����G��86^��`���Q�s��W��J���c�� �UR��k���7��X��C�����?%��Tf��r���w����mH�����-�z� e�E��ww�

W"�Z��@��%^�J9W��������|����P��R��&�C�.]�>�������]��gJ���!�����O!o9��r����#;�:\?�����4�������c.l�����t17���C[>��;����e��7����X�	S����D�.|e2�l�-�'	d
����iG��ogh�f�X�w����-.~et�VK�M'T��t��t�9	��1^�d
�%�J�(!������R3,H8e�������&S��;d��=������b ]���7�\��gu����B�^p,�$�nh���%:�x[�O��h�!
N��-:����Ql��y�Q=�/�\@
 Wh�t���Dj]���O,��vg�&����m2��@uz���&�i�+}	�����`��mj��bJ0��"�6J��.0x"d8���s��7_����|�:�}|z&`�?3A�k�f�r&�3���Q���A���`L%)����%L
�Ss�q������ �n����k��W�x�
����E����x�z>\����(�����}���C-i��X3�0����������n�&���E!+������U�z��O�1�)#$�P�bQ���fiS��Tq5`���X���1N����2�x�=h� &6���,���z8�L`\�_�����"���.��*��-�T}��4z��CC!�C���;��B:����E��f��v��T�n�jJ��p��J�-�Kx�����U'�KI�f{�t���B9)�����C�w�/�1��|�\��%�'(�#"�Z����������F7��!O��������c�&�h��x���8n*pZ������dL���Pf"rg�������c�C<�[[��V������Z�X�����	��oh$�z�o�#�G����o:���$�M)����>$$��$\�1����b����j-qY���n$t�+l"��Wz���� C�&�g|�A?�.����	�)(4�9�
�4��N�����������-�9B���+�~��1���*��sw4�O����@�+[=p�;+�kV�5D�����@1��{�%-C5���x������b@����w89�P�C�K����?$�e6�(�U�4���K���~�#s��E�]!�b��'�4m�zU���6�z�P
�����8
%|�!s���3������c�F^{�J<���.����a0�'�P�j��ShEu ~l��Q���6]J����� 8�b������1<6m��q���4�s;�l�-M�:����2������GK����&��,���e���"����X�'���|Fp -���[�l+�-�-���������P��]�W����I,~GU��������sSj]Rf]��5V��Rv#A����"H~���?	���I����1��za,�)SL2���+��4��{2� 3���[���N��T���fxq�J���d�������{3��r2��6'1�3���-��q$u������iA ��q`e����
���xO����6�?
^�P��
�����\`��O>����v�� ��lB�����g��i���V<�
�����M%���DR(8&
�E�:I�L��[$:�����r�V)����	�,�>�.lF�m���\���\j�`�~c��H�}'��{�I��>&b������p��y�3�V!Al�#�X��xi_����~i?���������+Z
�te{z���7����, ��$gI�J���������A��Z�z�SFO���f�e���+IC4��������H%������z���/��/�2�������-�G���8-�����1:R0���Z�eT��	p�>Jp-�p�~	"����g%�|��ROo8��!�N&�w���sS�]�'�7����yz.m�g��x��ug��Hp����N��I��zC�����y�!Gk�+ ��~���?&�*S��W�����F�8+�����u�5?�,8]���sYK��
��F��~z�[�@��=��������5%PY����i��Kl1��H�'hbO�9��'''b�)����|�ma��b�,��d����.t��_�B?��qA�@����2���eoZ�p#&��%�n"3{���r��2��0���������4��e|���D?Q�-d�JW���&:�LR��%�mNX!�4CA��f
�F+��7��3�y���`�]�P�U����5Y�dH���%�k�Xq�s�W��zcf
+�����Z���a�(�S�ZQ��ai�Zw�t<c��.*#*�Or�i�@\K���� Z��B
�!�/����R�(W+&$	X7Ol'Z[��K���h��<t��vPr��%�&mu��t-��F!h���1�tT����M���(b�%�a������.��
2���!���lVn�����uj��5�|�>��|��y�}�%����*4�g��@�e'H�5t,2!�������^�x)T_I6Pt�?�X�J��O�x,��L���o}
��g]Z�~~��mA�+��R�������\�p�S�����W f����22�gL!6��v������#W�#7���S����$����}��qh'�d`��l��_�0��=�V[t��{���9�T���]��+fmh�(�Pc�O.6�a�t�s�2x2C{�.��O�N�z� 4@�.��pf2���a�8y���
Q�]���[�QQ#���n/�!�+y�C��[l/����?���@��o����8�����S��0B�$�f�kp�Z
R�l�������v����}�}�2B�I�U�y�2����>}�\���w�~�.�5�3�0i�ED����(���8���!o�y��
;�7��������T��p���7M�Kr ��`*�����$�N�e�~sA�� T�!h��r��#�N4��O<�L`	�� �E���}����8���$k�I1|)��C�/
;��1�����i�p�m�\�Q���4��w.���+���]i'<J8��?���8���G��BU<���=M�qUM�[��������	������>J75�
{��Utj�k;	\[�C�~2D[��#m��n�d�v�vu��j�����`�����&�	Kp5�Vz�����0a���S��r�����+6
4�9��"~�DQ����Q������s5����!����K��?01i�2�~4�ZeE�|2LM��U��o9G1�	�m��r��L��x2B>����������Hl����q��q�<'8T��v��^���?��� 6g�>T�#������^��pD�;8 <kA9�/!�Ku]�&&D�V���
���n�s����u6�-z���8��1�*�x7����Q
o��9am9MR��s�����������=��[+!��p8�����������I�+������P��c�
2������\���A�#��'��A������L�Fj�N�����uX�r=�m-����9�Sx�+.�m��Zb�cB4�����3������}{�Q�������F��#P��"�����i�&ONxO6>�1�:5�&��r���n��@���>{��(�jn&q��w���x�%E�s��g��TE��:J��� !������I ;IfM<�x��E�������N1PD�����-�N�z���������k��pE&��3�lK�%���(�m>��=���������
.��N�*X�����GY�5�(k����(�3O
WZ��zCQ�?��/�}6�����.2��p�|��m�l���&

��a������	��6�&��f	}�g)��D��T�dQ�1n~�a�-8Z�OO^_r�=i������j�������-Q����^���j�>������d!4/�Q��$w�J���s�}^<[�����E��Ee��a��E�Y}�t<��4�<2����I%��S��g5�J�3��o���`~w�L��zT��3����C�������S������l�Ma������%h0YLM�r)�:�7����'�%/�<���Qn<Q��Q�����d��x2F	�D��l=�+��'�9:;&���p47AI�����Yy��=��UTH��}�z[G��D�[��O8�S�t	����P��>1�_(
�=�q�����h��~�-J>�������5{�
AY��]����%$��I��U|��x,aO6v��w3�Q��E�i]�p��$f�h�������Rv��X4���W��|#�?�?�<��D����c$w��o��T3��"���a�>L	�g��qb���%�(�(��&#k���0�
r��h�B��H���:'�m������I���4��TV����^�];����[E�~04<���:���o;����f���)�������
�9��m���w��HM������f A8���>��T�2�@,G������>�0���6�]VI��URZ��Z0����q�����g]0�h\c�;�G�z�pg6�����3��������3�k������	 E�V�/��O��/�h��j�v��E��%�>M.�*h�@�~j���F��;wZx�dzf-P��N�X{n5b���b�����
�y�M��Wu�F�^��Y�)�UvF��PQM</�0��@�fX��owh�I84s�6�������4���,3��_J�]�|�#�46w>3���t!(������"	O���9����.���z��������Z�#7�{e�z��C:����}���f`�rD��� 4)@_=Qy�RA�	����C�F��m���/���=p�-���(�y�Ed��i�4 ��yD���V'my��meGA��������1h���!���-~�EI	syC?/~��)}Y��l,����%P��D��9����B�w;�z�l<�V�u
��Y��@�{��a�������Xr_���vjD��i�Z�80�|�=nO�,;����"v�8H�������19Eb����V��Hu�I�-��[,�r���r��Zr]��_�Z���,�"��}�x�h���0gU�������� ����AK���x)�xE
����%5�)���yy���Gb�<C?�vq�;�&@���/h��+U�~���K�e��|���~��U�����%��}7"H��0����w;�e����6�3���}�|������������������������W��4�}|�'����B����MzH 6Pu���M�U�)L0\��f;�093��X�c�#c9$��8����)bN$G��*����b��NE
��E������/����GI���m�nZ��y�B��z�x��6�pe0P7�Po���b���� 9����CN
��p���L���
�w7_���dY�����sa���9�w)��G�+�.g�����W�f�ny�{���['z�������3���O�g���5�A]��=��a&� `O�`�����W5��q��A����Sj�['���Q&��q+���lt^���vT�*�A����@>�k�%i�nmV��B=B��(ghk�r�J|���HG�-��B3�������P�;��
qL���-�����+���I%�h���v$����~a�y�e5�_��V��|/�+��E�oUH�1�����;q�/�B��������({�T������v�S��
�[���[,E��X��%F-����5���1fKg��2-��E�_����CQ��B�P�|�h��cvvD�l�����q��=���E�^�������������;����%`��t�~���}K�.A1O���?x	{�*��x+B�1�e����]�6)�IW�zRD��St�?���0���1���&��_���z�}���n�5��R�/8J���va
n�@dH�_�R����X�@�K���
\{�y�h�`�X���u��L��b��A�)"�P�3[�3��1�~Ec���3sQq��`q,<�2�M�k9�{������8����K���7�g����s�{�|F!q&B}��k��Zb���S@���X~��+�PN�,�b������~�U�����!?,sb(/F	��[���+����M�T� b�s���=8p�%�R
�@^:�kwi�X��T(!Y[�2�1�y�N|_Z� ��c^q����jN�%x�������������w~�P`�p&OL�v�����~r��������
���g��BD� �y��O�{������A����)�h$�,*[[����g�>����GYI>"B�0D`��O��sK��{M�KX�2q)buZ=2�BSV�MG�9-z76���P�vv�(��]�O�o�JQ�K*����u�J��� ��jH^F����������
0�%������;
n=R0�
�$��sMoj������;������8.kP�ZEoA-�z]����w������y��7�Hx����E�X)T�_�N�y\�LL�-�f�gN��R����%Got����:�]��z.H(�C$`GjA��\9����=�o� � �*2:�=7
�^�~������\]�_�S:�D���m����[mG%NB�
��Gg�[Y����1@�2LI�2Z5��:�uS�W����o����6��������2�8b��:�:�l	U����U�J��15��i 8�P0��VJ�y�I�1��M0y]]��'[�Q��Q��"e��/'�g���V�'�m1qM����[�������~Wf]pp�A��`M���.��L�(���W��/Xa�&\���2�[�,�E��L�t�`�h����~�%��<n����K���ff��[�[�A�U�6�b%�c��
�BF�Z�'�K��@��O�{�����N-�X������yJg+2!���ZU(�A?�Us��l%]1�����Va����1��5��� m���������O��O�0b��q'd0�����������0%X�;�\;��`�R��m /u���������D��Qg�TE�����z�,@��f'��;v��w���W�'
w��d y����������1sjq��Z���B��fMgp�7���-�IL�����JSLP����x���{M��+��tv�������l
�y��*&�Z���Q�K5�8i&H���|�?S=X�L�LG�I��s�h>�����}xh�C����@:���a�1fI�n�'���h��U�@r���/mz�l�+���7�U���,�z��7-J�n�S�F
��J�g�~���Z����>���>��xy�����$�-'�n;1U�F#y���Hp��"5��p��y�]$�������+j��dL���-6����;���[_�k}��������Z_�k}��������Z_�k}�������}��
#65tsunakawa.takay@fujitsu.com
tsunakawa.takay@fujitsu.com
In reply to: Yugo Nagata (#64)
RE: Implementing Incremental View Maintenance

Hello,

I'm starting to take a closer look at this feature. I've just finished reading the discussion, excluding other referenced materials.

The following IVM wiki page returns an error. Does anybody know what's wrong?

https://wiki.postgresql.org/wiki/Incremental_View_Maintenance

[screen]
----------
MediaWiki internal error.

Exception caught inside exception handler.

Set $wgShowExceptionDetails = true; at the bottom of LocalSettings.php to show detailed debugging information.
----------

Could you give some concrete use cases, so that I can have a clearer image of the target data? In the discussion, someone referred to master data with low update frequency, because the proposed IVM implementation adds triggers on source tables, which limits the applicability to update-heavy tables.

Regards
Takayuki Tsunakawa

#66Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: tsunakawa.takay@fujitsu.com (#65)
Re: Implementing Incremental View Maintenance

I'm starting to take a closer look at this feature. I've just finished reading the discussion, excluding other referenced materials.

Thank you!

The following IVM wiki page returns an error. Does anybody know what's wrong?

https://wiki.postgresql.org/wiki/Incremental_View_Maintenance

I don't have any problem with the page. Maybe temporary error?

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#67nuko yokohama
nuko.yokohama@gmail.com
In reply to: Yugo Nagata (#1)
Re: Implementing Incremental View Maintenance

SELECT statement that is not IMMUTABLE must not be specified when creating
a view.

An expression SELECT statement that is not IMMUTABLE must not be specified
when creating a view.

In the current implementation, a SELECT statement containing an expression
that is not IMMUTABLE can be specified when creating a view.
If an incremental materialized view is created from a SELECT statement that
contains an expression that is not IMMUTABLE, applying the SELECT statement
to the view returns incorrect results.
To prevent this, we propose that the same error occur when a non-IMMUTABLE
expression is specified in the "CREATE INDEX" statement.

The following is an inappropriate example.
----
CREATE TABLE base (id int primary key, data text, ts timestamp);
CREATE TABLE
CREATE VIEW base_v AS SELECT * FROM base
WHERE ts >= (now() - '3 second'::interval);
CREATE VIEW
CREATE MATERIALIZED VIEW base_mv AS SELECT * FROM base
WHERE ts >= (now() - '3 second'::interval);
SELECT 0
CREATE INCREMENTAL MATERIALIZED VIEW base_imv AS SELECT * FROM base
WHERE ts >= (now() - '3 second'::interval);
SELECT 0
View "public.base_v"
Column | Type | Collation | Nullable | Default |
Storage | Description
--------+-----------------------------+-----------+----------+---------+----------+-------------
id | integer | | | |
plain |
data | text | | | |
extended |
ts | timestamp without time zone | | | |
plain |
View definition:
SELECT base.id,
base.data,
base.ts
FROM base
WHERE base.ts >= (now() - '00:00:03'::interval);

Materialized view "public.base_mv"
Column | Type | Collation | Nullable | Default |
Storage | Stats target | Description
--------+-----------------------------+-----------+----------+---------+----------+--------------+-------------
id | integer | | | |
plain | |
data | text | | | |
extended | |
ts | timestamp without time zone | | | |
plain | |
View definition:
SELECT base.id,
base.data,
base.ts
FROM base
WHERE base.ts >= (now() - '00:00:03'::interval);
Access method: heap

Materialized view "public.base_imv"
Column | Type | Collation | Nullable |
Default | Storage | Stats target | Description
---------------+-----------------------------+-----------+----------+---------+----------+--------------+-------------
id | integer | | |
| plain | |
data | text | | |
| extended | |
ts | timestamp without time zone | | |
| plain | |
__ivm_count__ | bigint | | |
| plain | |
View definition:
SELECT base.id,
base.data,
base.ts
FROM base
WHERE base.ts >= (now() - '00:00:03'::interval);
Access method: heap
Incremental view maintenance: yes

INSERT INTO base VALUES (generate_series(1,3), 'dummy', clock_timestamp());
INSERT 0 3
SELECT * FROM base_v ORDER BY id;
id | data | ts
----+-------+----------------------------
1 | dummy | 2019-12-22 11:38:26.367481
2 | dummy | 2019-12-22 11:38:26.367599
3 | dummy | 2019-12-22 11:38:26.367606
(3 rows)

SELECT * FROM base_mv ORDER BY id;
id | data | ts
----+------+----
(0 rows)

REFRESH MATERIALIZED VIEW base_mv;
REFRESH MATERIALIZED VIEW
SELECT * FROM base_mv ORDER BY id;
id | data | ts
----+-------+----------------------------
1 | dummy | 2019-12-22 11:38:26.367481
2 | dummy | 2019-12-22 11:38:26.367599
3 | dummy | 2019-12-22 11:38:26.367606
(3 rows)

SELECT * FROM base_imv ORDER BY id;
id | data | ts
----+-------+----------------------------
1 | dummy | 2019-12-22 11:38:26.367481
2 | dummy | 2019-12-22 11:38:26.367599
3 | dummy | 2019-12-22 11:38:26.367606
(3 rows)

SELECT pg_sleep(3);
pg_sleep
----------

(1 row)

INSERT INTO base VALUES (generate_series(4,6), 'dummy', clock_timestamp());
INSERT 0 3
SELECT * FROM base_v ORDER BY id;
id | data | ts
----+-------+----------------------------
4 | dummy | 2019-12-22 11:38:29.381414
5 | dummy | 2019-12-22 11:38:29.381441
6 | dummy | 2019-12-22 11:38:29.381444
(3 rows)

SELECT * FROM base_mv ORDER BY id;
id | data | ts
----+-------+----------------------------
1 | dummy | 2019-12-22 11:38:26.367481
2 | dummy | 2019-12-22 11:38:26.367599
3 | dummy | 2019-12-22 11:38:26.367606
(3 rows)

REFRESH MATERIALIZED VIEW base_mv;
REFRESH MATERIALIZED VIEW
SELECT * FROM base_mv ORDER BY id;
id | data | ts
----+-------+----------------------------
4 | dummy | 2019-12-22 11:38:29.381414
5 | dummy | 2019-12-22 11:38:29.381441
6 | dummy | 2019-12-22 11:38:29.381444
(3 rows)

SELECT * FROM base_imv ORDER BY id;
id | data | ts
----+-------+----------------------------
1 | dummy | 2019-12-22 11:38:26.367481
2 | dummy | 2019-12-22 11:38:26.367599
3 | dummy | 2019-12-22 11:38:26.367606
4 | dummy | 2019-12-22 11:38:29.381414
5 | dummy | 2019-12-22 11:38:29.381441
6 | dummy | 2019-12-22 11:38:29.381444
(6 rows)

REFRESH MATERIALIZED VIEW base_mv;
REFRESH MATERIALIZED VIEW
SELECT * FROM base_imv ORDER BY id;
id | data | ts
----+-------+----------------------------
1 | dummy | 2019-12-22 11:38:26.367481
2 | dummy | 2019-12-22 11:38:26.367599
3 | dummy | 2019-12-22 11:38:26.367606
4 | dummy | 2019-12-22 11:38:29.381414
5 | dummy | 2019-12-22 11:38:29.381441
6 | dummy | 2019-12-22 11:38:29.381444
(6 rows)
----

2018年12月27日(木) 21:57 Yugo Nagata <nagata@sraoss.co.jp>:

Show quoted text

Hi,

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.
IVM is a technique to maintain materialized views which computes and
applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018
[1].
Our implementation uses row OIDs to compute deltas for materialized
views.
The basic idea is that if we have information about which rows in base
tables
are contributing to generate a certain row in a matview then we can
identify
the affected rows when a base table is updated. This is based on an idea of
Dr. Masunaga [2] who is a member of our group and inspired from ID-based
approach[3].

In our implementation, the mapping of the row OIDs of the materialized view
and the base tables are stored in "OID map". When a base relation is
modified,
AFTER trigger is executed and the delta is recorded in delta tables using
the transition table feature. The accual udpate of the matview is triggerd
by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS will
be removed since PG12, so OIDs are no longer available. Besides this, it
would
be hard to implement this since it needs many changes of executor nodes to
collect base tables's OIDs during execuing a query. Also, the cost of
maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without relying on
OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4] on incremental matview
maintenance. In this discussion, Kevin proposed to use counting algorithm
[5]
to handle projection views (using DISTNICT) properly. This algorithm need
an
additional system column, count_t, in materialized views and delta tables
of
base tables.

However, the discussion about IVM is now stoped, so we would like to
restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at
least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables,
but using
logical decoding might be better from the point of performance of base
table
modification.

If we can represent a change of UPDATE on a base table as query-like
rather than
OLD and NEW, it may be possible to update the materialized view directly
instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes on
base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on "change
propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe this as
SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify
which row
in the matview is corresponding to a row in the delta. A naive method is
matching
by using all columns in a tuple, but clearly this is unefficient. If
thematerialized
view has unique index, we can use this. Maybe, we have to force
materialized views
to have all primary key colums in their base tables. In our PoC
implementation, we
used OID to identify rows, but this will be no longer available as said
above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate (eager)
or deferred.

In eager maintenance, the materialized view is updated in the same
transaction
where the base table is updated. In deferred maintenance, this is done
after the
transaction is commited, for example, when view is accessed, as a response
to user
request, etc.

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command
to perform IVM.
I am not sure which is better as a start point, but I begin to think that
the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

In the eager maintenance approache, we have to consider a race condition
where two
different transactions change base tables simultaneously as discussed in
[4].

[1]
https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/
[2]
https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1
(Japanese only)
[3] https://dl.acm.org/citation.cfm?id=2750546
[4]
/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com
[5] https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

#68legrand legrand
legrand_legrand@hotmail.com
In reply to: Yugo Nagata (#64)
Re: Implementing Incremental View Maintenance

Hello,

First of all many thanks for this Great feature
replacing so many triggers by a so simple syntax ;o)

I was wondering about performances and add a look
at pg_stat_statements (with track=all) with IVM_v9.patch.

For each insert into a base table there are 3 statements:
- ANALYZE pg_temp_3.pg_temp_81976
- WITH updt AS ( UPDATE public.mv1 AS mv SET __ivm_count__ = ...
- DROP TABLE pg_temp_3.pg_temp_81976

It generates a lot of lines in pg_stat_statements with calls = 1.
Thoses statements can not be shared because the temp table is dropped each
time.

Is there a plan to change this ?

Many Thanks again

Regards
PAscal

--
Sent from: https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

#69tsunakawa.takay@fujitsu.com
tsunakawa.takay@fujitsu.com
In reply to: Tatsuo Ishii (#66)
RE: Implementing Incremental View Maintenance

From: Tatsuo Ishii <ishii@sraoss.co.jp>

The following IVM wiki page returns an error. Does anybody know what's

wrong?

https://wiki.postgresql.org/wiki/Incremental_View_Maintenance

I don't have any problem with the page. Maybe temporary error?

Yeah, I can see it now. I could see it on the weekend. The page was not available for at least an hour or so when I asked about this. I thought the Pgsql-www team kindly solved the issue.

Regards
Takayuki Tsunakawa

#70Yugo Nagata
nagata@sraoss.co.jp
In reply to: nuko yokohama (#67)
Re: Implementing Incremental View Maintenance

On Sun, 22 Dec 2019 20:54:41 +0900
nuko yokohama <nuko.yokohama@gmail.com> wrote:

SELECT statement that is not IMMUTABLE must not be specified when creating
a view.

An expression SELECT statement that is not IMMUTABLE must not be specified
when creating a view.

In the current implementation, a SELECT statement containing an expression
that is not IMMUTABLE can be specified when creating a view.
If an incremental materialized view is created from a SELECT statement that
contains an expression that is not IMMUTABLE, applying the SELECT statement
to the view returns incorrect results.
To prevent this, we propose that the same error occur when a non-IMMUTABLE
expression is specified in the "CREATE INDEX" statement.

Thank you for pointing out this. That makes sense. The check of not-IMMUTABLE
epressions is missing at creating IMMV. We'll add this.

Thanks,
Yugo Nagata

The following is an inappropriate example.
----
CREATE TABLE base (id int primary key, data text, ts timestamp);
CREATE TABLE
CREATE VIEW base_v AS SELECT * FROM base
WHERE ts >= (now() - '3 second'::interval);
CREATE VIEW
CREATE MATERIALIZED VIEW base_mv AS SELECT * FROM base
WHERE ts >= (now() - '3 second'::interval);
SELECT 0
CREATE INCREMENTAL MATERIALIZED VIEW base_imv AS SELECT * FROM base
WHERE ts >= (now() - '3 second'::interval);
SELECT 0
View "public.base_v"
Column | Type | Collation | Nullable | Default |
Storage | Description
--------+-----------------------------+-----------+----------+---------+----------+-------------
id | integer | | | |
plain |
data | text | | | |
extended |
ts | timestamp without time zone | | | |
plain |
View definition:
SELECT base.id,
base.data,
base.ts
FROM base
WHERE base.ts >= (now() - '00:00:03'::interval);

Materialized view "public.base_mv"
Column | Type | Collation | Nullable | Default |
Storage | Stats target | Description
--------+-----------------------------+-----------+----------+---------+----------+--------------+-------------
id | integer | | | |
plain | |
data | text | | | |
extended | |
ts | timestamp without time zone | | | |
plain | |
View definition:
SELECT base.id,
base.data,
base.ts
FROM base
WHERE base.ts >= (now() - '00:00:03'::interval);
Access method: heap

Materialized view "public.base_imv"
Column | Type | Collation | Nullable |
Default | Storage | Stats target | Description
---------------+-----------------------------+-----------+----------+---------+----------+--------------+-------------
id | integer | | |
| plain | |
data | text | | |
| extended | |
ts | timestamp without time zone | | |
| plain | |
__ivm_count__ | bigint | | |
| plain | |
View definition:
SELECT base.id,
base.data,
base.ts
FROM base
WHERE base.ts >= (now() - '00:00:03'::interval);
Access method: heap
Incremental view maintenance: yes

INSERT INTO base VALUES (generate_series(1,3), 'dummy', clock_timestamp());
INSERT 0 3
SELECT * FROM base_v ORDER BY id;
id | data | ts
----+-------+----------------------------
1 | dummy | 2019-12-22 11:38:26.367481
2 | dummy | 2019-12-22 11:38:26.367599
3 | dummy | 2019-12-22 11:38:26.367606
(3 rows)

SELECT * FROM base_mv ORDER BY id;
id | data | ts
----+------+----
(0 rows)

REFRESH MATERIALIZED VIEW base_mv;
REFRESH MATERIALIZED VIEW
SELECT * FROM base_mv ORDER BY id;
id | data | ts
----+-------+----------------------------
1 | dummy | 2019-12-22 11:38:26.367481
2 | dummy | 2019-12-22 11:38:26.367599
3 | dummy | 2019-12-22 11:38:26.367606
(3 rows)

SELECT * FROM base_imv ORDER BY id;
id | data | ts
----+-------+----------------------------
1 | dummy | 2019-12-22 11:38:26.367481
2 | dummy | 2019-12-22 11:38:26.367599
3 | dummy | 2019-12-22 11:38:26.367606
(3 rows)

SELECT pg_sleep(3);
pg_sleep
----------

(1 row)

INSERT INTO base VALUES (generate_series(4,6), 'dummy', clock_timestamp());
INSERT 0 3
SELECT * FROM base_v ORDER BY id;
id | data | ts
----+-------+----------------------------
4 | dummy | 2019-12-22 11:38:29.381414
5 | dummy | 2019-12-22 11:38:29.381441
6 | dummy | 2019-12-22 11:38:29.381444
(3 rows)

SELECT * FROM base_mv ORDER BY id;
id | data | ts
----+-------+----------------------------
1 | dummy | 2019-12-22 11:38:26.367481
2 | dummy | 2019-12-22 11:38:26.367599
3 | dummy | 2019-12-22 11:38:26.367606
(3 rows)

REFRESH MATERIALIZED VIEW base_mv;
REFRESH MATERIALIZED VIEW
SELECT * FROM base_mv ORDER BY id;
id | data | ts
----+-------+----------------------------
4 | dummy | 2019-12-22 11:38:29.381414
5 | dummy | 2019-12-22 11:38:29.381441
6 | dummy | 2019-12-22 11:38:29.381444
(3 rows)

SELECT * FROM base_imv ORDER BY id;
id | data | ts
----+-------+----------------------------
1 | dummy | 2019-12-22 11:38:26.367481
2 | dummy | 2019-12-22 11:38:26.367599
3 | dummy | 2019-12-22 11:38:26.367606
4 | dummy | 2019-12-22 11:38:29.381414
5 | dummy | 2019-12-22 11:38:29.381441
6 | dummy | 2019-12-22 11:38:29.381444
(6 rows)

REFRESH MATERIALIZED VIEW base_mv;
REFRESH MATERIALIZED VIEW
SELECT * FROM base_imv ORDER BY id;
id | data | ts
----+-------+----------------------------
1 | dummy | 2019-12-22 11:38:26.367481
2 | dummy | 2019-12-22 11:38:26.367599
3 | dummy | 2019-12-22 11:38:26.367606
4 | dummy | 2019-12-22 11:38:29.381414
5 | dummy | 2019-12-22 11:38:29.381441
6 | dummy | 2019-12-22 11:38:29.381444
(6 rows)
----

2018年12月27日(木) 21:57 Yugo Nagata <nagata@sraoss.co.jp>:

Hi,

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.
IVM is a technique to maintain materialized views which computes and
applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018
[1].
Our implementation uses row OIDs to compute deltas for materialized
views.
The basic idea is that if we have information about which rows in base
tables
are contributing to generate a certain row in a matview then we can
identify
the affected rows when a base table is updated. This is based on an idea of
Dr. Masunaga [2] who is a member of our group and inspired from ID-based
approach[3].

In our implementation, the mapping of the row OIDs of the materialized view
and the base tables are stored in "OID map". When a base relation is
modified,
AFTER trigger is executed and the delta is recorded in delta tables using
the transition table feature. The accual udpate of the matview is triggerd
by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS will
be removed since PG12, so OIDs are no longer available. Besides this, it
would
be hard to implement this since it needs many changes of executor nodes to
collect base tables's OIDs during execuing a query. Also, the cost of
maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without relying on
OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4] on incremental matview
maintenance. In this discussion, Kevin proposed to use counting algorithm
[5]
to handle projection views (using DISTNICT) properly. This algorithm need
an
additional system column, count_t, in materialized views and delta tables
of
base tables.

However, the discussion about IVM is now stoped, so we would like to
restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at
least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables,
but using
logical decoding might be better from the point of performance of base
table
modification.

If we can represent a change of UPDATE on a base table as query-like
rather than
OLD and NEW, it may be possible to update the materialized view directly
instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes on
base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on "change
propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe this as
SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify
which row
in the matview is corresponding to a row in the delta. A naive method is
matching
by using all columns in a tuple, but clearly this is unefficient. If
thematerialized
view has unique index, we can use this. Maybe, we have to force
materialized views
to have all primary key colums in their base tables. In our PoC
implementation, we
used OID to identify rows, but this will be no longer available as said
above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate (eager)
or deferred.

In eager maintenance, the materialized view is updated in the same
transaction
where the base table is updated. In deferred maintenance, this is done
after the
transaction is commited, for example, when view is accessed, as a response
to user
request, etc.

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command
to perform IVM.
I am not sure which is better as a start point, but I begin to think that
the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

In the eager maintenance approache, we have to consider a race condition
where two
different transactions change base tables simultaneously as discussed in
[4].

[1]
https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/
[2]
https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1
(Japanese only)
[3] https://dl.acm.org/citation.cfm?id=2750546
[4]
/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com
[5] https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo Nagata <nagata@sraoss.co.jp>

#71Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: tsunakawa.takay@fujitsu.com (#65)
Re: Implementing Incremental View Maintenance

Could you give some concrete use cases, so that I can have a clearer image of the target data? In the discussion, someone referred to master data with low update frequency, because the proposed IVM implementation adds triggers on source tables, which limits the applicability to update-heavy tables.

But if you want to get always up-to-data you need to pay the cost for
REFRESH MATERIALIZED VIEW. IVM gives a choice here.

pgbench -s 100
create materialized view mv1 as select count(*) from pgbench_accounts;
create incremental materialized view mv2 as select count(*) from pgbench_accounts;

Now I delete one row from pgbench_accounts.

test=# delete from pgbench_accounts where aid = 10000000;
DELETE 1
Time: 12.387 ms

Of course this makes mv1's data obsolete:
test=# select * from mv1;
count
----------
10000000
(1 row)

To reflect the fact on mv1 that a row was deleted from
pgbench_accounts, you need to refresh mv1:

test=# refresh materialized view mv1;
REFRESH MATERIALIZED VIEW
Time: 788.757 ms

which takes 788ms. With mv2 you don't need to pay this cost to get the
latest data.

This is kind of ideal use case for IVM and I do not claim that IVM
always wins over ordinary materialized view (or non materialized
view). IVM will give benefit in that a materialized view instantly
updated whenever base tables get updated with a cost of longer update
time on base tables.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#72tsunakawa.takay@fujitsu.com
tsunakawa.takay@fujitsu.com
In reply to: legrand legrand (#68)
RE: Implementing Incremental View Maintenance

From: legrand legrand <legrand_legrand@hotmail.com>

For each insert into a base table there are 3 statements:
- ANALYZE pg_temp_3.pg_temp_81976
- WITH updt AS ( UPDATE public.mv1 AS mv SET __ivm_count__ = ...
- DROP TABLE pg_temp_3.pg_temp_81976

Does it also include CREATE TEMPORARY TABLE, because there's DROP?

I remember that repeated CREATE and DROP of temporary tables should be avoided in PostgreSQL. Dropped temporary tables leave some unused memory in CacheMemoryContext. If creation and deletion of temporary tables are done per row in a single session, say loading of large amount of data, memory bloat could crash the OS. That actually happened at a user's environment.

Plus, repeated create/drop may cause system catalog bloat as well even when they are performed in different sessions. In a fortunate case, the garbage records gather at the end of the system tables, and autovacuum will free those empty areas by truncating data files. However, if some valid entry persists after the long garbage area, the system tables would remain bloated.

What kind of workload and data are you targeting with IVM?

Regards
Takayuki Tsunakawa

#73Yugo Nagata
nagata@sraoss.co.jp
In reply to: tsunakawa.takay@fujitsu.com (#72)
Re: Implementing Incremental View Maintenance

On Mon, 23 Dec 2019 02:26:09 +0000
"tsunakawa.takay@fujitsu.com" <tsunakawa.takay@fujitsu.com> wrote:

From: legrand legrand <legrand_legrand@hotmail.com>

For each insert into a base table there are 3 statements:
- ANALYZE pg_temp_3.pg_temp_81976
- WITH updt AS ( UPDATE public.mv1 AS mv SET __ivm_count__ = ...
- DROP TABLE pg_temp_3.pg_temp_81976

Does it also include CREATE TEMPORARY TABLE, because there's DROP?

CREATE TEMPRARY TABLE is not called because temptables are created
by make_new_heap() instead of queries via SPI.

I remember that repeated CREATE and DROP of temporary tables should be avoided in PostgreSQL. Dropped temporary tables leave some unused memory in CacheMemoryContext. If creation and deletion of temporary tables are done per row in a single session, say loading of large amount of data, memory bloat could crash the OS. That actually happened at a user's environment.

Plus, repeated create/drop may cause system catalog bloat as well even when they are performed in different sessions. In a fortunate case, the garbage records gather at the end of the system tables, and autovacuum will free those empty areas by truncating data files. However, if some valid entry persists after the long garbage area, the system tables would remain bloated.

Thank you for explaining the problem. I understood that creating and
dropping temprary tables is harmful more than I have thought. Although
this is not a concrete plan, there are two ideas to reduce creating
temporary tables:

1. Create a temporary table only once at the first view maintenance in
this session. This is possible if we store names or oid of temporary
tables used for each materialized view in memory. However, users may
access to these temptables whenever during the session.

2. Use tuplestores instead of temprary tables. Tuplestores can be
converted to Ephemeral Name Relation (ENR) and used in queries.
It doesn't need updating system catalogs, but indexes can not be
used to access.

What kind of workload and data are you targeting with IVM?

IVM (with immediate maintenance approach) would be efficient
in situations where modifications on base tables are not frequent.
In such situations, create and drop of temptalbes is not so
frequent either, but it would be still possible that the problem
you concern occurs. So, it seems worth to consider the way to
reduce use of temptable.

Regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

#74Julien Rouhaud
rjuju123@gmail.com
In reply to: Yugo Nagata (#73)
Re: Implementing Incremental View Maintenance

On Mon, Dec 23, 2019 at 7:51 AM Yugo Nagata <nagata@sraoss.co.jp> wrote:

On Mon, 23 Dec 2019 02:26:09 +0000
"tsunakawa.takay@fujitsu.com" <tsunakawa.takay@fujitsu.com> wrote:

From: legrand legrand <legrand_legrand@hotmail.com>

For each insert into a base table there are 3 statements:
- ANALYZE pg_temp_3.pg_temp_81976
- WITH updt AS ( UPDATE public.mv1 AS mv SET __ivm_count__ = ...
- DROP TABLE pg_temp_3.pg_temp_81976

Does it also include CREATE TEMPORARY TABLE, because there's DROP?

CREATE TEMPRARY TABLE is not called because temptables are created
by make_new_heap() instead of queries via SPI.

I remember that repeated CREATE and DROP of temporary tables should be avoided in PostgreSQL. Dropped temporary tables leave some unused memory in CacheMemoryContext. If creation and deletion of temporary tables are done per row in a single session, say loading of large amount of data, memory bloat could crash the OS. That actually happened at a user's environment.

Plus, repeated create/drop may cause system catalog bloat as well even when they are performed in different sessions. In a fortunate case, the garbage records gather at the end of the system tables, and autovacuum will free those empty areas by truncating data files. However, if some valid entry persists after the long garbage area, the system tables would remain bloated.

Thank you for explaining the problem. I understood that creating and
dropping temprary tables is harmful more than I have thought. Although
this is not a concrete plan, there are two ideas to reduce creating
temporary tables:

For the pg_stat_statements point of view, utility command support is
already quite bad as with many workloads it's rather impossible to
activate track_utility as it'd otherwise pollute the hashtable with an
infinity of queries executed only once (random prepared transaction
name, random cursor names...). I'm wondering whether we should
normalize utility statements deparsing the utilityStmt, and also
normalizing some identifiers (maybe optionally with a GUC), eg.
"DECLARE ? AS CURSOR FOR normalized_query_here". However commands
like vacuum or drop would be better kept as-is.

#75tsunakawa.takay@fujitsu.com
tsunakawa.takay@fujitsu.com
In reply to: Tatsuo Ishii (#71)
RE: Implementing Incremental View Maintenance

From: Tatsuo Ishii <ishii@sraoss.co.jp>

the target data? In the discussion, someone referred to master data with low
update frequency, because the proposed IVM implementation adds triggers on
source tables, which limits the applicability to update-heavy tables.

But if you want to get always up-to-data you need to pay the cost for
REFRESH MATERIALIZED VIEW. IVM gives a choice here.

Thank you, that clarified to some extent. What kind of data do you think of as an example?

Materialized view reminds me of the use in a data warehouse. Oracle handles the top in its Database Data Warehousing Guide, and Microsoft has just started to offer the materialized view feature in its Azure Synapse Analytics (formerly SQL Data Warehouse). AWS also has previewed Redshift's materialized view feature in re:Invent 2019. Are you targeting the data warehouse (analytics) workload?

IIUC, to put (over) simply, the data warehouse has two kind of tables:

* Facts (transaction data): e.g. sales, user activity
Large amount. INSERT only on a regular basis (ETL/ELT) or continuously (streaming)

* Dimensions (master/reference data): e.g. product, customer, time, country
Small amount. Infrequently INSERTed or UPDATEd.

The proposed trigger-based approach does not seem to be suitable for the facts, because the trigger overhead imposed on data loading may offset or exceed the time saved by incrementally refreshing the materialized views.

Then, does the proposed feature fit the dimension tables? If the materialized view is only based on the dimension data, then the full REFRESH of the materialized view wouldn't take so long. The typical materialized view should join the fact and dimension tables. Then, the fact table will have to have the triggers, causing the data loading slowdown.

I'm saying this because I'm concerned about the trigger based overhead. As you know, Oracle uses materialized view logs to save changes and incrementally apply them later to the materialized views (REFRESH ON STATEMENT materialized views doesn't require the materialized view log, so it might use triggers.) Does any commercial grade database implement materialized view using triggers? I couldn't find relevant information regarding Azure Synapse and Redshift.

If our only handy option is a trigger, can we minimize the overhead by doing the view maintenance at transaction commit?

Regards
Takayuki Tsunakawa

#76tsunakawa.takay@fujitsu.com
tsunakawa.takay@fujitsu.com
In reply to: Yugo Nagata (#73)
RE: Implementing Incremental View Maintenance

From: Yugo Nagata <nagata@sraoss.co.jp>

1. Create a temporary table only once at the first view maintenance in
this session. This is possible if we store names or oid of temporary
tables used for each materialized view in memory. However, users may
access to these temptables whenever during the session.

2. Use tuplestores instead of temprary tables. Tuplestores can be
converted to Ephemeral Name Relation (ENR) and used in queries.
It doesn't need updating system catalogs, but indexes can not be
used to access.

How about unlogged tables ? I thought the point of using a temp table is to avoid WAL overhead.

One concern about the temp table is that it precludes the use of distributed transactions (PREPARE TRANSACTION fails if the transaction accessed a temp table.) This could become a headache when FDW has supported 2PC (which Sawada-san started and Horicuchi-san has taken over.) In the near future, PostgreSQL may evolve into a shared nothing database with distributed transactions like Postgres-XL.

Regards
Takayuki Tsunakawa

#77Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: tsunakawa.takay@fujitsu.com (#75)
Re: Implementing Incremental View Maintenance

But if you want to get always up-to-data you need to pay the cost for
REFRESH MATERIALIZED VIEW. IVM gives a choice here.

Thank you, that clarified to some extent. What kind of data do you think of as an example?

Materialized view reminds me of the use in a data warehouse. Oracle handles the top in its Database Data Warehousing Guide, and Microsoft has just started to offer the materialized view feature in its Azure Synapse Analytics (formerly SQL Data Warehouse). AWS also has previewed Redshift's materialized view feature in re:Invent 2019. Are you targeting the data warehouse (analytics) workload?

First of all, we do not think that current approach is the final
one. Instead we want to implement IVM feature one by one: i.e. we
start with "immediate update" approach, because it's simple and easier
to implement. Then we will add "deferred update" mode later on.

In fact Oracle has both "immediate update" and "deferred update" mode
of IVM (actually there are more "mode" with their implementation).

I recommend you to look into Oracle's materialized view feature
closely. For fair evaluation, probably we should compare the IVM patch
with Oracle's "immediate update" (they call it "on statement") mode.

IIUC, to put (over) simply, the data warehouse has two kind of tables:

Probably deferred IVM mode is more suitable for DWH. However as I said
earlier, we hope to implement the immediate mode first then add the
deferred mode. Let's start with simple one then add more features.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#78Yugo Nagata
nagata@sraoss.co.jp
In reply to: tsunakawa.takay@fujitsu.com (#76)
Re: Implementing Incremental View Maintenance

On Mon, 23 Dec 2019 08:08:53 +0000
"tsunakawa.takay@fujitsu.com" <tsunakawa.takay@fujitsu.com> wrote:

From: Yugo Nagata <nagata@sraoss.co.jp>

1. Create a temporary table only once at the first view maintenance in
this session. This is possible if we store names or oid of temporary
tables used for each materialized view in memory. However, users may
access to these temptables whenever during the session.

2. Use tuplestores instead of temprary tables. Tuplestores can be
converted to Ephemeral Name Relation (ENR) and used in queries.
It doesn't need updating system catalogs, but indexes can not be
used to access.

How about unlogged tables ? I thought the point of using a temp table is to avoid WAL overhead.

Hmm... this might be another option. However, if we use unlogged tables,
we will need to create them in a special schema similar to pg_toast
to split this from user tables. Otherwise, we need to create and drop
unlogged tables repeatedly for each session.

One concern about the temp table is that it precludes the use of distributed transactions (PREPARE TRANSACTION fails if the transaction accessed a temp table.) This could become a headache when FDW has supported 2PC (which Sawada-san started and Horicuchi-san has taken over.) In the near future, PostgreSQL may evolve into a shared nothing database with distributed transactions like Postgres-XL.

This makes sense since you mean that PREPARE TRANSACTION can not be used
if any base table of incrementally maintainable materialized views is
modified in the transaction, at least in the immediate maintenance. Maybe,
this issue can be resolved if we implement the deferred maintenance planned
in future because materialized views can be updated in other transactions
in this way.

Regards
Takayuki Tsunakawa

--
Yugo Nagata <nagata@sraoss.co.jp>

#79legrand legrand
legrand_legrand@hotmail.com
In reply to: Yugo Nagata (#73)
Re: Implementing Incremental View Maintenance

Hello,
regarding my initial post:

For each insert into a base table there are 3 statements:
- ANALYZE pg_temp_3.pg_temp_81976
- WITH updt AS ( UPDATE public.mv1 AS mv SET __ivm_count__ = ...
- DROP TABLE pg_temp_3.pg_temp_81976

For me there where 3 points to discuss:
- create/drop tables may bloat dictionnary tables
- create/drop tables prevents "WITH updt ..." from being shared (with some
plan caching)
- generates many lines in pg_stat_statements

In fact I like the idea of a table created per session, but I would even
prefer a common "table" shared between all sessions like GLOBAL TEMPORARY
TABLE (or something similar) as described here:
/messages/by-id/157703426606.1198.2452090605041230054.pgcf@coridan.postgresql.org

That would remove the drop/create issue, permits to reduce planning time for
"WITH updt ..." statements
(as done today in PLpgsql triggers), and would fix the pgss "bloat" issue.

Like that the "cost" of the immediate refresh approach would be easier to
support ;o)

Regards
PAscal

--
Sent from: https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

#80Yugo Nagata
nagata@sraoss.co.jp
In reply to: legrand legrand (#79)
Re: Implementing Incremental View Maintenance

On Mon, 23 Dec 2019 03:41:18 -0700 (MST)
legrand legrand <legrand_legrand@hotmail.com> wrote:

Hello,
regarding my initial post:

For each insert into a base table there are 3 statements:
- ANALYZE pg_temp_3.pg_temp_81976
- WITH updt AS ( UPDATE public.mv1 AS mv SET __ivm_count__ = ...
- DROP TABLE pg_temp_3.pg_temp_81976

For me there where 3 points to discuss:
- create/drop tables may bloat dictionnary tables
- create/drop tables prevents "WITH updt ..." from being shared (with some
plan caching)
- generates many lines in pg_stat_statements

In fact I like the idea of a table created per session, but I would even
prefer a common "table" shared between all sessions like GLOBAL TEMPORARY
TABLE (or something similar) as described here:
/messages/by-id/157703426606.1198.2452090605041230054.pgcf@coridan.postgresql.org

Although I have not looked into this thread, this may be help if this is
implemented. However, it would be still necessary to truncate the table
before the view maintenance because such tables always exist and can be
accessed and modified by any users.

--
Yugo Nagata <nagata@sraoss.co.jp>

#81tsunakawa.takay@fujitsu.com
tsunakawa.takay@fujitsu.com
In reply to: Tatsuo Ishii (#77)
RE: Implementing Incremental View Maintenance

From: Tatsuo Ishii <ishii@sraoss.co.jp>

First of all, we do not think that current approach is the final
one. Instead we want to implement IVM feature one by one: i.e. we
start with "immediate update" approach, because it's simple and easier
to implement. Then we will add "deferred update" mode later on.

I agree about incremental feature introduction. What I'm simply asking is the concrete use case (workload and data), so that I can convince myself to believe that this feature is useful and focus on reviewing and testing (because the patch seems big and difficult...)

In fact Oracle has both "immediate update" and "deferred update" mode
of IVM (actually there are more "mode" with their implementation).

I recommend you to look into Oracle's materialized view feature
closely. For fair evaluation, probably we should compare the IVM patch
with Oracle's "immediate update" (they call it "on statement") mode.

Probably deferred IVM mode is more suitable for DWH. However as I said
earlier, we hope to implement the immediate mode first then add the
deferred mode. Let's start with simple one then add more features.

Yes, I know Oracle's ON STATEMENT refresh mode (I attached references at the end for others.)

Unfortunately, it's not clear to me which of ON STATEMENT or ON COMMIT the user should choose. The benefit of ON STATEMENT is that the user does not have to create and maintain the materialized view log. But I'm not sure if and when the benefit defeats the performance overhead on DML statements. It's not disclosed whether ON STATEMENT uses triggers.

Could you give your opinion on the following to better understand the proposed feature and/or Oracle's ON STATEMENT refresh mode?

* What use case does the feature fit?
If the trigger makes it difficult to use in the data ware house, does the feature target OLTP?
What kind of data and query would benefit most from the feature (e.g. join of a large sales table and a small product table, where the data volume and frequency of data loading is ...)?
In other words, this is about what kind of example we can recommend as a typical use case of this feature.

* Do you think the benefit of ON STATEMENT (i.e. do not have to use materialized view log) outweighs the drawback of ON STATEMENT (i.g. DML overhead)?

* Do you think it's important to refresh the materialized view after every statement, or the per-statement refresh is not a requirement but simply the result of implementation?

[References]
https://docs.oracle.com/en/database/oracle/oracle-database/19/dwhsg/refreshing-materialized-views.html#GUID-C40C225A-8328-44D5-AE90-9078C2C773EA
--------------------------------------------------
7.1.5 About ON COMMIT Refresh for Materialized Views

A materialized view can be refreshed automatically using the ON COMMIT method. Therefore, whenever a transaction commits which has updated the tables on which a materialized view is defined, those changes are automatically reflected in the materialized view. The advantage of using this approach is you never have to remember to refresh the materialized view. The only disadvantage is the time required to complete the commit will be slightly longer because of the extra processing involved. However, in a data warehouse, this should not be an issue because there is unlikely to be concurrent processes trying to update the same table.

7.1.6 About ON STATEMENT Refresh for Materialized Views

A materialized view that uses the ON STATEMENT refresh mode is automatically refreshed every time a DML operation is performed on any of the materialized view’s base tables.

With the ON STATEMENT refresh mode, any changes to the base tables are immediately reflected in the materialized view. There is no need to commit the transaction or maintain materialized view logs on the base tables. If the DML statements are subsequently rolled back, then the corresponding changes made to the materialized view are also rolled back.

The advantage of the ON STATEMENT refresh mode is that the materialized view is always synchronized with the data in the base tables, without the overhead of maintaining materialized view logs. However, this mode may increase the time taken to perform a DML operation because the materialized view is being refreshed as part of the DML operation.
--------------------------------------------------

https://docs.oracle.com/en/database/oracle/oracle-database/19/dwhsg/release-changes.html#GUID-2A2D6E3B-A3FD-47A8-82A3-1EF95AEF5993
--------------------------------------------------
ON STATEMENT refresh mode for materialized views
The ON STATEMENT refresh mode refreshes materialized views every time a DML operation is performed on any base table, without the need to commit the transaction. This mode does not require you to maintain materialized view logs on the base tables.
--------------------------------------------------

http://www.oracle.com/us/solutions/sap/matview-refresh-db12c-2877319.pdf
--------------------------------------------------
We have introduced a new Materialized View (MV) refresh mechanism called ON STATEMENT refresh. With the ON STATEMENT refresh method, an MV is automatically refreshed whenever DML happens on a base table of the MV. Therefore, whenever a DML happens on any table on which a materialized view is defined, the change is automatically reflected in the materialized view. The advantage of using this approach is that the user no long needs to create a materialized view log on each of the base table in order to do fast refresh. The refresh can then avoid the overhead introduced by MV logging but still keep the materialized view refreshed all the time.

Specify ON STATEMENT to indicate that a fast refresh is to occur whenever DML happens on a base table of the materialized view. This is to say, ON STATEMENT materialized view is always in sync with base table changes even before the transaction commits. If a transaction that made changes to the base tables rolls back, the corresponding changes in on statement MV are rolled back as well. This clause may increase the time taken to complete a DML, because the database performs the refresh operation as part of the DML execution. However, unlike other types of fast refreshable materialized views, ON STATEMENT MV refresh no longer requires MV log on the base tables or any extra work on MV logs in order to do fast refresh.
--------------------------------------------------

Regards
Takayuki Tsunakawa

#82tsunakawa.takay@fujitsu.com
tsunakawa.takay@fujitsu.com
In reply to: Yugo Nagata (#78)
RE: Implementing Incremental View Maintenance

From: Yugo Nagata <nagata@sraoss.co.jp>

On Mon, 23 Dec 2019 08:08:53 +0000
"tsunakawa.takay@fujitsu.com" <tsunakawa.takay@fujitsu.com> wrote:

How about unlogged tables ? I thought the point of using a temp table is to

avoid WAL overhead.

Hmm... this might be another option. However, if we use unlogged tables,
we will need to create them in a special schema similar to pg_toast
to split this from user tables. Otherwise, we need to create and drop
unlogged tables repeatedly for each session.

Maybe we can create the work tables in the same schema as the materialized view, following:

* Prefix the table name to indicate that the table is system-managed, thus alluding to the user that manually deleting the table would break something. This is like the system attribute __imv_count you are proposing.

* Describe the above in the manual. Columns of serial and bigserial data type similarly create sequences behind the scenes.

* Make the work tables depend on the materialized view by recording the dependency in pg_depend, so that Dropping the materialized view will also drop its work tables.

Regards
Takayuki Tsunakawa

#83Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: tsunakawa.takay@fujitsu.com (#81)
Re: Implementing Incremental View Maintenance

Unfortunately, it's not clear to me which of ON STATEMENT or ON COMMIT the user should choose. The benefit of ON STATEMENT is that the user does not have to create and maintain the materialized view log. But I'm not sure if and when the benefit defeats the performance overhead on DML statements. It's not disclosed whether ON STATEMENT uses triggers.

AFAIK benefit of ON STATEMENT is the transaction can see the result of
update to the base tables. With ON COMMIT, the transaction does not
see the result until the transaction commits.

Could you give your opinion on the following to better understand the proposed feature and/or Oracle's ON STATEMENT refresh mode?

* What use case does the feature fit?
If the trigger makes it difficult to use in the data ware house, does the feature target OLTP?

Well, I can see use cases of IVM in both DWH and OLTP.

For example, a user create a DWH-like data using materialized
view. After the initial data is loaded, the data is seldom updated.
However one day a user wants to change just one row to see how it
affects to the whole DWH data. IVM will help here because it could be
done in shorter time than loading whole data.

Another use case is a ticket selling system. The system shows how many
tickets remain in a real time manner. For this purpose it needs to
count the number of tickets already sold from a log table. By using
IVM, it could be accomplished in simple and effective way.

What kind of data and query would benefit most from the feature (e.g. join of a large sales table and a small product table, where the data volume and frequency of data loading is ...)?
In other words, this is about what kind of example we can recommend as a typical use case of this feature.

Here are some use cases suitable for IVM I can think of:

- Users are creating home made triggers to get data from tables. Since
IVM could eliminates some of those triggers, we could expect less
maintenance cost and bugs accidentally brought in when the triggers
were created.

- Any use case in which the cost of refreshing whole result table
(materialized view) is so expensive that it justifies the cost of
updating of base tables. See the example of use cases above.

* Do you think the benefit of ON STATEMENT (i.e. do not have to use materialized view log) outweighs the drawback of ON STATEMENT (i.g. DML overhead)?

Outweights to what?

* Do you think it's important to refresh the materialized view after every statement, or the per-statement refresh is not a requirement but simply the result of implementation?

I think it's important to refresh the materialized view after every
statement and the benefit for users are apparent because it brings
real time data refresh to users.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#84legrand legrand
legrand_legrand@hotmail.com
In reply to: Yugo Nagata (#80)
Re: Implementing Incremental View Maintenance

Yugo Nagata wrote

On Mon, 23 Dec 2019 03:41:18 -0700 (MST)
legrand legrand &lt;

legrand_legrand@

&gt; wrote:

[ ...]

I would even
prefer a common "table" shared between all sessions like GLOBAL TEMPORARY
TABLE (or something similar) as described here:
/messages/by-id/157703426606.1198.2452090605041230054.pgcf@coridan.postgresql.org

Although I have not looked into this thread, this may be help if this is
implemented. However, it would be still necessary to truncate the table
before the view maintenance because such tables always exist and can be
accessed and modified by any users.

--
Yugo Nagata &lt;

nagata@.co

&gt;

For information, in this table data is PRIVATE to each session, can be
purged on the ON COMMIT event and disappear at SESSION end.
Yes, this feature could be utile only if it's implemented. And you are rigth
some data has to be deleted
on the ON STATEMENT event (not sure if TRUNCATE is Global or Session
specific in this situation).

--
Sent from: https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

#85Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: tsunakawa.takay@fujitsu.com (#75)
Re: Implementing Incremental View Maintenance

Materialized view reminds me of the use in a data warehouse. Oracle handles the top in its Database Data Warehousing Guide, and Microsoft has just started to offer the materialized view feature in its Azure Synapse Analytics (formerly SQL Data Warehouse). AWS also has previewed Redshift's materialized view feature in re:Invent 2019. Are you targeting the data warehouse (analytics) workload?

IIUC, to put (over) simply, the data warehouse has two kind of tables:

* Facts (transaction data): e.g. sales, user activity
Large amount. INSERT only on a regular basis (ETL/ELT) or continuously (streaming)

* Dimensions (master/reference data): e.g. product, customer, time, country
Small amount. Infrequently INSERTed or UPDATEd.

The proposed trigger-based approach does not seem to be suitable for the facts, because the trigger overhead imposed on data loading may offset or exceed the time saved by incrementally refreshing the materialized views.

I think that depends on use case of the DWH. If the freshness of
materialized view tables is important for a user, then the cost of the
trigger overhead may be acceptable for the user.

Then, does the proposed feature fit the dimension tables? If the materialized view is only based on the dimension data, then the full REFRESH of the materialized view wouldn't take so long. The typical materialized view should join the fact and dimension tables. Then, the fact table will have to have the triggers, causing the data loading slowdown.

I'm saying this because I'm concerned about the trigger based overhead. As you know, Oracle uses materialized view logs to save changes and incrementally apply them later to the materialized views (REFRESH ON STATEMENT materialized views doesn't require the materialized view log, so it might use triggers.) Does any commercial grade database implement materialized view using triggers? I couldn't find relevant information regarding Azure Synapse and Redshift.

I heard that REFRESH ON STATEMENT of Oracle has been added after ON
COMMIT materialized view. So I suspect Oracle realizes that there are
needs/use case for ON STATEMENT, but I am not sure.

If our only handy option is a trigger, can we minimize the overhead by doing the view maintenance at transaction commit?

I am not sure it's worth the trouble. If it involves some form of
logging, then I think it should be used for deferred IVM first because
it has more use case than on commit IVM.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#86tsunakawa.takay@fujitsu.com
tsunakawa.takay@fujitsu.com
In reply to: Tatsuo Ishii (#85)
RE: Implementing Incremental View Maintenance

From: Tatsuo Ishii <ishii@sraoss.co.jp>

AFAIK benefit of ON STATEMENT is the transaction can see the result of
update to the base tables. With ON COMMIT, the transaction does not
see the result until the transaction commits.

Well, I can see use cases of IVM in both DWH and OLTP.

For example, a user create a DWH-like data using materialized
view. After the initial data is loaded, the data is seldom updated.
However one day a user wants to change just one row to see how it
affects to the whole DWH data. IVM will help here because it could be
done in shorter time than loading whole data.

I heard that REFRESH ON STATEMENT of Oracle has been added after ON
COMMIT materialized view. So I suspect Oracle realizes that there are
needs/use case for ON STATEMENT, but I am not sure.

Yes, it was added relatively recently in Oracle Database 12.2. As the following introduction to new features shows, the benefits are described as twofold:
1) The transaction can see the refreshed view result without committing.
2) The materialized view log is not needed.

I guess from these that the ON STATEMENT refresh mode can be useful when the user wants to experiment with some changes to see how data change could affect the analytics result, without persisting the change. I think that type of experiment is done in completely or almost static data marts where the user is allowed to modify the data freely. The ON STATEMENT refresh mode wouldn't be for the DWH that requires high-performance, regular and/or continuous data loading and maintenance based on a rigorous discipline. But I'm still not sure if this is a real-world use case...

https://docs.oracle.com/en/database/oracle/oracle-database/19/dwhsg/release-changes.html#GUID-2A2D6E3B-A3FD-47A8-82A3-1EF95AEF5993
--------------------------------------------------
ON STATEMENT refresh mode for materialized views
The ON STATEMENT refresh mode refreshes materialized views every time a DML operation is performed on any base table, without the need to commit the transaction. This mode does not require you to maintain materialized view logs on the base tables.
--------------------------------------------------

Another use case is a ticket selling system. The system shows how many
tickets remain in a real time manner. For this purpose it needs to
count the number of tickets already sold from a log table. By using
IVM, it could be accomplished in simple and effective way.

Wouldn't the app just have a table like ticket(id, name, quantity), decrement the quantity when the ticket is sold, and read the current quantity to know the remaining tickets? If many consumers try to buy tickets for a popular event, the materialized view refresh would limit the concurrency.

Here are some use cases suitable for IVM I can think of:

- Users are creating home made triggers to get data from tables. Since
IVM could eliminates some of those triggers, we could expect less
maintenance cost and bugs accidentally brought in when the triggers
were created.

- Any use case in which the cost of refreshing whole result table
(materialized view) is so expensive that it justifies the cost of
updating of base tables. See the example of use cases above.

I think we need to find a typical example of this. That should be useful to write the manual article, because it's better to caution users that the IMV is a good fit for this case and not for that case. Using real-world table names in the syntax example will also be good.

* Do you think the benefit of ON STATEMENT (i.e. do not have to use

materialized view log) outweighs the drawback of ON STATEMENT (i.g. DML
overhead)?

Outweights to what?

"outweigh" means "exceed." I meant that I'm wondering if and why users prefer ON STATEMENT's benefit despite of its additional overhead on update statements.

Bottom line: The use of triggers makes me hesitate, because I saw someone's (probably Fujii san) article that INSERTs into inheritance-and-trigger-based partitioned tables were 10 times slower than the declaration-based partitioned tables. I think I will try to find a good use case.

Regards
Takayuki Tsunakawa

#87Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: tsunakawa.takay@fujitsu.com (#86)
Re: Implementing Incremental View Maintenance

Another use case is a ticket selling system. The system shows how many
tickets remain in a real time manner. For this purpose it needs to
count the number of tickets already sold from a log table. By using
IVM, it could be accomplished in simple and effective way.

Wouldn't the app just have a table like ticket(id, name, quantity), decrement the quantity when the ticket is sold, and read the current quantity to know the remaining tickets? If many consumers try to buy tickets for a popular event, the materialized view refresh would limit the concurrency.

Yes, as long as number of sold ticks is the only important data for
the system, it could be true. However suppose the system wants to
start sort of "campaign" and the system needs to collect statistics of
counts depending on the city that each ticket buyer belongs to so that
certain offer is limited to first 100 ticket buyers in each city. In
this case IVM will give more flexible way to handle this kind of
requirements than having adhoc city counts column in a table.

I think we need to find a typical example of this. That should be useful to write the manual article, because it's better to caution users that the IMV is a good fit for this case and not for that case. Using real-world table names in the syntax example will also be good.

In general I agree. I'd try to collect good real-world examples by
myself but my experience is limited. I hope people in this community
come up with such that examples.

"outweigh" means "exceed." I meant that I'm wondering if and why users prefer ON STATEMENT's benefit despite of its additional overhead on update statements.

I already found at least one such user in the upthread if I don't
missing something.

Bottom line: The use of triggers makes me hesitate, because I saw someone's (probably Fujii san) article that INSERTs into inheritance-and-trigger-based partitioned tables were 10 times slower than the declaration-based partitioned tables. I think I will try to find a good use case.

Great. In the mean time we will try to mitigate the overhead of IVM
(triggers are just one of them).

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#88Takuma Hoshiai
hoshiai@sraoss.co.jp
In reply to: Yugo Nagata (#64)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

Attached is the latest patch (v11) to add support for Incremental Materialized View Maintenance (IVM).

Differences from the previous patch (v10) include:
- Prohibit creating matviews including mutable functions

Matviews including mutable functions (for example now(),random(), ... etc) could result in inconsistent data with the base tables.
This patch adds a check whether the requested matview definition includes SELECTs using mutable functions. If so, raise an error while creating the matview.

This issue is reported by nuko-san.
/messages/by-id/CAF3Gu1Z950HqQJzwanbeg7PmUXLc+7uZMstfnLeZM9iqDWeW9Q@mail.gmail.com

Currently other IVM's support status is:

IVM is a way to make materialized views up-to-date in which only
incremental changes are computed and applied on views rather than
recomputing the contents from scratch as REFRESH MATERIALIZED VIEW
does. IVM can update materialized views more efficiently
than recomputation when only small part of the view need updates.

There are two approaches with regard to timing of view maintenance:
immediate and deferred. In immediate maintenance, views are updated in
the same transaction where its base table is modified. In deferred
maintenance, views are updated after the transaction is committed,
for example, when the view is accessed, as a response to user command
like REFRESH, or periodically in background, and so on.

This patch implements a kind of immediate maintenance, in which
materialized views are updated immediately in AFTER triggers when a
base table is modified.

This supports views using:
- inner and outer joins including self-join
- some built-in aggregate functions (count, sum, agv, min, max)
- a part of subqueries
-- simple subqueries in FROM clause
-- EXISTS subqueries in WHERE clause
- DISTINCT and views with tuple duplicates

===
Here are major changes we made after the previous submitted patch:

* Aggregate functions are checked if they can be used in IVM
using their OID. Per comments from Alvaro Herrera.

For this purpose, Gen_fmgrtab.pl was modified so that OIDs of
aggregate functions are output to fmgroids.h.

* Some bug fixes including:

- Mistake of tab-completion of psql pointed out by nuko-san
- A bug relating rename of matview pointed out by nuko-san
- spelling errors
- etc.

* Add documentations for IVM

* Patch is splited into eleven parts to make review easier
as suggested by Amit Langote:

- 0001: Add a new syntax:
CREATE INCREMENTAL MATERIALIZED VIEW
- 0002: Add a new column relisivm to pg_class
- 0003: Change trigger.c to allow to prolong life span of tupestores
containing Transition Tables generated via AFTER trigger
- 0004: Add the basic IVM future using counting algorithm:
This supports inner joins, DISTINCT, and tuple duplicates.
- 0005: Change GEN_fmgrtab.pl to output aggregate function's OIDs
- 0006: Add aggregates support for IVM
- 0007: Add subqueries support for IVM
- 0008: Add outer joins support for IVM
- 0009: Add IVM support to psql command
- 0010: Add regression tests for IVM
- 0011: Add documentations for IVM

===
Todo:

Currently, REFRESH and pg_dump/pg_restore is not supported, but
we are working on them.

Also, TRUNCATE is not supported. When TRUNCATE command is executed
on a base table, nothing occurs on materialized views. We are
now considering another better options, like:

- Raise an error or warning when a base table is TRUNCATEed.
- Make the view non-scannable (like WITH NO DATA)
- Update the view in some ways. It would be easy for inner joins
or aggregate views, but there is some difficult with outer joins.

Best Regards,

--
Takuma Hoshiai <hoshiai@sraoss.co.jp>

Attachments:

IVM_patches_v11.tar.gzapplication/octet-stream; name=IVM_patches_v11.tar.gzDownload
�G^�<iw�F��J����cS�!<@)���h�����o^_h����5���VU x����w��x64��uu
-xd�Dx��
�V�j��d���H������R��I���X�K*9�a����p�p'�c����"�?�����*���^��W#�j�G����b.��u�����+��W�M�0�}2�����_U��l����W�F�U���A��p��
�9�T�i�B��m�9�y��z��Z[�4���k���Z����P,��b�rL��,,"�c�n9���Oy������a��k�X�(��<�W�S�~�L�pR�LL�cEce��(��r�aF�������
S�#U���x2��x��gR<��x��x�.��]���b��������w�����z�����=[��7e���U��;u��_D���uF]v?W�N����3v����L>��U�!�}�q����{�"�u��;����u�l��wOG��I�Z�Y�Gn��:Z� ��4���=����*+�8��C�p<�]Z���-�����Um8�X���ts�:a0,�����"d��{Sa�5 
#��R���T�Y���zP,Z�m�ju�D��Oh����x�x���b4�z�f�^78H�����R�0� {�����5��de<�4���
�J�p�o��0�A��
��!L��J@�����\TX��A�x(�J�����N�����/�>@��(\,���/F(���>Q�_�wl�|�X��<�Y��(i���
�r�w����vO�?�jK���^d{��c����9I����-?��Qz�"�1��_0��H/�wN4�����X���XM�]�=�������z^n��/E��������bo:���k���9�^�/�������k$Q�w���G�W�����e`�g=Tc�z;�����e�w�!����]������[���/5&pu��Y�v�p~v`����7��W����*��Y��O�	����[��|+'0��~���+|4��,F���+B&l�����i�����(��o��<��wF��P��L1]EU����K�,-�`9�������?	H�1���gP������,�����&D$�#fma�h��bg8~������NH��f��s�*�
P�]�Rg��� ��OZ��hw��q��7����I<4��yF�\��o�1��������;����
��=��h,����� G�@D>g��\p�����!#|����?�VDAN�I�H?�%��g
��v��	o���=��w�'�A2�'�}�W0���k���9���L�<���4]�?����}\V�f
.F��so����������Y��b��*����gU�-���%�Q��\q6 ���8��J����m
�'4�N�j�j�JU3*j�M):N�U_�+1\p����H���m�������A�_u����|=�ar,r�D�U���$������"F��tw���v-�]�����'����2�6	?#
����vt�=��;<��H�(^L���v��x��{V��_u���:�Nv��q���;rz�5�+0�������������������7������=�3��Gfu/�AS�#�����d���������"?���GFS�F������0��a�cXE�0DD�E$T[���P���o�4����d��-@�u�.�'�C�X��0���$�R�Q��s��Xg�_|�,L"Oi�7F�B��'
+����7z���uF������w}NX?d�G!Q>7c����1�����44!�|�m��f�&��m����_���}����Y���=�PP]���{�<<(n@��4�������i/�����H����_yg?�f{v�z'��z��>^�I���A}f �rVdv����Q�kj������zG&��Q�vb�yU���^L1�����{xZ���d~����oSmi��zK�����A����h�F�2[�m����Z���f��j�Mh��/��mkz^�WK���x�8C�Y"�L�'���X����g��.����Sa�q�>b��<r^��F����w7�~��Az�Vm;9��h&��f����W7a��e�w�.#�
������+����;S�x���$�u+o�~��%��A�&��*l�[�H�Y:!��7_!����l���aa�)T
+�nl&���fB���|�'{���51-�]����0&"?#��};%�����&gO�n�h^��S��h�pE�*��T_��h���n���/o9�������S�*��p���g���r^
�q���D�Cg�?�������{!7qP���=���;�<��|�F��<o�14�y��������������.�Y�<`�� y�@���	��>.s�-�-��X��-��� J1yC�
��R������������������[I
jr�GPIsET������Z�m���88F��4E��h�[�����@|����X��,�U�fXzq�Xt��Y����G���!n�!i�T�n��>��yv}��X�VS�l�j�.u�/yx��!����������1�h�N��u��R�UZ��E��
W"Z�dc�+�7&��7\v�a��:��&'�|
@�A�����P�������,D�����
���}x�VKW��E����>.).`"Rt�`E�xS_Ux�+�������Z�{�
GWoOG��rj2�DfO2�������:��
�d�)����RL�"�EZ=�e��_�R�a%(�����U0��=�]�����/�+�X#������jZ]M�z�Fd��W����F[��
T�
\2�����,T��T���Z�m����+��{���X���{0*�b��Gpo"���� ��%�ij����3��k9����	�h>�'b��3N���������I^��
��+'�9������ �p�'���Z�e	������N$�)��`(��5���� ������Y��U����#,@����7�F6)�*1��%���lK@h�g:�m4���4Aqs�$��B��;�}��)r��	4G��*����^
�Z~3oH�F�(�(2�X(�!��L<Il�I�~������e�����'���O�e���!��g~�Y���r_'����}����%�71����
��������R�o�<*N"��uA��6Nm7s9
��R����Q�v�ZW
z�k-�uo�0XZ���G�@�5�!�5�������S�~��������V������	^�f1o ����Is�m7�����mWIo�,�6eA�Yi��u��K	X��r�r��Ln��?��//.0z�\����O�w��1DB%C���k����1Q���g���,�-�]�<��������`�UP(_�v���I��A�����_a���`��]qp�	t��� �@��n8����������v�\~o�w'��'�{
�nL�Z����������a���0�kMFlp���x	�q�I������L���-�B�]p�e�h�v"�>�F~���g9r���m�<��a�#�1��y��K�@��Q���)^���Q`#���=^6�����$��11&������.c�^�l�<A�OQD-EO���X(���!�,�OO��2����PJ����V�y!�*�6������`��>N��t(m�~A���S����0}�N 3:#�����N0O�r&�$d9�!����'3~+�L,�u�j��������*}C��w}oZu[T�xU������JQ]z��/���Y�������_��������l7�v]��M�+MSS11Z�I�m�mc�UU�����q��W��e�?�p+O�����<��l%�r�[�P<��| ��`��b�"�a��
���eBD�y5�^�8 x�@z'Q��}�+	���p�XD�{��2��l����*}�����.�w��(n�'1kC��v�BG2��En�D�y������HN�9�m��G,�!��p@�_�T���K��'M�����k���|-<��iwA���]�����n�M|�U�^�,��{6���������| q,M��z���f[o�)�@����
Dy@C����������Q���m�km�i��%����`�����y�{.�+d��������w�\x�����A��OY���������e���{���	������ts�g��~m8�@X���'US�e���W]q�}�#�F�R �@�>��z�]O>2��?}[3A�R������g��@:G*�������b���rJ,�(c�������/���=�6 }9L�4\9��!:�X0���A�� ��g�!�1���'��mB�p�B�|g����~j{��YF����x��V5��s�x�n���C9��A�i8�*zY�3��y����Y����8���Pu��� �c�	�zX�.���U,E4JGC2s��_Kv:�F��3�9�=��xw��&@V������L��e[R�S���y�)��@�SL�����BK�����4���egi�2��}�_��������������Hq� ���.�e:-������oX�{���������W��V��kV�oa�mH�V�������G��w��J9�z�h
(�\2�=}��17�Qr��)}���� R��mOU1S�]q2m�
|����� 6)��$o��#XL��Ks�t��fQ�7^�h�����D4���?K���"�����!��[�idC��/�^�;��Jr@�bP����f�'[�9�c�T�J�>��{F���EV&��*}���s���z��m;�"p�{�O���[x��
;�u-��D�s��q���� �����$N[�WT���7+Z{�<���$e.��7�+��I��0��x���c' T���
b>
6���2#�+�9������
�b���>��}�`H�{,��5A&���/�.�����i����������h���������0���dOL.�>l2hc��0h��7}=}HT\��F����-Cc��VJ;�ZcRcR@�h�Q#�@,J�t���=�����*��=�z��S������T�����P^��q�����rPT��TZ���	L��Rw()b�����]	���>���|#:b����*��K�x���=V�d��	���S��sq��fI��,9�?���V��<�S	�V��m<�����YO>�l��7m!D��������Vv��QI��}O
<��
:k�����rB�3�='�����I�N��~�R~.�/��j*��� ���_`�n����U����'�I=)�eM�[�Q�����f~�\y&�[���2_�T��!�����g�*���*�ex������_MU�Z}���T�[��k�����.�bpE�y�e������f�a�Bo	�>��������������?�j#��|E���7iKF����n��������UH���U6O�w��z�����j���;�JT�:�>�����a����@,=%%�F��B����+������k+^Q��F����	�+ �*����n������Dt�A���#��<Pi;$��"���*�;��Zm����3��j�����e����uu:�	Q�NqT$��e�(�z��)�w�a�������D���NH[E���#n\h��B�e�������^������P=��
�7
*�AE��)�-��6��@�e�e����q�U'���&��W�E�G��j��Z�oW*h-5N`�!�h�)���0
4���+89�N�����F�a'2Lpa�������#��b���R�~�������AG�;A?L�������?����������#�p��	�Nx��J�@7�J>���zd���,�V����`r�� E�G4���<.������6
�n�g�S�L�p���V���gNo!�?&EA��|d{@MV�����!�$����X���	����BD�?���n����BM�:2Gh��O��	���"7 S&3KY��|qt�,x���U��:0�����t��x������:���M��� � .����|8
�������A<�?a��U�|%+�2�� ��aW=00Zb��W�;fr0�k4�`�l�B�7aG�V��^����v(	�%"(���"zB�>'��{;������d��)�z�>�nrt����f*W3�|������-c��3�i�����Zc{s��l���L��f���U�=^#��m�0�Os��7�k����@�Om����&#I5�GK��H,��m��*�g��sr���1�xz���A`����)�������d4rj�/���$�K�\�NC��l��0���wI�����6��#p6��������v�R�6�76k[����^����vd�#�rcC�$���M�:��'���j�[�E��0�VoQ_���r-�����o���p�6�b��:��'0�G�0�W$om�L��(����v�mFg�7�W-�����")���#��[���u0,����������0�G�	ws�a0���H����J�&|�s���|?�h�GC���H�a3p�T�wE�F�B�	��X�Vm�{�?s�����90"G����������<Q6fQ���n���bk�;���MRG�����i+�tq�=1���{��'lFh0��]�������W�j|�e1��u����0`[���������Zu��;�g�����99Rf��^O>�r5�y7y&�y����Q����������0��f�^&���Ga�g���k����e�c~���?�{���yo����T������oeC���*����g9)9<~�	��7��p\)��m78=�t���
�>��������U��O;�P_��A����b�@���%��>N�_N����~q�O����:�����
g�<����P����'3�z�K�bv��C�+�z���?-&�E�R������V�n������D���1f�x'N�B�0$B���j}�pH�E����h'%�"&dl�S�C�_��f�mV�f5,J���x����|�y$�R`����&��zJ��X���id�9lJ�&��9����[�-�^^�H�Aw����y,
���I&L����To���[
��x�J��KY]]Vn�8�N�(P���xR1#���
��������UaL1���#<�u�BvZ��'���D��{|��
b�����[�(�E�bf)�s6$��T�$1��B.�*C����W{�$�rOo��o�����M�P
�`��,
Q`�dEN
.��:�Q���~K��|����wJ)K���C=w���D��
�b2���n[t���"��������S��2��n��vn8jInZ�q���[��k���l�����������eg�B�ju�Q�a{���)��Q/�(������8PY�������%���T�f�u�QV�����j�p��?��j22�T������p���
�{�>�,�L��71�x_T�Dd��������E�*��Z?m��:M�y_�W�c�����V��xW�T��e ����}�?-�iX%4)HTFa�G�}R��b]�oq�8Q>��`8����@lP�j\tP�Xl����%�o�?ZH7�_�/�y`-�5Us?���:�a1�?/�m�S[m;v|����
�{q��j*���(b����Kv�
���As�+�?�.��`���t�U�0.<�<Jh���$��v��m4j�����eY��8�LP�-���G��I8~�:{��	L�_�c��_��h�o��;	���i�|�&��f|�t�H��������0�����/�5�(�,�Jn�/�/��Zav���iJ�E����m�B�
�:.�����$c:?��E�����%���~gC�o�"7���jv���q�h?wh�,�`�$�����8l�����j��N���%g����y�(��������\N�^Q�z������qxn�9�k�h��M�i�������1����I�O0��~4n���6��qTV����,z(�^��_�O��i�u��b�An�������3�B�K�T�P�\�R���������/��-
i�� ��l��\7��9���-t�����[�8dK�H,��L@�~����&"6����$/���:�}b�dq���'}cuG�U�����I�8^r����qDW�B��j��sJ����H����e����4�G�!���9(5��]W�A����u�_���:���;\�y��[�1�w�������k�>F��p3�%�5�}��������p|z\8J>��P
�|Au/P���� (*�]��h�[����NX��AY�����3��X�*	��1��i�it�#1i���Dk\ZpK��H%!���i���������5Z_�dd�v�Ga�C�,��A�dw )Y�H:�q������Q;G��g�n�X^DpN���
op�;2\����F��Z
a��w���KVZ���;$W�����jm{�^����L����Q��i�J<��0�/�fE�q��4RX[�2�yf���V���'���Z��p`\��Q>���@	��$���EC�%y�3d\|���UN�B��V(�r���'O�v�?�������5����]\F�����-9��s�=�~���
&���R_��.��]��crJW�1�q1�n#
e���)���_D�9ck��VW�����8�pU=N�"�U���j��d����e��g5gf�j��1_���M��������I����Y�����*Z<�H����(�L�����z�`r��<�����3�z/+��?��"�������m��5zx*���)E��2�QV}'� ��w�A��cVX2>�i������h&���?j��4R$�z���Iw5����7t���Z7�MK�l�G��&��?H�GgV���$�Q�6���x�:��({��$�G��87�6���K��
p�����Z�'�
��.>'���G=o��pHI���,��Z\��^��|8���[����0�t�w�F�{���=�5V��<Dt����~�;�")��^/���7������_[{oO���G������z<X?���s+���j��,�9��4s	���=���0lcL'Y���Wt��9�b����Z-R������R��4�H���U�X�^x�W�Z��!�+l��	YW�}�Eu�?���p��fj�Cb"�L�Z�;�����b��}�����E���2����Ot�������Ey���<���Ou�n���luQG��:�!����23(��3'\���7����;����"����3�Q*�x���^���3���I�
�b�i:@��9�L�T���4����e%�������^z���<���hLi���k����|f��\���Y�k�g�%����pL^��$����>����f?��E��`c�[����h��G~��	��%�P��@�1���:,l�=k��=��E�����q�o�9ZG$��ZZZ����eK������n���.
��Iu�����d)��u`�1P~
|xH���?����+��Q��t��s������`<�L*�)����<$%gyX$(���!^�T�L��<�W���%���f:�n�x�;�����9����I�{pN��"�����h�k{�4��s5�g$�-n@eQ�w���`�Un~������J��}��R����/��6_�45�������=:���E�Zl����'��,��zH���P~�w������,~���n�����-�<�k���=���9�����������}�wq����%�7c�����g`��O�o��z���	�M�x���xz�� �=z���0��I�%�'Lf��DlY(#������u�@��7��j�S������������)����>RXJ-���������m��sL�/3P�W�(n�^���)[���\���p<����r�v�M����Z������;��~��������g9���/2��nhu����v{��a����eV��"A�A8J���P�e�Lj��n��D%�l�O��Nyc+�6��V~�t�s4&�"rw�bg�N���(�O��1',�0x(�>qsm���upxp�������?���x�Jm��BAQ����$���u�G�tM����u��|���9.YTR����c�E1���iE:3%�|���	�w6��^>Jr��l��	#����������L�������{�$��G�9*q��Q���B�)����c��]�Wo����#y4_'zN�S�z1����Vc��Z�S�2��f@�J�2�+�8f�(/%�������C�C;��������I&N�jc6=G\#3)�9����I����)����HIc�8f"����QC�Oa;`��8�Dp����a-�{��p�2�i�6���yx�'&�|����%1>m!E��k��g�U��`�����8%��ehz`_��U�_���c�N%����x���z��rl���sV_!�����-�d�Ur�q>]r��n9�X��)�j�M`r���>7*�����i�w�����	,}k6
��K����C}u��������r&�L���^����V��f�*
��t���kW��EO��QH�8�
�F����/�;|�1	���$�����q�mXy8��j$�W����#�%lS���q�ot�Xkl�7j�$'�UtZ��`�9 z���[���L����&1c��t-�klIe]8	��a���\6��I��!�����|���Y���.�h���Oy�%��p��Q�Mk8
9�H���<���a8)��.��y��H9����������t���&�&A@�7��2ZH��Y������Av�.����������Rqc��,A�q'���	|��3�Y����� ��SN~�����'�eM�Y�$���(oThppX��G.��0���7�C_�
�m��bDX�$���i��,c�^���)�?s��h�	�o%�@V		�w��b�;����������@s
�f�Ti�2�p���x�RG�lu;hx���<\��������tU�L,����y���|���E��R�Y�)�����_�Q>�G�����m�N�&�Ft�U?Tv��!���_����	�����d�}N��������$� ��1�\�Y �ZLt�"�?����Qx����s��&CL���"-������1����`8�b
������W.E
��A�V�H������,[�P,a�'Y{E�L{�����S2T
A�f�����b������cN�?2q���n���#I
�#t>S��EQ�UL�=M�������X���/H������������,���j���5h��y���,i����#YTQj�gY����s�9�t�J��1�U�������q��?�8m%^'��qp���Z�e�`d:�T����$���?>2��]V������$��DLrj���%�qr��@���u�����)77�=<��<[��-��fv� �	���4f)]o��H�^��:������NVy6j������,\�7��^6�����S=�-���(�����&�HP$��w����q�%W���"��a���b6����p0o�sE��t�]R���E��$:�c�n��{<�(3}��Pa��1GrF1�B:�8n�8n�����#�^�'J�)rV��0��9���n�hC(>d24�1������V�F���F��,���S�r1�y�+=y["��'�naeb|hz0'�����Jv�&@���t��k
k���Z�.�*��\<��J�F?j8{�G����_O�~i�Q,I#e����7O_�J������p��{�{JWn}��83yA��Do�EH�#v�#^r�r��h�k68R"���*.�&hSQoF�f���\���b%+'�a�d��B��]������:�
}�9D�*�-�.[kZ��+n}e%���?�Oz�����u���bF�������5�����b�g��	8)DW�{������������E%f�l�Y<���C��V���3���u?kr��_�?�%��������u������@�0��r�Cl��4ma���������\���[)"��}��yZ���� �t��e�q	�f{qZ[1
&�VyZ!7��s���g��_R�������86�-N������o��kH��8�Drgj����������X�� ��h�u���<�}�|6��g�	|�G�������rv��%<.���a�m�������X��2ExE��d�Y�"����N�= ���%�sF�`�H|&N���Q��i��|N��R�n��!�S�v.J��"g���GC��B���5�H�\-9��
t�*���Y7<�P���^k6LG���KO���^�d�l�sV	��=��/[����X*�i@��P�3�<�5�~��VV�3�W��ug�Ve8|�di�8���I�����o��d<�������,�L�T�d^�A;��"�m5�VZ��z����l��9�^��[�������������&��F�*t�v7�@;�v���B�����p��G��,��bN��oahw������Ie^3������)�-���V�gOy�n`x��R�����h�V{}RA�o7O���Z����
k��H�F"%�`nexW�[��(�fX�^���z��?����s��Eeq���2�^pfsr�k7��3��,x���r�k�8�e�"�/8y}:��"�-�"��)�3�CA����
��EyL�$�7��C����=&��dK������X�;����5;��
u��"�0�����FI����lBr')�O��!��Z��@�����;���������l�x{����Z{�/O����.v]����/J���������1T�y��@��*�������}�>�BYH�����V��O�W�����e����Z!��Ek��?2:���K���s�%�f?C��*"���}�
C�1R�����N��+���Ln!,��0f���%>)��w��:=@�w��Q�Y�M�)��q�Ms�����D����1�����?�����CH4���_~y�%���U�n��#�Q��o�?�&roAH���	%"�����^&��y������6����0��T`�>�D=5�� Z�X!�+���J�����El�J�d�������#m&��y��#Y�x2a�S��WG��h���~�;x�fR�����<8�p_c�J�bg"Ut,��S3��f�n���dnr������40KNr}l�t�%��%	D�0����Ur2���p<��J�uF��
�2�v�h�����g��d��;}m�S��<<m��w\��$@��+�;�
Y�Oe
r���05������b��S�!Pj����?�>���*�����%Q�i�tO��H��T��({~G\�D7mSS��{2j���+������gq^���-�,Rd,pm����$����t�OK�R�A>�I��_��M���C�� F62Q���������Jk�+>ZJ@=������%��������"o�Y��i����^�����$�:}��n���=���Lk�F(v1b7�\�#[����2��'G���o��M������$��L�T��i�"�W'��,Q?tho@w�g������W��
��e���sS��~%_�����t�~����4����"&��$�h�A�7�.���;���V�2;��M���^��7�;��8{�(���'�:b57���f���x��(�l`V�0@C|	�����9��S���w���u.�'�6K���2�yK�KO����8�n�F��'@��_�[����k�����i?�T8%��:�5��epp����KgC�%�Z�������	%i:OC�K�w��O_�������hp�l!Y%R�R����QF�#
��D�:�)���7&)�_�Kg�������<�sOP'�.��b�.�P�
�/&c� &�1�.�C��6����7}����~�
�*{B���A�y��^�������q,��D��r�������������C���^��E�{G��I_7��~�U�k�P$��$������%�C+6R�q��y���3�����t�-Mw���*���JIt�6&Q�7����*�%LF�'���U���4��Wl����wZ���X�3�Z�5�=U9��q��������gHwg�,u�2����B�Q�Y��x��$�k!���@�.H2�����O��7fw���mK��o>h(W�����1��h'D���1�Gem�(T�/Q3����M�)�iwI�j���������|3IhC^��6�QxfY]?��f-��������C9/��I�;�ewK��`8�V]r�c��1����6�����^�O��q�����?�P���Y��3���_�z5uf�i�����R'g���#����0~��A����{Y�v��i��P}.�p��2-�If��x@�
��W!6�����O���9�uc����d�x�HI� V����
Ox�l=6T�&}�C|��'����^0�i4.�/��+��M�Tr�����Y��sJ��&���h���)�|���3�F�Hr�wr������u$wj���R),@<�"����=�q
���]��'ft�gF��2�v�w^�����W����Q��
J�������������WT���j�E������{�/���HA�<��rO^�G��D�DI����%��1�������9����E��o��''����f������8����XB
E��1�����$�*k8��eX�R�)w������9�.�\Y��3WK�u8�)�2��a�R���g�H2abx��c�p�
���<�ywAb���Cdy�DGW�[�Y���B'�p���tv��'�����:�q9.s,�yZ��Zb�I��8q3���Cx���Q�����a���u�q*R;|]p�Q8�?��A=J]��T���t����������L=g����a�����FS��i7ZZv"�?��$Y�A<��1Vfq�Pkgb�I���y�:=�U�_N�T��egr���(�?7�������Oa$d;�����$��N�LLsZ6c��/��c ?�\L���AAo��u��Fd��&���f�~8����h]��H�+zO�a:#89���F���h���`o'�����a��=Zx9
�g�w�*������/�D&9=���8n�'H��&���8]�W"4���Q!t��LQ�Xg5�n	�f��tm8����o���jE�U��i
����7�w_�T�<��:��+r�.��k��>
p�T���3��u����=cs�� ��fna�s25.%
��Z���u�j���&���V�P�������KL��;� 
��^\��$,��%��0�:���1%���t�sl]F�m/X[s +/���..k���������A���_$�qRu.'�7<��ksK�b��Z���Ws-��/��Y�J����O\�$;��,��.���T3>�L��b���	G��rR*�*btGDhoV��^�����������"���F �4�"�Z�����@#�{�Y���Ve0�aJ(����~��T��Xd2�'�2<(��>�Q��
�
����0����`;Ft.u���Jb9�����J;���D�X�{�E6�^f������Fi����1$�3��k�W��1_.�� 
A�s�r�G�+[�&A�9=�2X1e��G�F$�aB��w�W�t�w)GB���i�3E9L"-�-�Z���������S��6���S�e�i@��bA�����ZYa��6g��G�0����z��H[Y��Va���$!)�cN7Dv:����o��7�B���Rm������ ��}�
����f�-'�|������X����%�S\���'"�	���E_� 6c� �t9�[BO8���>Ij�S0*�[���Z���:�++�
�PO�9��Z>I��3���m4�h�S�����:a�����j��c5��[�j�8��j����j��$�g`&V�����.s���3�b�d[I�-�F��k�e���
Ery�CR������t��U�3����5��n���[n<�U-�����}���8z�$����i:(p[|����x4�A!Iv�@�U �a��gH?*��X����]q^���
�����\{f&����LV���!h}���.�,4�-�_GE�u�1Yb��9����
��K%�L�r#G:y��'�D�}i����
}�:���^�p�q������� q��I�Q.����D��ZW��_'�����c���z��%ut,_I[�_'j��9t�����a������dE���O��� 0�E�"������pm�0��W��������{�JI	����:\�!|D�]�`��/��En4
[���?,�8�"���6*�*}����VR�c��3sO�G��b�������Ek@�9����z0�f/����\p���.Z'o��
O�kC��*!?�X����`4���k<OEw;���I}�gX�hU����f��ha�����2���!I�Q�O�9�3A���i6�^E�K3g�uS.Dg~�o&(�2���p��iD��?PCx���G�*��V����Y&5��Q��XhOq#�.!����g���rY������Y��t���{�gs��f�P"�T�����M��pN��hk�� o���q1�a���gX�JL.DgNu��-�����&�=�E%q�S����g�}����M��E����������M+�,��>�9/Bc ��5������NZ�Q�!�O����:�����H�����]�`z+>���}�}a�����u���'��Q%_lpv=�������+'T�a���\Ac���h�I1�.�;��}I�G�]��^�������N^�RN�;������+t�+4��0%@;^�)���DG��c_�F��[ZW���d�d;��4[p�������-bd�;�����}g)-�8���
�18�>��_&���P�V8R������,����4N��V��2J����S��e�]Hm�������S��(j��ol5icv�u�}SW�g��j�F�T���\����D�������^7��z�~���n�R+��,�S[�����w��>��o��(0f����v����2Y�#sZ�3,����o���nHGWkJ���_�,��>HF���i�YJ��%��H����������:=���<�t�=5��4�����+�@.*�z���#=I�f~A�f�u��:��g�uXO�5S�{J�/2�o���o����Ja��}JA�o|M�AO�go�����=la����zd1,;����D	��9��(��^\i4��H���q5���
+��,|
����3!����2a&,+�q�
��	��37$(q;k���b�C�-��+����e�m2�e�:�ap��s�T����~�����`��:��O�ol�m{L��t�'�tGG��e�k�l��x"��B��3k�_���<{���
�_��fW��{^W59��o���cs��G���|9�H-!y���np]�p\m
J����0y���0},�Sr����5��H:a��9������l�.j*e�7�����k#J�A1�q�cL��d���\#�|-��d���~����M���!�~V�u�U�L��g9��K2��W�%a�O�p�8�g��mC���T��D��w���4%X�"E6������E<g�+�\�R�9$���o	���g?g��Z�Z�����LG+s����L[x����)2��@����h���%Bx�#��:>MwQ��x7KZ��M���}�r���z�1���
��o��)�'���
���m��uf'�K*b? ���:0���Q�S0�'@����K\���K#!-ZV��i2���c-Ou��@��W,�O����4�{�����?:<l���+"9HA�U�3��s-)u��x�C;�p�����������_��I�T�}nnm�(Fo�gN�_u��������\��u�����<���w��Ch��,w.
�h�&
�n�����������m /�vY�8����)��q������'6kVF���[����r��'��� �<#���d��Ma���{�?�n�X��
0�8�����
0�Lc
`��3,
��?�@@�`��yqr���v���x.�]���3���>�{a�����Y���O�Y�������������;T��Uf�:#�����$��I�>�"k�v���3{�i��'��"��W,�����gq0xnkJ����3��Vpq1
/t=��c���Go��~M�Y0.���\�a*��]��V��M��
����k�I�\�4�*.Wa��)���y�k�c�g4��j�������;M
8't�����G���za
�t�Q���)O�~�91D2�F)��$�Mw�k�r��>9V�����#�G�`�m|6
��Pw�Z�
���"����cuz��c6�[�K���,tGN��1�.��w���W\��D��T��*��gl%�08�F��U�Pp�R)����K�:�*��"�x����H9��u�`��8�����1�BF��p��HG,��0M���?�xG��*�D��U\e�����YEV��(i��g���o�{����+E���mBnEh�s��Ei�8_��qK�r	�����]���w��k�E�^�q~���v��/zWe�
q��f�@b�7QZ�
�\WKe�F:��&��\did��8U��#Q���|Fk��zI��]b�cA[
���-z��j@��pi���2���������)������K5N�~�	�d�\�����c���t!,���v��)g>T�m<z�IC-e��f� ���G)
'��������x2��/�'�~g�d�+��
��82����r��Xm6������6�
�/|��o��+s�[8n��<y����7��F�y7:����$�!Q����H����=}�~#QsI�a��T���?4���:'W����J�d��c��d	}��:JA'H���������Ii�	�I.�L�������#�H.Cufa����l�(�RpC+�\�owa�(����	����%��pS�U:���H�Po��Q���0='pe����5"�������D>��Gz���'����3ik�=�����7��N����$�����������������I���5-&���N]���m��%��Q�����s�S!�.��Am�E��\����h��v�fA!��b3�r��"��D����&�����2���%�bpV�2q$>��en0��ai��`�Z-��`��Xa��s�gI��}�L�k����o�ocO���f@� ����3v�FYg.#+�9i�:�����)>0����t���5�[@�j��)JT�4���}�_8���^�����R��^���8v=�1���������<?>zc�)?���
�,*$�pd�/]����b����;��������AYU�J2����/�%,�T���n������3�{�K�_�>�$o������'�_�:<9������Wqr�?M�/�k��j�V���?�:��$�����
FcL"g��S��"�X�[���2\�r&l��G�8���]t��u��g��t���	���!����d�(�B�X����B�^�~+�d["� ��-u]���`�OK%S����Y�.�9�A�W�H@&7�Y.C��I���B|0�jgq�YF>�e�Po�t����`a��7���_����q����iK�%�6�'��M+����IU���x�|}�S�������0x�\g���>��X��:9"��z	g������=�z�h	>�].��z�����_��(��r���/�$����_�n6�=]8��ew�3e	�q�r}�f����!�U�/����,�y���m�$���+b0�L&���[��z$������u�cE�Vh�?=>E~�$*$����%]�l�${�5��=��2f�z��`,�����(n2���n��
�!��(<O3/��R_�}�M�����-�B���?] �8�����7��<���f��z��U^�u�nx\�����N�P)�KLf��[o��Xc`S��+�9}z�����X�jr�v8	[�'����/�z��x�~�9��?�4��#��*mu6�A��<���nc�S���g����U�[�����c���f���������Fy[��Ox����0�X}��%��M	O%0�8�']�����9������
�s��
�z0�[�!��U�u���V�p2��8�i������_7���6[��O��{���R����
G����-8��u�@e��a�|�>�+l��D���zC���]�eq9g0D8~���0�8N�L��`L����nT*��V�<����ti�I�A|�7�6�[j�~����G��,%(�C����_['�{����[�����>����VgA�-�b;�w�!}��!���f~���rFOf��i��J��^���Y�i��6�������U*��F'��B
2���Y�m��f����R�9�6�����3���o�����<�Ab����������T'����!2Z����x���g�N�S�W*��pgw�3����i����n����O�}h���cc'~ s��w&�?���1s��k �9;�[L�q�����WY�mR]+���I�L����1������p�.:�����6��*�F�Zk��gm��C�>�mp#k��-�H���M��d�1�K�����E�`�Q�����������wj�rZ�^ml�vv������v�|���Wy��w,�%@���YA����V'������\�����q��0��U�{����;�
�j�>��)1V�^���z���������S��P���&8�M���6�%�
?�WSV4�)I\V"��hB�"�g��q~���9����Gc�n@�U��d�7��{\N�����V���2r��o�@k�_|e�<-�@qKZ��!��t����I�/l��n/�P��[Lw�jZM�C�}��6�O�������7�t��5��t���\�D/?������-�D��QF�S����J�J�=���n�9�� �)ti���Ux~r�QG����'��Y�����j&DG.������������9����6�z�^eh�!*������&	�!Foa�5��O�<L0{!p��M%������0@�'���4!���5��>�OS����^�� &���_�4_�=�6�$�������%�on�=�k�t24��u�.$7`���r���g}6Z�e~�P������|0gT/� F���eV+��t-�\>�%zQ��OB��;[�����UX��R�n�y���w�������%UP��I��!�A�r��/�����.��zy�"��s�7>�t�gG��(�xu�
r�z�q�%J:75|� n��>�u1y'h]����(�]��KK�9�{Z��W%s��������J�����V
D���fPm���y���������Z�;��v<���D+��96����5{N;*zBA����t��X*1
�D:DNz���NB�Jl�8t���}>$���i�d���/������Q�l,�?�����>�V������&���Q8��b��e��4��}�z�{u��u��������h4����������#�n�6:���X�����Ju�]
�U��lG3��6E,#Fv
��U3�"�k��g�?����HTk���6KD�Wp�'NQd7�=���1w��R�z[)/���h��yY��@&C]��sz1�33��K:��V�V�$�Li��C���
�4�<Y4u,��XC��&��F���������uI[��q\��&�eU1�����/��	�[�J����9�i^�$�� ��|��L'1u��z��IF��n�Q�_e'�N=��DD��HR��HS������h�l�;%����/7��(�����L�@���<���I�g��
G��F��y��8�Le��L�jA�xfD����DSy��d{gu@F����-�c��9���)g��G~*lA��U�!�k}���9K[1��)�����i.YoFg�be_�?�==��S��������m;3-z9�{[#����+y�Wrsp�e�D2eP�;�#��a)�Y)���6���g��^���{%3��Jnr��i��W>/����R|��N��25����D�+�R}��J�m����
-��*r�	�C�%w����H�'��m�[%eHmw��,�9VE:8F2��Y���@3��0��XF
oN/
�J}`��Q��=t8������}b?��B��l���I��a��d������=��w=��6_Q���{�?46����z�����U_<���m������s��ZO(���JcJ6(��
{�HbE�|�UT��	�9����n�[�����vn���i}XNwZ+���v�V���_j
bu�}e��g�~���4��\	\�c��MF�i���c���e�x��`Z9��Oe�}y��G�;����z���q{D�k��"��.=@o)o����za'������x����*���t�-�_D� �o������5s���-/�������S�<%�$1/�@P<��W7��+��V-�������.20<�����3Bd�,P��3�1N����7�!�@ ��a���/��>��T$������sw�f����^�7A�0P���dK�E�Of�������������x���
dB�_q����'x\�N��d�Qg�WO�OJ��o��a$��'-��b{,�	s��+���4�A���:s�7/[��F�Dk��%�L��A�i����A62%��x#�J�����	K��:���I����x��N6���F��g�������V��R9?�������3�����h��O��~�')�������`E[�$�ZN��8���E�ME{�NC�����?�������$3��o
KR'212���,��C�y~�U��W4rqt�v�%N����aB{��q�I���"�tHz�$���J��V5�|L�dhR�f�/�{��Z#]�j	-q��|���u8�th�RT��=�Ox��y����Px�P~�pz���x�D����jE��
�H����������]8�O<���l���
��N�Q�
�������SR�V7���
�����:�]�2�2������x8��(�d�C�3�PS����[[����Yu���F�O��V$���'x�������&T�G��r_FA��n��o���-t��������Nu�Z�:;k�g;�N�j�����>;��:�T��0��CU�V��c����n�������@�8��O?�������n���b�X�Ee���6��!�����^Sk��j�p2!���������Uu�Q��o���|�D��������� .�����Z?�{n�J���^��6�Q� ?����[��TH
�J!A��S��P��Y�4�NF��3���K/�`�K�����\#��K�]��5�_A�@ ���;:exi�����R!�:�����&rM�T����N�����O��ev���d6#�]1�/��u������h�A����1���D���>���	j�@�������Y��B��D�j��Xe4���
.�6��)+�w����c�FKZ��z�i�JM�v��J�O������P��r���:}�cA�W`N�^:.�b`Z��T�`���`|R�����O���d@g[�_bx�W����j�j����z��Q�#����	/-��_�U�y�����ekUU���tx��|a{���'����V��i#������1n�k%��z�j�:{����'�Y|�[���9���:����������A
�����o��_k�7�������m���w��������	k����]��;����f����Ww�+8�o���������7s��-������<P�SL������?�d^~�j�ja�K��Z��B��G�.\�OH�������;����z��`���j�,
���t����'���*��(Sb���,����(����=�������,�����\]��l�@�]�D
T�����Q]3,�)��_�D�0��N�-��bX�s"(�������NxFz?5��Ups4J�A�{M���L����dM����!�7#/���%�.�p��+q4��#���3�).*8���Xp�V���� ��b+C���~$��\p����W�K��P�|�w|�@c�n!��
-SK	�z����#`��a�2�B:'��p���%-��;�@�o�8�g����8s�#���d�[QG<��09A���Y
%vRk������%1���5�A"��4�����S'��c�X+/
t*����������.`3�� !��t8����P�B�P�1�[�����N~Z
�u�9U����o;�&+���,
v���c��q
"�fTl�#v
��	}���R1,"�0@��a�;��Rr��8�'��8;��\��O���(9[���@#^t>���L
i,7]".�����`��	)��
SgB�����{y)v)"�#����
�e�R~�#�&cI�����&���@���a���t|�8���TzBp�0�.�@{�;�l.�b���:��_�pH")�-8���� 
�}����R�r��HF�+��
�
 ;1������\��}���/�� =_�����y���8��%G?�5&.\��$�l�c�,���@O_��O�.���:9%CE�Lu�oq�97wJ7v��`��J�v7�@���6�|��������I����_!���������	�Yk����-D��6j���n�r�����3�f������B)v�4���t�&������ZG���oBw
'���`���UO��u����h*�A��-�_��#8��z���?�J�:]6�p���1G/%K0R�L�Y:qr�6�5�����(4���T��!$-Ag������������W��Zm�x�rg8��ziP��Zcs�\#=��������_���a�
e�%t�DG2)x�]{��L�'(����v���������&��:�,�,�����f���z>��$'	���^VN`�/p<���&��z�NN�Yl��4"gFo��"t���S�OC*�(^����?VBJ�BB�Y:�T�V���s_�k��}&W���?�w�c�0j����\>�����s��|)����C��QE���?��w��o�^�5T�Q?��v�����n�{�|��a���t���y���z-��z�%^H�M B�z�6�>|���^�%_�0�I���C>�� �8�p�U(���x-���xLX2�0^t�eL����X�M�3D��`�����l��k�=�9���J<oe������P��]���V��������D
zA��"$ ����/���������7�Kq��0�b����]jCu|�����:�m�ZV���7����`��(z�P��lZ���g��d@�afH��p��1���)Y�/��VN�_~�����M�2����w��<,b��1��'�g�N`�:�(�� M"�DK��rf�m�-f�pb��b���U�&�[9��yK���M]��2gQ��H�b�;�V��
4���zS����Zrc��(����\��$�~��������������q���8�H�:�s<//�Y�P��.�x��A/AF(��G��6���*�����bCh~*��x��=M����"�J�:��w���W��jYh�[M��������wc����!Q2����������OS
$�U'���f|y�f�W�J�m�����f*Kq�0+p����kr����n;����v�T���W��F���m�M��W*u!�Cl�#�J�5���i�*�_�+�����o�J����k���S��h���H�p��Prdxr�c\QYP}Z����FH���OG�_�����\b%�E�\4�#�~�\4�����O"c
n��
��N�2�t/Y��=vm��;��,[�r �v�;6�y��W� P^�)�XVK�r4�����=y�i:0�����k�����c&��"<RD�S�����������3_<�
D��I/������Ej'��<��y^�z����^��&s��G����e���O{����}�<>��KC+Gji{?��\�<OMT��������W��6o������ ������{��{)���������yju������+3o2���SY�O^�>?���7���?99�{�f���N���>m�r��<g���o0?[�<2A
��������������CS�����K>���LL���
��iL��Y��I��1E��:S�y&����M�7��"�dc��2��\L�O�0��a
����G&H�0����)��`�����<��:��<�t��km�i�T��D�c$1
��Y��>kW*;g���N0�5�v1�c!��C��o��v���e��Y�ma
r�?Ix	4����?8��y�w�:n����0���N��-@�������t�>h������r�����4a���(b�c��>�}���S�!�p�-���� �@r��-B�p���������
��la������e��$z����V��b��'wO\�cS��@�G]y��Rf�-^��b��gU�g<��3;?�?��
�f<Z�A���0_S���2���s�o�m�w�g���k[�&��3��&"��������1.qp���%�GyL\�������5��t���w%�_	�6�w�Q������E;��������^MJ���?��`���s����'o@�\���e'Y��\����usl��-�ck�X�'�[�+������G����M��d\��T=Cob�Pq��J�wq�2c�2�I� ��f�*
�h(c$<ql��N'}�|���q�pN:����-���0���#��+5}�21��7������
��L��Exfg- �V����;���#���kAitZ�`|YV� :P��#4�s����T,g*09Kel�����U���x��k�'�^7Wt�%�\�~����V���u�4?��A)Vg`���`�Vza�Fg�$�>������?����%���9��T���@%_�n>Soq�'	��V0��!�� _,�U�Or��>�1������!���+�@�}��?F�����+_np9�e�O}��i��Z��l���O��(g2����K�c!��K�#]��i�L���R�l_�,����G>�G����D���]�V��|r7m����oQ�����'wa���YXc�4{�g�M��U>�t���i����m��=m�DO�] 4�]-���V���pY=�����
t��b����Dq#����g��vLi��t,3O}d����n�ia[����d��pV�&q#k�@�B/z��;�\o�
�)Q��h����!�]�	���z�;��M����	%��_e����%�� �[15���x��i=,P���}o�vHllosb��l���bhA���2�������o�u�j"m��o?"��v�I5��;�a�?J��	s��g}�������
s���J'��:���b���2E�\��'l������~8z@���P'�B�x;�d������Vn��T
���7���*���,�}��u�p
H�27�H����aMC�Q��4�������=2'Q�������%J���;�[N�hk�zx���k��bH�x&�^�>�����S����Xpk���-��Q�u��%L���(.S)S�?����u��\G�lI�pY�;�>���(�3�8"~��v��]��y\���	��Q�E=r��n������n��o>�?��2���v�Z����z�������97t������yO|'���d8�|��)=��������t�3�ys�������K^�C���x����]�2�������7����=���k:��������w�K��[9������r���-'�rS��t�|BlBO�m����j��5����R�"f�6�����#���Q�Y�Qe��$W�i����
�1OX���d<���TJwa�`~>QO@�S'��y�b��-x���N6AU�sOo��������+V��=s�9�+�0�����D������0�Y���e�X.�5���%��JFO�M�b��h;������R�UZH��5�ya;�����WFk��hq�l�HA�
:>�B����#�Cg�1�����t�
�D'� ��$�7���c��QT�X��,Zm��Z�Z���n���#4�9��WV�����\�b1Xp�)'�	|���|�
rF	b��u����I0P�~�Ho���"%gM�����7S�'��P�����b��<�&$��I��eeu���?A��2E�e�t���^�w���������8$O��C�=�u�T���D�5Gw���Hy�����Icb����,?QODH��������)���f,7VW�08������m]��@��l�i�64��L�g����85u&h:_|&��("[A��C4#H�y�U��z�N�;�\�*	�����r�R��M��$�7+>L�����]�����_tuE,/]���k����5��l��]Y�/p�S9=������}x!������V~=��DN8,��$f�}p�t�j�����D���`H�@�)���W�4�H*�S��QMz�h25#I:-���y�keeVZ�����ae����S�uJm@�ZWZ�LF�g��x9��$|�[G��<f��rEr��6�S���X���y�������C,\����|�q�
;�Z����)�v����-���d2�
���)u#)J�,�,�����U�0ke���{�+�_�����B�<��9��%S+f��a�H)k��.J��'����l�������q�!���������
�V�@@I�\3'�J�����V��%��:�L�HV	��	Zm1����4�d�k��$W��b�B�rL.���<�:�\��v�(��R�/�m�����S��j�W�>�#hpe��1�:�dc4�)��T��asY.�~�'��������k������Fmc��D���X���m?�=j�J��c�W�%�%�F��?g���qWB�c]1�#�|�8�g�4U�PJ*��E\K�]�Co�������i���UE�����w>�����D��"t��#������������N��oo�"�y#��W����S��]=��_S_4�X�*0� �^6���D�H)��CN���_O�c�ndr�6��|�_���e�k>�	�.=tt�"t'5*��i/a4��g�J�_�9v���|0t�U_�k�*�+��U^,{5��y��8s��Lf17�N�>
U���_�"-������k�{CiU�
MSZwO���7�����J=������|t�t]t��F��T�g�tJ��)����z
�w�?���G�.AWKVu��y�wzt\�Rm\6���tK��8����\�}���q���^�7�R�Sa�����^QS�F�ugg���$.�o�9���7���*s�9s��XV����b�k�����q=Ni�;c~D��Va�,4�[�T�����i0^�[�RQ�w)�Z]����Y�u
������3JuBi�%&(����
qt�������C���=~����2��N�����W��N�������)��I6_�cx�<|~��#'X_�YDi�`���'���7d4y�z���wr�L]�I�Pp�+O�����iS��Q��K��(u�LG�"������&��r��RY�0q��Z%�������0{R>��n�Vg���_�R"k��f�P[����1�����Ig�B�k�����j��0���r����
?2�E���M�
=�n)�]}�wu�����E���4b��P8����}��@n����8X���U��>�O��s�~��L�n�����l�1��dg�������a� �I�x�nK/N��P�2�A�j�M���#���b�`��6J�%CEp��J�O{R/;�P����~��Y��])�)`'�2T]��@���(Rw����f����#�F9�Qd-|Lp�9�*��U������������r�>�Z��2�t���L����6��&H�:�m�t��A��BW�!x���Vgv�rKY:�K�^�E�������u8�FX,�&�A��L�k��"R��`��w<�BI-���d�i�M`��jZ=-���;|��7���l��`
�����w��qA9���'�R��/a���}��x�EO��I�}��A���4���!D����F�C�GRP����>"x���(��������V�VSk���z�N����)+�h�FI �CJ;d?�;NZ!�VW��>��]�Vuj	�}���{������{��%�u�9����I���-����p�C�k�<}{|xp�R���TG8e�A����|����)���H;�Q�1U��J!\X�@&}��M������339��6*�'ie%k?�&Z2�~��'�'���S���5��������s8��;%�)�����kwc3x����,��n�`��#H��#�X�+��)U���P��yTdR��N��N�7�sY�QG�-����d�07������/��^�U}����eU6#��qR�"=J}d��� ��WKe��1�V���Z{b�X*��rOo�`�m3GR���MP��zI�+���0Y%�4��XrB��`�Bj4�s�g�o��><:��d�_���\AI��s��Y\{A���I9 ����s�I������	Nc�����+K]5s�}�,V��%H��mX�:*;���[��U�
��*:���x��Y���3��Q���^���x����L���Y�aj����#���{�,�Q�F�CSFa\�<��������I��������-��������'�G���S��X+i���jQ�1�������BE�[U�'��7u�;�d����;����[�W�����j���B6989���j���x�/
�����?l<��H�����[Q��S�oR�z}��Qn4tR���q�~�Y<\�^3u��@�,������fJ��[#$�<tR��hz���^�v/���\����\nV�O�t�y���H�X��Y��O;K��s��Uh?�x��0z<-�����B��<�h!��N�A*�n����1��%-���T�!?���(���S�F�r��u���Vi>c
�/#Km��"w��Y��y����9��f�s%BT�����I����p�������h�a���g@���?u#���1���s0DS�^c|�����?]��;������y\eyb�?gX�'J�]L�SL���`�X�::VJ(�Q�a��S�L���"
������>�,���#%�8}EE����N8��	�a�S��������4O���c��F��[RZ�~���Z��j>���Z�m��1��21�$7�|�y��ubd�U�8v�Fm����b�TM�:�UQ1�R��h��Q��
1B�)�P�����k�7���P�G�*�P��(R�*��+���k?S�I��/�����7��������-����t
r>���Bt�7��K���LZ �Sj?I�D��M���c�O��I��I��4�;bSJ�����M��%gjB@����P(�-u����h.'Y�/2\���J�DUx�l;@��e8>�\�E?��g����2�g`�C�7��}����'��s�_)����q�sb�q(�{85��:���zNf	�u�a�!h%�/��F�_�F�����)����1�5�"hc�� ��\H.C���#v�#/l���?���@b��n�)�L�f8�B����V��,r�*��M/������D��+�w*��?�*��S�d|��7���������&���D��1������h�K��O������e������k�w6�n�EdA�I��3�-��zgxW`dp5�w��~�I����1��"�����I8V����E�{�����Vv��W�����*��*�y��N�7�G�_��0�h�Y����)r�S�+�$���-!�;��I�s��;=��7����@Z�9�&J�P*�OLvC�k�s�YT���S�d�b��^cReA�1!�!��0�:�-�� O���P����E;�,�Bf������5L0�%V���!n�B2�O33�$�6���v������js|o�}�o8�3�����t4�A-y ���'��=.�!�\�#��V!�G%��%��?^1vP��8�i"�w+�dT����!�T��W���
��:'����2y4S'��q*�����'��\��(��(�l9��1P ��9x~t��������(����e�o����xA�������SP�?��������8[4�>��\��H�([9$V6��c%�4\�5L� ��96�����:K	��K�IH������F�c-���O�b�Je&��	���e�r�}�'�i^�����w��vk���\W�?4��]��W�3���I1����X^��t�X�'�[>V�*B�x������j0�U:)+U�;=\�[�_��e38E9h���LW%�jjOw"�F���$�O8'����f��*Z���K�� (���0�"_�
�����V[�ME|���Z�?y3�I��p7�#����$����5TL�vK��w����c*�X��J%#��LGU�����'+{�z{��;�g�/��E���^#Z����
?��-{��������J�����@�?k��QV�J�UUE�5	f�����F4Z���������SU�<�����pb����;XG����Fs'<���3�{���2M���~������N�7�uY����exQ�M�k����9!(������A���l�L#G�����P��#�y[?-�����j�����.(�X�.i7l4�J0c��(���v[�v�-��'4���vtn�OS�Xr���X	���M!��D����Z�,Y�{�t2�a�����eA��������Z�n��zJ���}
K������\��g����������8�:��s��]��Gx����oB��Z�HdI��a�+j�u������Y3��-�r0�5���w�B=�'��$&$��#m��|�4O[?��z������zdsJs�}���v
�j����W�\�#Os���R���?nx��_��_6J���X�I������j����������������F�at�5�9��&��7ip�����Okh'�8��h��r62�b]u���2�`m�S�������LPGF%[�q*����*���������3l��~�w�7��:���-���N~�?}����a��D��<(�jY�R�?���
�h�y��:��4�:���L�Y6U]�u�����5��m�s���
FoW��������?�6�����O�m���7-`�_����^�p";:}�*{�G\Ux�2O�t�9,t��[����RyM�����ow��sg���;u;]����&�������&kO�s�r���w��6����
��������G���J�X�������a��O�,^N)��p�O�8Yv�����AO�#<{qp�\"�5# c;DK(�e��
�%�'����"xp?�c��������sY��.�������q�0�E��C+���#�kRbP����_p@�)�L��t��#����Q@\09Pp����!��:������6�r��S��S�D������H�VF!q��������6��Y;��^���M�}u�1f��8:��Ra�3����M���XU�1��$5f�
P�x�a0_^KaBU��e`J�p7��O!'�#�����*����6��+M',G�9�[��k\��8^�o"��_�����yw_�Z`������9��0$Aa<���m�d���&��+c��#UC�#�Kq�'dE%}s�@��ioD����2.U�H����a*�
�L��������#�5�%�M�f��gC�P\�(�/GR��������B�K,Mq�}�I����$�^s��$������"�
wA`���M`���I�����	���0��%�>�f����U`��h'��D�u��T�!4"��O��p�+����-3��5cw�I��q<��C3N������5�r�g�T2�*�|t����w
(k3ID���q��c�&���N�\����N�����������m���C���lS|F{�����c�n���U�V��4*�B�O���x���j����|=��!d<�Rh=��#�Q���}W��6�gm{�����6����j��j�Z�l���T����R�e/6��	r&J��r_FA��n��o��d
�l���7v;g;5 �g�`gw���������S�ht����50R'�P��U������������ur1P�����������X�J{Py7|Vx'��z1����z��GmW���k;�kU�V��V'25=V�z�w����n?���M��EO%�I������Q�S��v%���puu�J����o���������m����W{p[��*�����k\!��y��q�
�]�S����c��>N��w�.T��xu:���]�W��+�rM��/��j������e@���=�����Q\Q?c�����_7B�9�Jm��tH�\�B�9�����^��G!��Q��s����>����Y�����G� �M��T!�d��3��q���������z��#/�_��;ER�b�4uiDb��O4�`)fh��^P����,n��Y���=��\�P������_��r����e�QM~����/����N����S���
��#j�����������JpN�L&���^*(dr}�����E��j��	?����n��[�T��vgg`U���@����c$��lS�#�[��#�>������Z�a�}@8���,�,O8�G��@�sgP�'��c�@�!��Z�O����������! 4�0|������-�����<�	�����}J�t����hM��a��,����������t%(�:\�������Q5����������n�n����v�9��aj�@��AH�����q�#�`&��}�&����h��<��2O����������84rj��Q� �"%��P�r�E��SMv��K�{}��g�	����d������0R�T3������Yy:^����G�jK�����?64�%T2���NEY���66��567yk��6�`���N�=�H���K��H.=�f���:b+o�Q�Q}8�C���[C|CO�"�a{]�]=<g�����@��w7���u�++<�-,��zQ�n��7��>��������)~D��$�zck����4�H�)� Q�N��!e�����J!���8�1��z+�E(_�UvI��%\���'Yal�2�b�r'E�?�|������il�++�xq��uE3�����$<E��u�S���|���*�8�~����k�
�S��`��Nao�bA$���qfb��U�T��C������4�>e�v\4p%t�	���JY���V��'�P��)������a��%�i�u������u�?gU���j���Uo�n��.c~xb`��O�/#��sLSF�gy�Ff���������j���"r����&������<]��>��,�0�F����H)�����J^�:������*����K�g�/���&�C��a���K=�_�dI���/���-$�[��%�}Ws�����f��N��{g@D�a���
�k [�)*j�m�!�O��$$C��. 3'���N"CWT����SE�@VV�*�O���:����b��5Y���jr��a�
��X�?�"qQ�u:w;�`�v���,�}#e��{P�@�����6�R#"�D=b�p��>��u}^^��vK�&7$$�P�)��=��km�-���`���q���`Q�/u��~�)�3�4UiK��^v�{�Uk�r���67<���������������2[��N��3N�U8
F&�
G���"WH*Iz� Nj,-������zt��yl����n�I�?���j��6� �e�"�S�\_yfi+�D
!�SJ{����@�"7�pg�'EG��j�������X���iY��������$��[���}�\�-��+�0�]C���_�����I�����)6�]�B^/�5�z{������1���d��4w/�?1��(����C��|Y��g�[3���"���
q2K�.M�q[���7�2�#��Sw��'�3tbr�|�s�n��t��Bq��3��]_����3����.�zo�$�/���
$�p�6vnDM�/��9K<�`�OK"'�w�s����(^��Q],���Y�AA�L��5���F�bR������u�~h���.x�$��iM.����v����7�%��z��9������x�T�^��u�����K��.�a��[#�bn����������B��M��\�����/����z�	��_R4��mNs.�����
svg��H�j��p���\8��&6�����78���[|r��y�OK�9��b�eNL�����Z���}�Y'<����UF9�-gKE3t�,�����#2�>���
���� ��mK�XO�f�@��a����tN�p�>4o�i
 yf�N�u��?��xX�):����f��)s������U����C��
�g��!Pw�M0��N
�W���%&}
�iy����2	�C9������YC���������n�Nt�'����-D�����=����'/Q
�/�v�x�������M1>���w�;A�����n��1>�.���m#����)O�'k��JI����-kX�q��~;,�y�z��p=�[{�/O�F��	�K�@'p������P���vb��HX��_�e�h,	�Fi��l���X4M���p������.������izT��?i����.kNL�E���~�U��{~�]�hTQM	?7������\<�2/�T
�a0����o���7���WM]��aP�WkL�'A���S�������/{��}@��=�E�!�H��Dq�WHg{�3X}��a�E��dB�iMt
�*/�*~�_�����0L�����������g�e�u��k��|L����S3��V��mFZ��h�F5�@r����<�m��|J�g���7�g�JNH��dp~���B������l*O���a�(�$g~������p��R��%�7�W�lR��n�k�p�7[��&���T�=i:���:����B�$���ry�:	iB���U�����jE�&:����6�l3Pw�j"e��%RH�-�])��'7}V���U6�Mg��q1�����u����S(���j�#is<����X��I
_��*^6��Uz�.�R;c.���|���w�k0���,�3BE�sb��Vr6[������i��,�n�|����;xO}%/�[x�����[����>c+�Y��K�L����)>r��Y�i(��v�;[�6qo�>}rm�l���ll7���+
�45 j�0���%���i,��Y.�0��X���\k���u[dBH:�����)lwe���{���g��h~J�<�����J�����\���Y�\����4�7
�s�hp�|/�70g2�sf*]�������sq��9<gf���>3�h,�2���mFSm�Cc�����|��c'�7�t�`h�������J:)a]*��^�SN���6<Y���B&[,����e���N�`��b���=�m��K2����8�&/�1#i����'�v�3�q�v��uZq����~�����v��������i
S�1���p����%M�@1m���K����3DZq�S������J4pB�Wj'�@f��V���zW�o$��X�M4	*�?Cqz0�����uqp�)d���l����g���?���RFd��46����C!�Z����\tC���>�V���P�
A�9�&�G���-vvY����$����bg�?�::x�4�(��|�$�r)+�IX���dr��_f9��/.��;tt�m=;<:�w��Q<��Y?
�=��_�[z�`�1q����8$���%~�S�y%��M;Yjtbg�A�w���^7�����j�����^�YM�g#���q�������8���H����C��9�I����������~�Q��y=�j����k���A�������@O�,��}mv�fz�p������?w�L�AK���[���t��)��fylq�"@�Fs�m���H�p�0,:�X��8���@�?�m|^4���0�����-��k��Z-kV�I�K�7I��7�)�%���b\�������*
G�8$����J���i�]GG8Jz����v���k.�r�7��k.�C����]��A������x�=�G��4Fx���7!rg�I����b��~#��v�I�(OI~���Y@K��Ey]�����t������}VQ���X��j�c� ��
�!B���?����D��M���&T�~��=px`-v���UM�oFT(W��L������4�Yg�~�c��*���jI��+�
3nq
K_�~Z�*���7�qn�x��O�5��>	V��q1.�������R�}�TY�d����N7�`X,%��6�;h0�CP���7��V��=������~c9�����i�/�v)Q�m�nx�j��u�p�����>�*�Q'�N�L�(��4,�d�
 �O�A��[�j�~t����	��Iw�L�y����O$3LJ�o�vU"��.@_Um�+6A^TL��?���9������G�0Q/o>�MC��y�����cMk6Q���It�(P:�[GM��U};����n��FHyjL�Ip6��xj��j���	�l����iO�)]XO�)�8x�Bt��x���9�u0�	����o�������&�NN�HN���`�q�3��}�L�j����|Jh<����w;4E�9�����=��s��iANgf�,�H����'T�
p�(�?b����?'���L`k������O�Z}����T�����t�Q���������m��w77wj��p'�������U��~���i�f��:�����i�������t�'�^��l+��*���=%�2�������`�\�__>)P/�(��5�����F���'R���(.�P�:�>%jb���H������:�3�bn��R(p:��Dw
�9��MK�LN&�i�������^���S�s9���C�%����+���Iw�s�`=.1F����5�G�e���b/W��YNY�	�(�a��N���� 9M1���9���C`�������0�����(���;����;T��d�����/��:��/��g�\��^�
F�.1��N��9C1�W���g�aH����JvF�
Jg�S+5gIB���=��D�
B�Lci�T���1��g��J��].,EMR�����xP��,]�z��90�
�W���M�xD����O�E_�2�.��a����(DW�.�m�F�:�6	��6�����F��?��A��l*����n����:���D����@,n�R�Kl������/����?���fc���.t�vT��i�o���S�h�-=��Q�4�G���{{���U�v4h��7�j;gF4��l�,h;[�eA�����f 8$��m�En3IiV�����a�v[��vj���r�_2�Y!��[���6�=2���d�22i�P�)U����9��
s�q�,���4[�7�+����~i�	�Z����+
�elII�(;�Zcs�kNF^�7H�v������W��hl�^���Y�,�w�	]�JA.�SR
��9?�����M����'Ci������ HBz!�X�b�c��3�Rjw};��I{<a�@�=��G:J�t}��
Xd0���^�cJkr�������:N��p��.�������� m���Qi/���+;���;���<O	*�=+�z��(������F�L��oB��|���Yf��}�'�J v`�A���20�U���|pc����d9��o��Y+�.�;7����)��.�9S���[���)	��6��(�+� c��vP�gm�
����;3��5�@��v���[��%�.q��Z���sb.�O����;�1V&�<��4��a���Y��\��==�����Ry�K�#;�����5���)>��q���^Y8Z���S�^�I�'�0�We�!����pbs��e��~H���;(2@r��p�]k���r� ��5@)b��~$�</�!������
���S��	�`o:KA��@~E����� ��� ��E3��JX�r�t��IZ�����/�9�0yg�m��*�d9����,�5�LG24��2���O%������_�����q�t&��A���ZI�[�8~���O�A�:-3�>O�C�'���&g�)H�\Q�@���!�qV^7���%�/�g~������0Q"-�=��lp|	u���������9?3g<U���J�9���f
rN��Z�%Po�$\�
<8i������6��?��'�@���"��������� ��w�:q���k�d�
>�H��.���)�e������Zr3��ju��X��6�;����������� �>��)H����`�D������w�m��7�������W���Z��P����l��3��xhP];I�,y�]!�TX��9d������e��l�q�9li?0���y����jK\I�>����)�'c��� F��@�E��D����)���)e��/aZt��5���Y��VVt8E\$'l��k�I|0����a�dl�J���|������w����vj��+KJ(����b�'=��-Cr��B*����i�0���Sn?e�l_�0Nh0�a�@�C��"�0v�,���+�h�v����n���dR�a��QF�0e��os�|��h��W����"�B�n����th��<I��R	��g��qKy�6Iq�?6v]CV�D��N����������X�s����$��	&����_
�9�v9�@'_��$:��i� ��@Fp�-p���3���p��8z�'���~p������,3�,d���3�O��e%\^�L�����$�(c�����Z���X���P3
=r4�*L�M��+4���-V��B��C�/��$$_����|	�}���UM3"������C�5�3I�-�$w[��HB>��
0J��LN
�5G$����6����qLu��w�"4K�}h�v�Q�>F\��X�t���;�	�T8�/q��I�b�@<�i��QV������;�����va"Y�����$7�^t����B��!�����/E�'g^����b���B�� =U�v�r��oA���Cd]@��xg��c"�I������[��D'�D���(���9�Fl��=f��b�|U�#)h�&"��/mI��c���:�����<��K���<<�3�v�(�fi�|�G���s�o�OIb�}
�y��5Q~8�3���3�C=$��{B�D��h��S6�v��`�,��Y�2��5?�yo�G��u��2;���y������N��+��)��L@�+�����r���X���N��@�����{����t�L2��`��e'����p�q�-�?S6iX�sJ!�y�=��)���[�3�UQZs�,���W��>0�!b$C3�}����~2�*����o��o%���z3w��G1�A��,��b�x��)Tg�s���YH���9����-J_>��Io� A��� ^}kW���1F!�w �P���x�����F����Pqk:.[
%,t�4�vj�SF*������6��'�!~�7�k���_��v�7�3��0&y��J��7��^���c���vRm�c�T���++���� �����nbr�{���Mb'0hhA[�l�#M������I��u���?� +�i�C|C��)��:�b�NY�B,����|Fp ��x�;�\o	��%�-���������PM]`
��gc�&��;*O�U���u2a%��V����}� ����]V�."���}���(���-m���C��+�V�t�e�|��&��e�C'�����2Z�|�ssp��-"_�
�O.��������qXt�}uD��/A�����$�D6��{-�CY�GR�(9d!J� xy��	'�h�n�65(+k�������8��j�[z����#k��RI%FCz���,4����M��^�P�p�iI�����������u�d:�1��K�d����h��M^�c���I�y�\os��������������eI�"����$[W_�?[@�1-�����Laf�g\�	���X�������\Q'��l�0���������&��q��)���qYg'8&�y)	��f��������1Q;
9�<� �d$j_���=)9ML]3&�\fss��.����Z���y��!Z��;�#6}����IP��������@��3D����yK�J�[��c�8��@���K&���fgooh�%��6K}�#�:n��DZ���%�l�e�����u���B�$��0'��j�#r�n���h��Pg��0	�����ID�
�y�7$���{�Wr&��r�"��7e��c�@�f��U1����8�|�E��������	���G`��|����+i�������o�����Ui�,'N�kJ6�3�����E�g{K<�?s��dM����p����Wj�)���>��ma�E�c��,�J��WZ��_;���	����mX�������FL�K��DW�t�����%���I��v�����BU�U,q��������qT�_����C`���	p�a2������0<q��0�i4�n��Q�\��~7	X�nG"T�����!��3�B
����1g�S��'f�v���no����lT�O��XQ����5��G�w�t"��2/*#*�Or�6�!.�����M�|��f���-B�zms��Y�I<=��ZN&�o�F��Ao�"�#6q*���p2�����(lT|�t�Y������vo(J?���4�9��6s��UM�����"YiMz�a�T�����e}�/���"��������^{b_~�xI����vQ�?��8 8�`IX5��	�������lM�jL�{(��oh=rE����@<��*��@����@���������mOs�X���r�5�'�t�z�W"�BU���lbI
a�A�(�2���C������\�NYBM���t#@�$�������8q�$]98.+���_�1�����?�k<����0�{��J�zO��sY��/k�.E���������0
���!�#��8caA����I{T�H?����H���xp������j8P���H��NBEg�:^��N!���e���?�������b!K�;.�H(+��{xF�}��������K��h��
{���R��?��*|��
�n�����j6���p0Q� d�O�e��NFB� O(�f^/*���aZO��@sPp.�[dz=����I��sA`�<)����8v�T�;����������Lt82�zV�1wr�t�����Jnu�t����/�D;�it������������� �d��C���������}��m�GN��9}�!�pk��X+^�N���E�kY2�P�Ew��#bm�=l��������=�����8�
p��O�PM'�S���r��rNA��&yr��+Li��`�rb	�3x[	+bq���H����?�|���(g�tZbiks������
'v#��)��~4���<����5}����v~)�
��H�����In������#d��Q� �ZU�u�I�����
�I�@��r��R��\���E�����7���0����
����QD�<;82�pT�~��ah��u��5mB�,���Q��0~7����h�1;38.����$,.7�g��8=$g�%.��mlq����qaF�z��<�:�������dJ��J�T��4NRQaK?9|{oe�L��.�9t<0�����1�y��\b^�v��:������Gb�W$��{��AI��&���k����b@I�RI��V��=d��6���|hE��X�\��vzl��a��y��{AL:�q�<�/�����h��~6_QV���*x}�h~ri�D|(����� 0�q�����sp�*��c{��m~.��O��^-��-�r�����5����v�>�������o�l+��<Q|G��g1&���3n�$�%EIa��36����%���#�a�Rv��C^Gg���(c#���p����������PE�t�M_K:�K�/�F)�E@�;�h �7��A���;�!,����>�uP��e�	��.���Op�Cp}���:M	9����?{��~"]d`	��#$�0��
g����(�����+�2�����K��A��������S����$������;��������E�U�������u���F`u~�����?���hvO/����xpx����^�t?k��Y9��Uq����hq!�������}�����O,���w�0u=��jhr-�\��s�!��:
.f�]��<���LGh���`h��I�J
]�n��K�>��������}��	���������Y*[�� (��e48G7� �'%��Y�����\yh���/���]��a��e��Kb�%����K�
������6jGc'(	���4
���6���;�J��Do���h�J���	P�Z�T�M�����\���o\�`�����M��
`@F��]W\����F�h0=L�'�xy����p�����t-
R�c�������aOn�����&"FuW��u�j$��`��Z�3.�}�
�X����l�U���M����<KB�������p, ����&��yV����")}���D]�A[�F��V���Rwe�9����� �g'����pq6���l���m������d�������fYq�z�O]��/k������|u*�[��wo��������.��l��Xs�xF��P�e���
�y�uw��3����'�+,j+l�@}VMk|B��h�Icx�g�CD�]�a=�r���X�{�C�)���H���v���u��6�d���#i�%�9/s%<��'c�1����-w?����z��K=�^5�}���_�#�8��Z������s�O���a�����1�<���o����N����N���~���g"6�<���������t�1����1"y9�M�t���'�z1�D��P1�[ZSn�3'W�`D�I��R�������0���p�
�v%5���v�/%>d�'8;�(�}f������u�.�����*�N���9�9I�WY�MM��d;�p�jZ����U<r3n����M�	� ��;�0|*xF���D`�FJ��o<QE��L�.dX
g�-f�6�������w�0�c{�f�����o=P	y'����vi���T�p���
� `8^��
����8�����Pr����b�?����TV�������u�Xq"!��"a@���k);��^Yy��P���	�W�.�43a6"�
J�~f�@]7��u�kDY�0Z�d`�z�L?�0e}�jK\��CG�K��q��_�)���V�h�.&�N��63^O�X�����e��)�����J[5g�:��z&YW�*h���J��J��5�+E	s��X���x)c-�$�'����#�����q��<�{�^yH~euz�<�8b�Z�p^d�2^���'���������p�'�ze5FM��$���S��U��;�h�,{~��t^Qa�gw]n�� 3��������v��v���	y�!�~������y����W����W��-zU��g�"7������l������*�b��7TWS�o*�Hj7a�������Oz
�=R��.��<c�]�S�(��C�������J��x�\��UW���=��niO>���dyQ.����`IU�*��zJ��L��"y`<V�`��K�?�t�Ma�qN�?�kOx�y�
�!�:�D�N��O�!B���|��=G���
���,�����O�g��y��gn�W����i���NN�8AG}�g���h@�����,�~�Y/`O�������T��)�SMV*��K�D�����S��-g���'�X�����^�t�V#(C�3�_�+��p�����X�u���8�J174�'h�8]~��m9E-L�2%��`^���h�������%u{��C��*���.�j��i���1'��Q�q��++����o��v��4Iy\��8�z%����������y�S���6G�������r������~�m��4���M�"���Hz�8j��e���w�EZ�8fK�gc�Xx�����ZS�aQY���x�^�6������e��G��pZ�+G-��i��z�R����e����kas��M���V��������.a7���+�;I�D+^4���SN���X��:��?�^��LI�]�I���qt�nuK����b	��E�^��f�
��7-	���7�|�)�Z:�R�������� y)2t��W�u�I��:�2/�|�������Ik$:�T��2���M�1z:���S4S���dl��0�����������9�1P	�pS��d,��2�M�1ls��*���gG�
r�<>�m���hF�
;�)��
8
��~��el^N.�96O:I�(�����\�K�N��c3�$��f�
�ool��&yYi��c:�R���[��g��Dn@6Vq"���5g�����/���I�K��&���w���+���-_,�����x-��
�t�r�����H�o���]KH�hE��?�_GEr,DG�a�+�;��'��(�������I���.g�=�69�!�m
�SH@G"��`���
�z+�����@����P��N�!ZTVW�Gu�������3L�9�@���d�A|`
,�����n�c�y|�2O\��:-�����EbS�#��-Z767L%'N%�A9���PO������mtL���{���t�J���
�:�0�L/VEG�5�_�,�z�\���T,�����;���$rNGS%�	c�����|W\o�m%��� ��j��X;�D�u���\������8�IH��G�Y3vW��b�SE�c,�_c�i���u����kJS^5�!�XKN�>�T�q��oE��1� I��l�Nvd,1W����G	EO�7a�\�0:�W�P4Z��]U���7������"�6Ph`+OK�����e�N
J+B����f�$
����%I{(���D���81%����/�m���6Ea����&*����t��:���T8a	8������Q�J`+)� !=���P������8�*����qO�E�Dp�*>Q��^�����>����5���1��&�>���y�u��Hpnju�u���7����������y�4B$��|1|
���A��	��p���K,(#�1�
��'�f�g������Q������{��$�B^��y�������P�m.*H��8���q�2~2^�/���PS��|ZZC��e�����V��y�.�D�f@��|��q6kF)�L��M���N��p����c�yJ�!8"?a���c�	��W�d=}���|��&C;8Q�G����w��eSg�kG�>
�)eb��0��9I�V�Ub�L�D�gQg�U�����T�+l����$9�Ln��@Y�l
���p{'���x�/���+�0!NE��f��])�UI4�� ��rL���������� ����t��r�ij�-����,e�L����:�+m_v
��:�����VL���L|�����`�C����L��*f������������RK|��a�
oy�
���S��p��!I�ia���6���0K�|�����]���~�.",z{2��M������-"u��S$/2����W�a���{����>s��%s���<m�O��fq������M5\��y�9�q��!u?M��K���e�bo���?�3T���Q1����
��N�Q�
�o�o���a��Z����u:�����O�u��\��x�>��V���c@����
�Y����?�W�c�O���V�l���T�o��R�;X��&XmO�?]��(�r��z�����hj��Q���u����������f�<�no������Y�8���� ��mU�>���:la�y�~�\�ap����}���x���T�
���
�����l�����U�k�77�Zu�Z-�L�a�����E����j�S����
�S9�iU���Y(� �3�_MJ+��u)>�B�p��pW��mAQ����{y�	@G�YXi+��wU��5��Ds;���@�
�|}���cD<����'\R���-��Z��6�<.��
�|3�w��F
��5�v�F�R��������:�{3�rq�)?�SJ78�s�����{5�b�<�����e���� ������b�$7��\���R�$I+Q]���bE��cT�)x�_>�X������0\�����Uo� �W�c���</�x@Z�v����
���_�SV'���I�m?�{'M�����\����>x�������S�����3�'�<|�D=8� �2�/��W	z������*��,����#����k7�����74���`f�q[K�P�X���+X��4�����(N��t���K|m�l�S�M����K�u>�y����^�c�P��8�2�	N�N��r�LR�d��	<�+�$�j���Z/Sb�����v�Y$k�M�5��U*�C�|-��{1�����������(�����4�L��MI�����g����	L��w�
�Vqu��n{H�/����^��{�q/4&�/�1���UY9���������9�d�{�5��c����=�
����O���vYG��2SXr����{��C��P9.eH
�t�Pnd=_XA������������]���Je��qV���\-��3��D:��f�����NM���|�_�>T}�:q�r�����_�E����8:n�<T�{?�j������27�
B��4Q�d���L"!������7�Y-G ��P�������q�u��t���h��:���s�X���tW"�����xp�c���v����
z�96��+me��j���������t�s�N���Mu���i3��<a��^w�8�OR&u��A>��u����d�.����CG�f6�
24����~��c������'��m��jV����K���`\�<�����^�l>_eS0}�Q5�,�[�q�?r�������C�NJ%��N���$�|��o�#Uj�o��O�����a�����|upr��U*�iK5��~�����t�M�c��������x�ooop�r��S�Z���DjG"x�S0��^f���~�q���V��Nd^e�t�.k������{[�����V}����b��'��3��_��o20_��3�M����4\�N�	B�����3\�O��g���<B��^�03��OX�'�[�	<&�����������V%������>�q���4��:������v�ZM���77��w���������Z��[��m8;ko56���v����l���F���U�_�1
���Z���,z*BOV�MQ
�F?x$<��%9��5W]�<f��������K������(�v�ne�������?u��5��|Fr?��<Y�����x���ml�*=����qkks3�}��#*�P��\�8@��e�j2t�������0�"����H�l����[�6:!���:��#t�w���FDV�Z���eB������M�nv���*�)���9��q���yH\�h0�Q�6��(��U��M���NM��#F�yx�?Pq��l���^�m���*���*z����r]��(7���
��fy��2?3���6�Yr���V���a{�����3�Ty	�A���wbF�������j!(�if]����z{n�|ju�G�-�|t�>��V�����������^)��u2��`
N&��*�S�?��[�����
���6��
�m���(�7�2eL���$cf�pI���/g@��V��Z�6�Z�����M���o��F��:i��w�5g8:\�����u���RW8*�/���l�
�b����W?����+���A�n�D5�Q������H	��+u1���3�L�H����=;�����3�4f�.u>,��YCF��{q)y�����
��{'[FU��1�4��
�g�)n���A/e�}�8?�=?�����������*�sx���'o_K�V���i�D���O/���I.$��h�o����wK�����Q��#��;@�r\o�w����
M�I��b�f9��0�[�!������Ian�E���OH
�����i!�����S'�n#��|=�v�Y�\��z��K�����5w�k�k�{.��r����6���1e'��N����b���{3f�����[;
�����jxx��j�O��glpz+������F�A~�$�s����e�-q�'������;d�A��.3�7C���`2tP��)�@gB���:�k��0���p���r*>�X��X>hA�t�0<�'�c�3�������,���I��sv��(�(�����z��0��������d*g��	k����<G[��}��zU�C��}���U��_��0d+��zov�v���������7sq<�+O�������C��[���!J���~������^�o���>Q����h7��u��(o�_x��jKk���'��������
�yL5X7��[�=������G��nM�~��I���z�YY��	@%��F=�zP�E	������r�k�����bk��y["_�>��O�#H�t@)(��I�\-�Vo����vy;��odIP	FtN����,>s��i��F�8�����lY�����u��\%=z#��966��,��X@��H�C��U>e�Q]�l��.^AGW��,Z�5�U��j\�\1�������[�"����I&�'���|E����������n&�5����������
d*Np'���N�lc��]����5c�{Z_`���4�8��{����X	z�!q���1Y���k#��:n��=><8|��lM���|F'1��d�i����n�����t��M6x��m�7K�������AT�p�`���n���������.�_
F+��j���=�����#;S�IN�S|pG��������<�� �?�_�O!�t`�d �T`�$ ����w��S�{��;?���Rx��
x���2J��&�����W�����O��=e��)s
��'.N�����qg-�i�s�����	�����N�?�	�}�0uFt��K9\gD����&J�9>x�w���G����E��f���}�J�P�4��������M|"� O�N.���`�\>��H7�g7���E�Nh�<�\�e��c>��\�8�4��Qd�=�}�p3��
-<6���5�xt���T�<t�H=L��i(�����������Sf�gb9�b}&q���n�����K7c�%����2�nOPI�S(����~�6�;���n�U��,{9�t�9�f
�V[-��MN�����y�4^���T�I��_����q��%��/
����_����0�?�~�R��Zy�EHWG�Q������.���-�O�
>��usm����k�~�����U��|R���ng.��f���4�;��j��\��Kq�q�O����t�(_3.�-��A���3$����N=��>I5<�
A������Z^c~]�{�����~C�][x���O���s��r�tg��y����_%Fme
EKv�H;��iP
��>�1��m�d�.��������L�/%��������A,�ym:$������x�����%��KD����T.K�����:[0�Q��V���vz���n����}�8�i�S��)M>��m��Z#&Z7�j�Dk�SJ��<�f��>#\�;	�=�h�������E�R�3�*���Y4P��-F��%�������satp�]�e�aOt=�����=����>�s��8ag�B��F�H�j������<
�����#���:S!{���9����<U���wo�^��v���D*��G�y�k�=���F�7�\c��W�*i������`b|??v�!l���+��V���U3Z���w�-�_��
����w��aG�����������7|�m���i����
7�����/3�������9�)M8�ui���������i_F.�SE������T/kwj�_�i���@�C��]&���*������[�DQ>k8�n�����fa�#=�G����������gNjm��1R���}��[��������s��bam���b
���7����W����+�V��j���PS;��W;C�~<`8�,�~�I�����i]��KYs���k�T�|�����(��m6�+��we5N���s��V����L�-nhh�4F��x����������F�?���#��z����fX:���i�SO�a2�h�M�	����D����YO������S�
�T
k������}p-�|�����ZZSa�]'�W����^�v���(f&
��o�Fj����fj�sm��r�L_�
�,u�\���;b5
�F��;�Q��tY��eM�j�'����e��V��'��/p��f@:k�O|����zS�_[>�[�O]��~�g>�X���:.�/����.M�l��6NsI�>��u�7����g?Ic�W�����pn��
�m��	�os����?���"���������@���t�����]��)������R���Q���������P�{e)\�;V����v���������w���5����x�L�IN@���v��~��-�P[���������������������oY����v�������kq����|S���� �Z�;E�?���v��k���x�_�\���^��5��F���T����h����e5_�����''~��v�H7[����;���������-)��E�%]�_��iU��qt_Zg��i2�:���������'�������q
��fi����Y��j����A����$����|���
����;^������%�Sw�K)��]r9����G_N�qpx��smF��_^��b����X����Fg�5��!����6���������-��������]�������IO���D�[]�-H��'p���������sw�l �]fXZt�o;*g�r�>*��ax�sK������D�,K,�����sk��=�f�a�6U_g>�����o4�fb�q#n6�.�����/_�E���Y~����
E�|]����k�,���Q9w�n�hT��I.EwGYur4i��Ug�Q�U���c�����BZ�{�[�>��J�^���U������e�����i�����y��y���7�U�
|�&�C����X�s�I����������U���mg@Yx�/34���k�"���[/�Z�����ov��1���h�������������l�������
��
��oC�p3���.������;Y���y��>+�71uWYArD���
���w-��]������-���	?���{�{S�-$#���;)>q���-��U�6�����w�[� ow��9.����c�.��c��,{g��.�[���
�_��{Y0�F_|Y���]������['��g��K������I�	(��A=�����W��}*���S�~������T9���u�n���.O�7W2wY���8�[(f�g�����r|��w����\����rq���n9����q��.���3��m��������,~���W����W�}��o4	�����?p~��W��.p�)��8�}��o/�<���P�{_�?�~�^��*-G�tW�G��@��yd�q�|��EM_g<�
�u�_k�]��[���K_����'�&R��N�}��^P�|����Z��m(a��S������w��/�fe�u�iV��e�{	��Y�����*�J�b���,>��Wh�H��(+n#O�����/N�r�d�W2|�������[���%_H���������&Q�����}�+��G������$��]nG����4�-��P������}��������2��}	���y`�)�z�����{_�ea��|�G+�r������X|�w��w_���	��P��,����?J�����D���9g-G�tG�H�4<���d�a�
��W
���!��+������z�u=_�����������|{q�,�[s
��K
}�m�/G��r����{M��5w�:"Of������e"-�WK~��3VqN�_k4�]��%�\�%��:O���;G�;G�y4��[<� >�k0���0�����=�[_�}:�oD����������,��
&q����-`�b�����j{���2���^HL7;i�O�����5qi���?��1��f�0o�_Cx�rW�!+��e��3D���\�"�v����b��w]_[l��I�����5�z���-b������Ig����}~�-qW��3��o�;�/�	>s�_��s�[����������y��H1��<6Ls\��v~n�F�-��:*XnGd�)��
��uW�~[�5���fW0������o��w����R�7D�6��M�W��������/��!g-f�%|�������\H��p0�8�M���&�g�P�/��E�A�f7�g�K�,G�:y�PL����G�;�����J7O��T�R��������^%msV2�	u���E���j@��C0�!�i��;,�-��u��[b�H)���jp������^(���d,�d�nF3FQ}�����?�rh�Ul�7n���)���gB�lZ�k��j��)E�U~x�7��"w�x��i��_�X�O<_�^t�q4��s+�LYv���x#ZgA�8Qq���:������ah?V7��:���O���p�������~��=�,F�[���}l��������(j�������-,�3���^=S��G���z��k�e��S_������N�Hi��gUu���T�<������b}����\r��h���&�b7_P>+e@d�?�|�1# uz�a����]~_~�5�2 ���7L��6��n9��<���3��nw}��j8
;Q;����l'��'�^��C`U��2��R�{��J��[Z  Bpq1
/��vi�����*��I���Y�<>z�$���Yk���@�.a)Y@�'g�M���ra�(N�\���G�gL{���r7989=1��%����0���?��/���������K|�������N1��Uk0�����}`��uG����P�����M��z�U�>���^s0�7����4���YpA�y�R�n��}%|����q��{�F��
?�����^�"~@z9�	1�~��v��!{����^�s�O��lW��7�|���()$����<��E�S�p�
����zAZ���������!K9�PY�����{�M�/������~��[�����\��=�B��}�/��Mg]��B�BV)I��Qt�������R������$zY�Vg��������+0�y��P�:�GZ���3�L{_�Tw���Cc��������R��X�-M]�e�D��s�*���p)��}��<�(�%J_ ���������SG��~���E}���c�I�K�/���E�h��fg�6����e'gq�Q�MX;���I�`.f';���������������_��Z�����=L��=���US�����������M���`�	}vF�!�q;��8���>�P@��v{�w�*�i�:�J[��Z�1���9��������������h��G������#8��0�
�Q����V��;���9�~'��:�F��*���F����T�66
�(
������|����j}{�����_�?x��F�����xH?���*��hZ������*��Q4�nu����\{�������rT���^���b4���gEC���y>�Q�������P]���E���k�_�a8
�x{G1<���m�9�ak�I��1�8;��xt��Q���^����d|�SX����a�^4��2�D��`�b�`MH�
�0���k��A�������X@�
���h��pr��� ^����y����&���;[����J%�<���@�d'9H�l�(]���[j
8Hm�� O|��Oe#���k���]�W�A���}�A+�@S�<|�����o�G�U�y&������ptUo�1��7���U*��<��n<��W�P[��)��T�U��������p���2 ���p�+ ���X C���t�\�]���Y���0I��|2.�w��������f�"��-��H_��6�
��FyC��Y�����~f?m����53z�Z7��x���U)Xy!����-.x=��	K�F�����[J�^/�DAD7@ ��6���/g@@[�f�'�
���s��]�I�T�n��07Y�y�&�����Z	�[|��1y�O�p�&�1��t*�Q����V.��P=��T��p���w&D�`�����"�c��X�d"��	�c�G���������<���[�������f�B`Q��B�v�� ����z��:KD7�~z���I�p�;n����G<h�wf��T�
IY�7o�������Y���1�9=���z����07�,�s��=����j;����"��he$�~��������7�2x��\�����������`����#��I�X�|��c�a����*�NP�C���_�Vv�#���� ��oz�W���TR��	�� �7"J�!�pe�����-CH�.<�\������dy'���!^+��~��J��mr/������d���v�kD�z����M?����G����
z?�V�G��n�z?330M���MF���'v�4��G���/�f��a:&�X�<�]-���[�s���hl�s��tN���k�P���zbQ8���O�m�6����B���P'mXYi�R�*#�j�����i����DP��V�*�q�r��u�k:;�����gc�GL��bP�*?���O���h�O8�1q�W���a�����MY��sw��[�����k�����w����3��d�i��TL/B��RG���t��M6Pq7��%�Kq\�$��������4�z���H=���"q����"!������t���[���O"�hZ<�g��6�M�H#��xJ����l4�h:J�����g�l���4B�9��|0
��>����(��C����Qe0;��QMpQ�9>x�w���G���b$G���,�p@	�������	��I��L2#��F��F���QT���$rG5�;�������0�$���<c�4�S �.aC$���l�FS	��{�I2��P(m�E��$F�Q���16��(����&<�idw�]n�����?n�������V[-�IN��R�f����
/%*r�Q��:sgU��m':9���g���V[���L��4:��z�UM7<�
�����_�����Z�Z�owysJ�[����{���\��c	���y��3��0��&��3f��M"�����5~����zj��;�ym������R����J����M�g5j���sjn�@���=��8��uy�^��I��VD�%�m�t<,��c�1�>�3��l��,������= �A�y��WbC����s -�O�#�,=B��se?���}���#�������@��H����Y7+b|���AC���JZ�1U�<c���e�����6��������N�3�x3xe)��r�/��rz��i_n.�Ki�QY��TST�2���g:$yi!T��������@Ds+��(!?R��E�D�f�/���:yD�r���G�%��/��W{����Gz�D�TXF����v���
��-���CA��������%��������R��0�~����{�*A�w�j���C\�^/jjgS��ag��~<`H�_C�������E��;|�v�eS
������F�t���I���S���4��^��o�F���NO:�d�N$yYn���T#��L��w*=�����Z�����`�S�f�B>����KYBM����PCd�nJ����J����*���.T�p�a��Y�a��y0w���\7<��2�9W�
���.w��� ����/������������
���5�]gw�a����;���������?�GE��������?��w���q�w�����PCyw�8��KK����_p���w��_p���,q9W�-[��������/�������\pwe���kn�����w��w���q�w����+K\�es������������;�������w	pwg�s$����-<��w���q�w��������w���9n�a����;���������?�wW�����-r��{��_s����5w���������]7�m�[|��k����������k�p���vwV9�V�K������q�w��w���q�;���ry��-[�����������;�����qwe���mn�*�������5w��_s���7w�9��h|)Skw�	��[����b�H�C�o�T5�[Z�]T���n�b��i���S�%S�%S����K��K���F���{�P��V��F���T���G_F*����2�0M�������F\���,�fM�uY��7"�w8�D-
��-�;���~8��j�uC���a���������z�������G�za�����!U	_�j���������B�h��P��C��9�i��;�m�yA�E���Q��\�/C�P0��sk7C�QT��!���GS���2������O��#�\E��HG���c�4��=��|���6����h
�j����V����wLC��"�~z�b�b#�����;����bT�B���X�����Q���I��9�)c��*�a�V�P��S�^+�!�x
����gRI��Et��~���IBV�c�t��Jj���M��(XP>+e�a�)>[d8f�}�]��e��w��G�T�?}�����!��nw}��j8
;Q;��#�r@I����W'
�/���!}�4����Qxb����a�=7�N1�������I���u_��=�m���;�E����-�(��-������y��TguiT���U���+���a��Nz�BG��z�����>��C-�GwY��LB�WC�<U�j�Oh5�6���E���[0l��5���J�UrS4��nC���f�f����7���G�#����}�=��d�p2���`{�$�Xu�=���y�Ss�g�Y����v;�&��fqQ�����I����ZlJ;�?%CD�{��:y�_>S����.�`��Qt����}���E
��%7��nz}g�����m�	���9<�,Zh����-g��yFs���?z{xZ|XJ�3�
�����g�]E�7Q���qfs�i��>������f����������
JzMs]|8�(�w�b����?��������A���{�|��:��a	�Ze����
�?���E��^$�U����^����'�0�Pj=8Nf��������P����������a�U�okk��7��O|�h4j�5���ju�Q��S��o�IU��89��t9�/� �m7��7������v;��j�S�����v�n�lg[����]��w�6�:��z
��I8T�mU�>���:�f�y�~�\�ab`��������`�����n���8������zU=�����������Z��V����J����������j�G�������Sz*=���������G����>I>�.�
�������������G0����2t�s_���FU���%M����i��F����j��:�u�����T���A��V*���	�!�]\/
���\��_Dc<���)/Q�~T��[���J�|���
������yj�������w�^��l�����5<;�?�{�~���z}s�/���a7h�x�=��;~�N:��8����?�����q���
><+(�'��&2���}�G���<
��g��=�8��>��7M~9�����������v��OG�PE�i�YV���G������D���5�'/{�BG���5g�*c����\i�Y�6�����+�'�w��

�/�A~��U=�~��
/Z�T��~��~V�1M�z�|T�}~��@�D�����+�����v�3�H��2�����N�V�E���o��b����O4	��D�����0��Nt{����Ya=W���:x��N�j���;Z���p��h�y����8a��T�k�*����_eU�T��J���S�n��a���a0�����������tAj���>��`\�-�(9���z�
�����-KR�&D��M���o}cb��J������FU�>��\"�=L�gi���	�F��/z��!�<8W�0lG@����h�E���Q���xG%j�������vj|��T0���8Mm����;���&�u��2�+l�`K������
�+�"�����U3�va6�<�Q����K�]������(j�;��o�#g�����MJJ|m �!�������q�?�U�������NAw�3����������v�����*�~�S�b.(wo��>�q;����-����tX]�	��~$� ��/1�;<�&�b�m�������{�e+��cs7#:�"��}����U �R<Zb�hw'4-�3�:=5�HA�v�Xl�#c����q�A�A���oz����3kXo�Y��`�8��1n\G��~o	�y���p4�I��_<[�E��6��<>>:~�n`�g$��u���-�Qju��$���e��7���v6(��_X��=c��X�|����m�O���gNcam.��G3F�����[�T����
�����B�["��M��Y'���r�,���pY
r.wa��������e ����(�D�9U�%Y�*z�fy����p�����/�-'���|Q�0�����nC�7��Q��^<��7���>���d���g!��4GZ1=,C����R?��yk����,Rz�����0���TTQC���cw�7=��E.�Z��u���h�=Nmp
��9�O��+��o$��N	c^w����du8`rr]��$r�����^��9��At�L��M����h+���T�KM�����Ka�urA3���"��c@�~|�I�=���������|�}�%�����AK�6��I�dYWe����O��d|M�'���e���wU9����^��#��]��K�5�����rt�o���)0�����Y�v7����]�Q���{.Z8g.AT�� �L�*g�S�O��/N��v�1��i��~>8|~���a��#�g^�\������bV	��Q��!��r�42���������3���h(����{���A�������7���~@��F�F���3�y{��*)\v
+���J���Wx�|q�<�1M���������~F������}$�2�����=3VB�qf�(���	�F��?�YB����1*e�ogE���1�

I���j��7��D}���e�����3��!�whWh�G��A�^�h��u��~�(��O���M�;�uGH�$!��c���u�K���7��S�����T�g�Nug�?��6�q��%����69�����y�1~2�X���6��^�l�N\�����Q7=P�^g[}���?�����~k���*�<]�)����| ����9L��
n������A?Lt�g0�e~6�M�B���p��S���T�bA�=���S���q���~��m�n��;�G�&9Gp�����A{4�_��u������K����@�{�{1���[5����a��.��%����x��5���{��J�3���9
,;
���B[t�stS��#�5�@����4HC� ���0���X{8��G��k�f������������CaE?�Ny d������e�a�`
t�������Q�.���	�j�����<������;������Y�<28Q�(��A[/��`�,��H�t�A���#{Cq�����1!�?�������6��c�Lt�Ao�H�@�n��8���80�C`�C���S�d�G�	�
-q+�����b��'3t3"����U�+&1�����BE�@��NU�������'}s����|������c��{K�b>��#c
�� �f��8k��S�<��sv����8�����7�����A4O�����Dh�6]�����~f��}���'Q��������X������<A����Y��iNy����W�s��k ���T��:WU%kg�|����G���J���j��4���:����������/�
r(-��p����V�d86�Y�I8�*�9����������l��� f|cu\��2�t�����'���.���a6�W�F�F
)���+Q��o����=U8��/u3�r|����f��tNzF/��x����I��,O+S��8��Y��r�w/���5�$O�����A����H� e�/���=���=�.��xw�t�!��&������$'�z�8X�I��Vm���o����F�����T�f��.�U9������ry����Y�Zrv$����_3�3�v������Rk����{�Uf��&/�u1sa���DE�����f-�\=��e���f�E�'�Yco�.2��dd����������(������sQ>w���o���X�
6���mJ����p���4gp�#�'���!zy,5���5(��S��-[+�/#�t���j��a3`�p���v��|n��[d�NG��a�?�>�]��D��K(n���1�+�6K����zC��+9.��>��J�w�5d�m��R}Qe(NFj���#\�=�����t�m^f_���^P2K|N1�=>/.>P�g]0�`x�Z�2z�x�X��?}���������IZ��Fx����>�G� IN6��F��:E?D��#����	T��tmtU����uB����������2����?l�!���M����jT��L����f@����N��0z��������k%��J{�:�T1Jjg
^Tg=p������r�Z�c���_�(J�wA�������'O��?��7�
�	H���*���J�]���FG��X�T��r�����_���	s,�D ������1,�V'�������~c�����le;^^n�mjCi_�%pR�q��Yx��'�W��������EH�M��'l:�s��sVEj-�v$��V!�J$���"OC/��\���Wu�V5R����V1v�+@�(:\���U�l��F0W�w�|Gq����BU��,lY ��m����0F��]m�I�U�Q<0�t��������<�DcT��$�Q�7��B.(�kP�)Rd8�jp�~��Y�����)�(rwP��5Fqk��Yi��c���6V	���J�q�����4h����b�]��pS�=�Q���o;Z1}����33q+$���6<4t���p���\1��3nb������� 1��!��!�4e�&�zr���u�d���{������		e}�B��I%������I������~�������>���L��)�/���37^��S�P-�����~�
,
���z�=B%�Y�O�#tJ@�w1���
��.����������z�a��:������d6�)F^�VQ`�nn�����{��U���Y��8i���n�m����2�t�c`]+�;���)���Iqj���3�d��<�6Ob�5r.E���%�����@RP���hA���9h���I�?��yX><~B��usv����2M4���<06�(�%���%�h�?���'��_`��)�Wg����������M�#���X���e6W�<�x���7�<�0,M~����9�#��8+LYk�"?�a��7��m��r����_�d��jX��9�mQ^����@.�&�k�p�����Ue��#���5H	��BZ��~=x���p��-*{k�IZ����`du�[	�q��.B�R���U���� h;���u\���0�#�,�-g���Z������|�Y�fR����Y}�Z�D`����tN!�YV�[�1�>�P����Ij�)���0��Be�["<n&%B��4�|�W'9���F�����G��@xg��txr��'w��S��:�����
`?�#���b����	t�.W=t�F�0�{��UZ�����c�,�a*�7)O�>�|��
�^���r*�����;Z��i�L�>]k�{a��Cr�X�f���4���8�n
[�d����%B g��2\����U����3�{�6��n�a'�=b$�	��w<��x=�{�=��3�\U���d�
0��)���wJ�n�@L�=J|�������G@����������6��E�L�A�Qb%q�Q��(!����������C�(�W'/�����@�Q��T�r�7����r��
�PA�ic�C��Far~��-�@�BF�[mX#l_��`n�q"m�R\�A��S����4�l��	f����CjY��:b�ux'���)�S�Z���vW��7���O�&e.!�!��	����������dR��i~�
:�Ww����k�e%��:WiW@
F���'^a$���XU��w����2�2@�eerr�&��?~8��c����.�g��t��U]�E�����1��>rv�py��2 ��I�d{��>�O�/�4��+&��z������ �
\����60��t����U��MrB���{}�����XbP����U&B�E����y�/��~8>�WN���;x�80������L�b>��g�����K�_,�<�s���_<~H3~�3x`�����X����y����v�w�o���B�������������"�H�����y��oxK��Xo[��,�,����.��d���1��>���SS�8&x����:��A�o/��zw��zg��V6v[��(L�� �
�_�7��#�1�
 j2�D� \��6X���c'�)����a&ZL-�p;���N/���w����m�S1�Vi��*�u��o�F������������S����f�O�}����I��{����Q����7���c���+�A��K)mi�y�,*��H�Q��b"������7��������p�1��M�^�R��hm{T���;���M�TeB��-���?�-sA��X��15�y���=��#�R�\�N�Hf�����?������X��
vF�T� 7�/�k��?�^c�G|���}��T�Z=��/�}�������X	~S���I�bz��4�1�iLc���4�1�iLc���4�������
#89Yugo Nagata
nagata@sraoss.co.jp
In reply to: tsunakawa.takay@fujitsu.com (#82)
Re: Implementing Incremental View Maintenance

On Tue, 24 Dec 2019 07:07:35 +0000
"tsunakawa.takay@fujitsu.com" <tsunakawa.takay@fujitsu.com> wrote:

From: Yugo Nagata <nagata@sraoss.co.jp>

On Mon, 23 Dec 2019 08:08:53 +0000
"tsunakawa.takay@fujitsu.com" <tsunakawa.takay@fujitsu.com> wrote:

How about unlogged tables ? I thought the point of using a temp table is to

avoid WAL overhead.

Hmm... this might be another option. However, if we use unlogged tables,
we will need to create them in a special schema similar to pg_toast
to split this from user tables. Otherwise, we need to create and drop
unlogged tables repeatedly for each session.

Maybe we can create the work tables in the same schema as the materialized view, following:

* Prefix the table name to indicate that the table is system-managed, thus alluding to the user that manually deleting the table would break something. This is like the system attribute __imv_count you are proposing.

* Describe the above in the manual. Columns of serial and bigserial data type similarly create sequences behind the scenes.

* Make the work tables depend on the materialized view by recording the dependency in pg_depend, so that Dropping the materialized view will also drop its work tables.

Maybe it works, but instead of using special names for work tables, we can also create
a schema whose name is special and place work tables in this. This will not annoy users
with information they are not interested in when, for example, psql meta-commands like
\d are used.

Anyway, I understood it is better to avoid creating and dropping temporary tables
during view maintenance per statement.

--
Yugo Nagata <nagata@sraoss.co.jp>

#90legrand legrand
legrand_legrand@hotmail.com
In reply to: Takuma Hoshiai (#88)
Re: Implementing Incremental View Maintenance

Hello,
Thank you for this patch.

I have tried to use an other patch with yours:
"Planning counters in pg_stat_statements (using pgss_store)"
/messages/by-id/CAOBaU_Y12bn0tOdN9RMBZn29bfYYH11b2CwKO1RO7dX9fQ3aZA@mail.gmail.com

setting
shared_preload_libraries='pg_stat_statements'
pg_stat_statements.track=all
and creating the extension

When trying following syntax:

create table b1 (id integer, x numeric(10,3));
create incremental materialized view mv1 as select id, count(*),sum(x) from
b1 group by id;
insert into b1 values (1,1)

I got an ASSERT FAILURE in pg_stat_statements.c
on
Assert(query != NULL);

comming from matview.c
refresh_matview_datafill(dest_old, query, queryEnv, NULL);
or
refresh_matview_datafill(dest_new, query, queryEnv, NULL);

If this (last) NULL field was replaced by the query text, a comment or just
"n/a",
it would fix the problem.

Could this be investigated ?

Thanks in advance
Regards
PAscal

--
Sent from: https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

#91Julien Rouhaud
rjuju123@gmail.com
In reply to: legrand legrand (#90)
Re: Implementing Incremental View Maintenance

On Sat, Dec 28, 2019 at 12:42 AM legrand legrand
<legrand_legrand@hotmail.com> wrote:

Hello,
Thank you for this patch.

I have tried to use an other patch with yours:
"Planning counters in pg_stat_statements (using pgss_store)"
/messages/by-id/CAOBaU_Y12bn0tOdN9RMBZn29bfYYH11b2CwKO1RO7dX9fQ3aZA@mail.gmail.com

setting
shared_preload_libraries='pg_stat_statements'
pg_stat_statements.track=all
and creating the extension

When trying following syntax:

create table b1 (id integer, x numeric(10,3));
create incremental materialized view mv1 as select id, count(*),sum(x) from
b1 group by id;
insert into b1 values (1,1)

I got an ASSERT FAILURE in pg_stat_statements.c
on
Assert(query != NULL);

comming from matview.c
refresh_matview_datafill(dest_old, query, queryEnv, NULL);
or
refresh_matview_datafill(dest_new, query, queryEnv, NULL);

If this (last) NULL field was replaced by the query text, a comment or just
"n/a",
it would fix the problem.

Could this be investigated ?

I digged deeper into this. I found a bug in the pg_stat_statements
patch, as the new pgss_planner_hook() doesn't check for a non-zero
queryId, which I think should avoid that problem. This however indeed
raises the question on whether the query text should be provided, and
if the behavior is otherwise correct. If I understand correctly, for
now this specific query won't go through parse_analysis, thus won't
get a queryId and will be ignored in pgss_ExecutorEnd, so it'll be
entirely invisible, except with auto_explain which will only show an
orphan plan like this:

2019-12-28 12:03:29.334 CET [9399] LOG: duration: 0.180 ms plan:
HashAggregate (cost=0.04..0.06 rows=1 width=60)
Group Key: new_16385_0.id
-> Named Tuplestore Scan (cost=0.00..0.02 rows=1 width=52)

#92nuko yokohama
nuko.yokohama@gmail.com
In reply to: Yugo Nagata (#1)
Re: Implementing Incremental View Maintenance

LIMIT clause without ORDER BY should be prohibited when creating
incremental materialized views.

In SQL, the result of a LIMIT clause without ORDER BY is undefined.
If the LIMIT clause is allowed when creating an incremental materialized
view, incorrect results will be obtained when the view is updated after
updating the source table.

```
[ec2-user@ip-10-0-1-10 ivm]$ psql --version
psql (PostgreSQL) 13devel-ivm-3bf6953688153fa72dd48478a77e37cf3111a1ee
[ec2-user@ip-10-0-1-10 ivm]$ psql testdb -e -f limit-problem.sql
DROP TABLE IF EXISTS test CASCADE;
psql:limit-problem.sql:1: NOTICE: drop cascades to materialized view
test_imv
DROP TABLE
CREATE TABLE test (id int primary key, data text);
CREATE TABLE
INSERT INTO test VALUES (generate_series(1, 10), 'foo');
INSERT 0 10
CREATE INCREMENTAL MATERIALIZED VIEW test_imv AS SELECT * FROM test LIMIT 1;
SELECT 1
Materialized view "public.test_imv"
Column | Type | Collation | Nullable | Default | Storage |
Stats target | Description
---------------+---------+-----------+----------+---------+----------+--------------+-------------
id | integer | | | | plain |
|
data | text | | | | extended |
|
__ivm_count__ | bigint | | | | plain |
|
View definition:
SELECT test.id,
test.data
FROM test
LIMIT 1;
Access method: heap
Incremental view maintenance: yes

SELECT * FROM test LIMIT 1;
id | data
----+------
1 | foo
(1 row)

TABLE test_imv;
id | data
----+------
1 | foo
(1 row)

UPDATE test SET data = 'bar' WHERE id = 1;
UPDATE 1
SELECT * FROM test LIMIT 1;
id | data
----+------
2 | foo
(1 row)

TABLE test_imv;
id | data
----+------
1 | bar
(1 row)

DELETE FROM test WHERE id = 1;
DELETE 1
SELECT * FROM test LIMIT 1;
id | data
----+------
2 | foo
(1 row)

TABLE test_imv;
id | data
----+------
(0 rows)
```

ORDER BY clause is not allowed when executing CREATE INCREMENTAL
MATELIARIZED VIEW.
We propose not to allow LIMIT clauses as well.

2018年12月27日(木) 21:57 Yugo Nagata <nagata@sraoss.co.jp>:

Show quoted text

Hi,

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.
IVM is a technique to maintain materialized views which computes and
applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018
[1].
Our implementation uses row OIDs to compute deltas for materialized
views.
The basic idea is that if we have information about which rows in base
tables
are contributing to generate a certain row in a matview then we can
identify
the affected rows when a base table is updated. This is based on an idea of
Dr. Masunaga [2] who is a member of our group and inspired from ID-based
approach[3].

In our implementation, the mapping of the row OIDs of the materialized view
and the base tables are stored in "OID map". When a base relation is
modified,
AFTER trigger is executed and the delta is recorded in delta tables using
the transition table feature. The accual udpate of the matview is triggerd
by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS will
be removed since PG12, so OIDs are no longer available. Besides this, it
would
be hard to implement this since it needs many changes of executor nodes to
collect base tables's OIDs during execuing a query. Also, the cost of
maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without relying on
OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4] on incremental matview
maintenance. In this discussion, Kevin proposed to use counting algorithm
[5]
to handle projection views (using DISTNICT) properly. This algorithm need
an
additional system column, count_t, in materialized views and delta tables
of
base tables.

However, the discussion about IVM is now stoped, so we would like to
restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at
least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables,
but using
logical decoding might be better from the point of performance of base
table
modification.

If we can represent a change of UPDATE on a base table as query-like
rather than
OLD and NEW, it may be possible to update the materialized view directly
instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes on
base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on "change
propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe this as
SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify
which row
in the matview is corresponding to a row in the delta. A naive method is
matching
by using all columns in a tuple, but clearly this is unefficient. If
thematerialized
view has unique index, we can use this. Maybe, we have to force
materialized views
to have all primary key colums in their base tables. In our PoC
implementation, we
used OID to identify rows, but this will be no longer available as said
above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate (eager)
or deferred.

In eager maintenance, the materialized view is updated in the same
transaction
where the base table is updated. In deferred maintenance, this is done
after the
transaction is commited, for example, when view is accessed, as a response
to user
request, etc.

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command
to perform IVM.
I am not sure which is better as a start point, but I begin to think that
the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

In the eager maintenance approache, we have to consider a race condition
where two
different transactions change base tables simultaneously as discussed in
[4].

[1]
https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/
[2]
https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1
(Japanese only)
[3] https://dl.acm.org/citation.cfm?id=2750546
[4]
/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com
[5] https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

#93Takuma Hoshiai
hoshiai@sraoss.co.jp
In reply to: nuko yokohama (#92)
Re: Implementing Incremental View Maintenance

On Sat, 11 Jan 2020 09:27:58 +0900
nuko yokohama <nuko.yokohama@gmail.com> wrote:

LIMIT clause without ORDER BY should be prohibited when creating
incremental materialized views.

In SQL, the result of a LIMIT clause without ORDER BY is undefined.
If the LIMIT clause is allowed when creating an incremental materialized
view, incorrect results will be obtained when the view is updated after
updating the source table.

Thank you for your advice. It's just as you said.
LIMIT/OFFSET clause should is prohibited. We will add this to next patch.

Best Regards,
Takuma Hoshiai

```
[ec2-user@ip-10-0-1-10 ivm]$ psql --version
psql (PostgreSQL) 13devel-ivm-3bf6953688153fa72dd48478a77e37cf3111a1ee
[ec2-user@ip-10-0-1-10 ivm]$ psql testdb -e -f limit-problem.sql
DROP TABLE IF EXISTS test CASCADE;
psql:limit-problem.sql:1: NOTICE: drop cascades to materialized view
test_imv
DROP TABLE
CREATE TABLE test (id int primary key, data text);
CREATE TABLE
INSERT INTO test VALUES (generate_series(1, 10), 'foo');
INSERT 0 10
CREATE INCREMENTAL MATERIALIZED VIEW test_imv AS SELECT * FROM test LIMIT 1;
SELECT 1
Materialized view "public.test_imv"
Column | Type | Collation | Nullable | Default | Storage |
Stats target | Description
---------------+---------+-----------+----------+---------+----------+--------------+-------------
id | integer | | | | plain |
|
data | text | | | | extended |
|
__ivm_count__ | bigint | | | | plain |
|
View definition:
SELECT test.id,
test.data
FROM test
LIMIT 1;
Access method: heap
Incremental view maintenance: yes

SELECT * FROM test LIMIT 1;
id | data
----+------
1 | foo
(1 row)

TABLE test_imv;
id | data
----+------
1 | foo
(1 row)

UPDATE test SET data = 'bar' WHERE id = 1;
UPDATE 1
SELECT * FROM test LIMIT 1;
id | data
----+------
2 | foo
(1 row)

TABLE test_imv;
id | data
----+------
1 | bar
(1 row)

DELETE FROM test WHERE id = 1;
DELETE 1
SELECT * FROM test LIMIT 1;
id | data
----+------
2 | foo
(1 row)

TABLE test_imv;
id | data
----+------
(0 rows)
```

ORDER BY clause is not allowed when executing CREATE INCREMENTAL
MATELIARIZED VIEW.
We propose not to allow LIMIT clauses as well.

2018年12月27日(木) 21:57 Yugo Nagata <nagata@sraoss.co.jp>:

Hi,

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.
IVM is a technique to maintain materialized views which computes and
applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018
[1].
Our implementation uses row OIDs to compute deltas for materialized
views.
The basic idea is that if we have information about which rows in base
tables
are contributing to generate a certain row in a matview then we can
identify
the affected rows when a base table is updated. This is based on an idea of
Dr. Masunaga [2] who is a member of our group and inspired from ID-based
approach[3].

In our implementation, the mapping of the row OIDs of the materialized view
and the base tables are stored in "OID map". When a base relation is
modified,
AFTER trigger is executed and the delta is recorded in delta tables using
the transition table feature. The accual udpate of the matview is triggerd
by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS will
be removed since PG12, so OIDs are no longer available. Besides this, it
would
be hard to implement this since it needs many changes of executor nodes to
collect base tables's OIDs during execuing a query. Also, the cost of
maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without relying on
OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4] on incremental matview
maintenance. In this discussion, Kevin proposed to use counting algorithm
[5]
to handle projection views (using DISTNICT) properly. This algorithm need
an
additional system column, count_t, in materialized views and delta tables
of
base tables.

However, the discussion about IVM is now stoped, so we would like to
restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at
least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables,
but using
logical decoding might be better from the point of performance of base
table
modification.

If we can represent a change of UPDATE on a base table as query-like
rather than
OLD and NEW, it may be possible to update the materialized view directly
instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes on
base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on "change
propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe this as
SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify
which row
in the matview is corresponding to a row in the delta. A naive method is
matching
by using all columns in a tuple, but clearly this is unefficient. If
thematerialized
view has unique index, we can use this. Maybe, we have to force
materialized views
to have all primary key colums in their base tables. In our PoC
implementation, we
used OID to identify rows, but this will be no longer available as said
above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate (eager)
or deferred.

In eager maintenance, the materialized view is updated in the same
transaction
where the base table is updated. In deferred maintenance, this is done
after the
transaction is commited, for example, when view is accessed, as a response
to user
request, etc.

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command
to perform IVM.
I am not sure which is better as a start point, but I begin to think that
the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

In the eager maintenance approache, we have to consider a race condition
where two
different transactions change base tables simultaneously as discussed in
[4].

[1]
https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/
[2]
https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1
(Japanese only)
[3] https://dl.acm.org/citation.cfm?id=2750546
[4]
/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com
[5] https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

--
Takuma Hoshiai <hoshiai@sraoss.co.jp>

#94nuko yokohama
nuko.yokohama@gmail.com
In reply to: Yugo Nagata (#1)
Re: Implementing Incremental View Maintenance

Aggregate operation of user-defined type cannot be specified
(commit e150d964df7e3aeb768e4bae35d15764f8abd284)

A SELECT statement using the MIN() and MAX() functions can be executed on a
user-defined type column that implements the aggregate functions MIN () and
MAX ().
However, if the same SELECT statement is specified in the AS clause of
CREATE INCREMENTAL MATERIALIZED VIEW, the following error will occur.

```
SELECT MIN(data) data_min, MAX(data) data_max FROM foo;
data_min | data_max
----------+----------
1/3 | 2/3
(1 row)

CREATE INCREMENTAL MATERIALIZED VIEW foo_min_imv AS SELECT MIN(data)
data_min FROM foo;
psql:extension-agg.sql:14: ERROR: aggregate function min is not supported
CREATE INCREMENTAL MATERIALIZED VIEW foo_max_imv AS SELECT MAX(data)
data_max FROM foo;
psql:extension-agg.sql:15: ERROR: aggregate function max is not supported
```

Does query including user-defined type aggregate operation not supported by
INCREMENTAL MATERIALIZED VIEW?

An execution example is shown below.

```
[ec2-user@ip-10-0-1-10 ivm]$ cat extension-agg.sql
--
-- pg_fraction: https://github.com/nuko-yokohama/pg_fraction
--
DROP EXTENSION IF EXISTS pg_fraction CASCADE;
DROP TABLE IF EXISTS foo CASCADE;

CREATE EXTENSION IF NOT EXISTS pg_fraction;
\dx
\dT+ fraction

CREATE TABLE foo (id int, data fraction);
INSERT INTO foo (id, data) VALUES (1,'2/3'),(2,'1/3'),(3,'1/2');
SELECT MIN(data) data_min, MAX(data) data_max FROM foo;
CREATE INCREMENTAL MATERIALIZED VIEW foo_min_imv AS SELECT MIN(data)
data_min FROM foo;
CREATE INCREMENTAL MATERIALIZED VIEW foo_max_imv AS SELECT MAX(data)
data_max FROM foo;

SELECT MIN(id) id_min, MAX(id) id_max FROM foo;
CREATE INCREMENTAL MATERIALIZED VIEW foo_id_imv AS SELECT MIN(id) id_min,
MAX(id) id_max FROM foo;
```

Best regards.

2018年12月27日(木) 21:57 Yugo Nagata <nagata@sraoss.co.jp>:

Show quoted text

Hi,

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.
IVM is a technique to maintain materialized views which computes and
applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018
[1].
Our implementation uses row OIDs to compute deltas for materialized
views.
The basic idea is that if we have information about which rows in base
tables
are contributing to generate a certain row in a matview then we can
identify
the affected rows when a base table is updated. This is based on an idea of
Dr. Masunaga [2] who is a member of our group and inspired from ID-based
approach[3].

In our implementation, the mapping of the row OIDs of the materialized view
and the base tables are stored in "OID map". When a base relation is
modified,
AFTER trigger is executed and the delta is recorded in delta tables using
the transition table feature. The accual udpate of the matview is triggerd
by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS will
be removed since PG12, so OIDs are no longer available. Besides this, it
would
be hard to implement this since it needs many changes of executor nodes to
collect base tables's OIDs during execuing a query. Also, the cost of
maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without relying on
OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4] on incremental matview
maintenance. In this discussion, Kevin proposed to use counting algorithm
[5]
to handle projection views (using DISTNICT) properly. This algorithm need
an
additional system column, count_t, in materialized views and delta tables
of
base tables.

However, the discussion about IVM is now stoped, so we would like to
restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at
least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables,
but using
logical decoding might be better from the point of performance of base
table
modification.

If we can represent a change of UPDATE on a base table as query-like
rather than
OLD and NEW, it may be possible to update the materialized view directly
instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes on
base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on "change
propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe this as
SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify
which row
in the matview is corresponding to a row in the delta. A naive method is
matching
by using all columns in a tuple, but clearly this is unefficient. If
thematerialized
view has unique index, we can use this. Maybe, we have to force
materialized views
to have all primary key colums in their base tables. In our PoC
implementation, we
used OID to identify rows, but this will be no longer available as said
above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate (eager)
or deferred.

In eager maintenance, the materialized view is updated in the same
transaction
where the base table is updated. In deferred maintenance, this is done
after the
transaction is commited, for example, when view is accessed, as a response
to user
request, etc.

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command
to perform IVM.
I am not sure which is better as a start point, but I begin to think that
the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

In the eager maintenance approache, we have to consider a race condition
where two
different transactions change base tables simultaneously as discussed in
[4].

[1]
https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/
[2]
https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1
(Japanese only)
[3] https://dl.acm.org/citation.cfm?id=2750546
[4]
/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com
[5] https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

#95nuko yokohama
nuko.yokohama@gmail.com
In reply to: Yugo Nagata (#1)
Re: Implementing Incremental View Maintenance

Error occurs when updating user-defined type columns.

Create an INCREMENTAL MATERIALIZED VIEW by specifying a query that includes
user-defined type columns.
After the view is created, an error occurs when inserting into the view
source table (including the user-defined type column).
```
ERROR: operator does not exist
```

An execution example is shown below.

```
[ec2-user@ip-10-0-1-10 ivm]$ psql testdb -a -f extension-insert.sql
--
-- pg_fraction: https://github.com/nuko-yokohama/pg_fraction
--
DROP EXTENSION IF EXISTS pg_fraction CASCADE;
psql:extension-insert.sql:4: NOTICE: drop cascades to column data of table
foo
DROP EXTENSION
DROP TABLE IF EXISTS foo CASCADE;
DROP TABLE
CREATE EXTENSION IF NOT EXISTS pg_fraction;
CREATE EXTENSION
\dx
List of installed extensions
Name | Version | Schema | Description
-------------+---------+------------+------------------------------
pg_fraction | 1.0 | public | fraction data type
plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language
(2 rows)

\dT+ fraction
List of data types
Schema | Name | Internal name | Size | Elements | Owner | Access
privileges | Description
--------+----------+---------------+------+----------+----------+-------------------+-------------
public | fraction | fraction | 16 | | postgres |
|
(1 row)

CREATE TABLE foo (id int, data fraction);
CREATE TABLE
INSERT INTO foo (id, data) VALUES (1,'2/3'),(2,'1/3'),(3,'1/2');
INSERT 0 3
SELECT id, data FROM foo WHERE data >= '1/2';
id | data
----+------
1 | 2/3
3 | 1/2
(2 rows)

CREATE INCREMENTAL MATERIALIZED VIEW foo_imv AS SELECT id, data FROM foo
WHERE data >= '1/2';
SELECT 2
TABLE foo_imv;
id | data
----+------
1 | 2/3
3 | 1/2
(2 rows)

INSERT INTO foo (id, data) VALUES (4,'2/3'),(5,'2/5'),(6,'3/6'); -- error
psql:extension-insert.sql:17: ERROR: operator does not exist: fraction
pg_catalog.= fraction
LINE 1: ...(mv.id IS NULL AND diff.id IS NULL)) AND (mv.data OPERATOR(p...
^
HINT: No operator matches the given name and argument types. You might
need to add explicit type casts.
QUERY: WITH updt AS (UPDATE public.foo_imv AS mv SET __ivm_count__ =
mv.__ivm_count__ OPERATOR(pg_catalog.+) diff.__ivm_count__ FROM
pg_temp_3.pg_temp_73900 AS diff WHERE (mv.id OPERATOR(pg_catalog.=) diff.id
OR (mv.id IS NULL AND diff.id IS NULL)) AND (mv.data OPERATOR(pg_catalog.=)
diff.data OR (mv.data IS NULL AND diff.data IS NULL)) RETURNING mv.id,
mv.data) INSERT INTO public.foo_imv SELECT * FROM pg_temp_3.pg_temp_73900
AS diff WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE (mv.id
OPERATOR(pg_catalog.=) diff.id OR (mv.id IS NULL AND diff.id IS NULL)) AND
(mv.data OPERATOR(pg_catalog.=) diff.data OR (mv.data IS NULL AND diff.data
IS NULL)));
TABLE foo;
id | data
----+------
1 | 2/3
2 | 1/3
3 | 1/2
(3 rows)

TABLE foo_imv;
id | data
----+------
1 | 2/3
3 | 1/2
(2 rows)

DROP MATERIALIZED VIEW foo_imv;
DROP MATERIALIZED VIEW
INSERT INTO foo (id, data) VALUES (4,'2/3'),(5,'2/5'),(6,'3/6');
INSERT 0 3
TABLE foo;
id | data
----+------
1 | 2/3
2 | 1/3
3 | 1/2
4 | 2/3
5 | 2/5
6 | 1/2
(6 rows)

```

Best regards.

2018年12月27日(木) 21:57 Yugo Nagata <nagata@sraoss.co.jp>:

Show quoted text

Hi,

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.
IVM is a technique to maintain materialized views which computes and
applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018
[1].
Our implementation uses row OIDs to compute deltas for materialized
views.
The basic idea is that if we have information about which rows in base
tables
are contributing to generate a certain row in a matview then we can
identify
the affected rows when a base table is updated. This is based on an idea of
Dr. Masunaga [2] who is a member of our group and inspired from ID-based
approach[3].

In our implementation, the mapping of the row OIDs of the materialized view
and the base tables are stored in "OID map". When a base relation is
modified,
AFTER trigger is executed and the delta is recorded in delta tables using
the transition table feature. The accual udpate of the matview is triggerd
by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS will
be removed since PG12, so OIDs are no longer available. Besides this, it
would
be hard to implement this since it needs many changes of executor nodes to
collect base tables's OIDs during execuing a query. Also, the cost of
maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without relying on
OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4] on incremental matview
maintenance. In this discussion, Kevin proposed to use counting algorithm
[5]
to handle projection views (using DISTNICT) properly. This algorithm need
an
additional system column, count_t, in materialized views and delta tables
of
base tables.

However, the discussion about IVM is now stoped, so we would like to
restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at
least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables,
but using
logical decoding might be better from the point of performance of base
table
modification.

If we can represent a change of UPDATE on a base table as query-like
rather than
OLD and NEW, it may be possible to update the materialized view directly
instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes on
base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on "change
propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe this as
SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify
which row
in the matview is corresponding to a row in the delta. A naive method is
matching
by using all columns in a tuple, but clearly this is unefficient. If
thematerialized
view has unique index, we can use this. Maybe, we have to force
materialized views
to have all primary key colums in their base tables. In our PoC
implementation, we
used OID to identify rows, but this will be no longer available as said
above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate (eager)
or deferred.

In eager maintenance, the materialized view is updated in the same
transaction
where the base table is updated. In deferred maintenance, this is done
after the
transaction is commited, for example, when view is accessed, as a response
to user
request, etc.

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command
to perform IVM.
I am not sure which is better as a start point, but I begin to think that
the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

In the eager maintenance approache, we have to consider a race condition
where two
different transactions change base tables simultaneously as discussed in
[4].

[1]
https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/
[2]
https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1
(Japanese only)
[3] https://dl.acm.org/citation.cfm?id=2750546
[4]
/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com
[5] https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

#96Yugo NAGATA
nagata@sraoss.co.jp
In reply to: nuko yokohama (#94)
Re: Implementing Incremental View Maintenance

On Thu, 16 Jan 2020 12:59:11 +0900
nuko yokohama <nuko.yokohama@gmail.com> wrote:

Aggregate operation of user-defined type cannot be specified
(commit e150d964df7e3aeb768e4bae35d15764f8abd284)

A SELECT statement using the MIN() and MAX() functions can be executed on a
user-defined type column that implements the aggregate functions MIN () and
MAX ().
However, if the same SELECT statement is specified in the AS clause of
CREATE INCREMENTAL MATERIALIZED VIEW, the following error will occur.

```
SELECT MIN(data) data_min, MAX(data) data_max FROM foo;
data_min | data_max
----------+----------
1/3 | 2/3
(1 row)

CREATE INCREMENTAL MATERIALIZED VIEW foo_min_imv AS SELECT MIN(data)
data_min FROM foo;
psql:extension-agg.sql:14: ERROR: aggregate function min is not supported
CREATE INCREMENTAL MATERIALIZED VIEW foo_max_imv AS SELECT MAX(data)
data_max FROM foo;
psql:extension-agg.sql:15: ERROR: aggregate function max is not supported
```

Does query including user-defined type aggregate operation not supported by
INCREMENTAL MATERIALIZED VIEW?

The current implementation supports only built-in aggregate functions, so
user-defined aggregates are not supported, although it is allowed before.
This is because we can not know how user-defined aggregates behave and if
it can work safely with IVM. Min/Max on your fraction type may work well,
but it is possible that some user-defined aggregate functions named min
or max behave in totally different way than we expected.

In future, maybe it is possible support user-defined aggregates are supported
by extending pg_aggregate and adding support functions for IVM, but there is
not still a concrete plan for now.

BTW, the following error message doesn't look good because built-in min is
supported, so I will improve it.

ERROR: aggregate function min is not supported

Regards,
Yugo Nagata

An execution example is shown below.

```
[ec2-user@ip-10-0-1-10 ivm]$ cat extension-agg.sql
--
-- pg_fraction: https://github.com/nuko-yokohama/pg_fraction
--
DROP EXTENSION IF EXISTS pg_fraction CASCADE;
DROP TABLE IF EXISTS foo CASCADE;

CREATE EXTENSION IF NOT EXISTS pg_fraction;
\dx
\dT+ fraction

CREATE TABLE foo (id int, data fraction);
INSERT INTO foo (id, data) VALUES (1,'2/3'),(2,'1/3'),(3,'1/2');
SELECT MIN(data) data_min, MAX(data) data_max FROM foo;
CREATE INCREMENTAL MATERIALIZED VIEW foo_min_imv AS SELECT MIN(data)
data_min FROM foo;
CREATE INCREMENTAL MATERIALIZED VIEW foo_max_imv AS SELECT MAX(data)
data_max FROM foo;

SELECT MIN(id) id_min, MAX(id) id_max FROM foo;
CREATE INCREMENTAL MATERIALIZED VIEW foo_id_imv AS SELECT MIN(id) id_min,
MAX(id) id_max FROM foo;
```

Best regards.

2018年12月27日(木) 21:57 Yugo Nagata <nagata@sraoss.co.jp>:

Hi,

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.
IVM is a technique to maintain materialized views which computes and
applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018
[1].
Our implementation uses row OIDs to compute deltas for materialized
views.
The basic idea is that if we have information about which rows in base
tables
are contributing to generate a certain row in a matview then we can
identify
the affected rows when a base table is updated. This is based on an idea of
Dr. Masunaga [2] who is a member of our group and inspired from ID-based
approach[3].

In our implementation, the mapping of the row OIDs of the materialized view
and the base tables are stored in "OID map". When a base relation is
modified,
AFTER trigger is executed and the delta is recorded in delta tables using
the transition table feature. The accual udpate of the matview is triggerd
by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS will
be removed since PG12, so OIDs are no longer available. Besides this, it
would
be hard to implement this since it needs many changes of executor nodes to
collect base tables's OIDs during execuing a query. Also, the cost of
maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without relying on
OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4] on incremental matview
maintenance. In this discussion, Kevin proposed to use counting algorithm
[5]
to handle projection views (using DISTNICT) properly. This algorithm need
an
additional system column, count_t, in materialized views and delta tables
of
base tables.

However, the discussion about IVM is now stoped, so we would like to
restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at
least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables,
but using
logical decoding might be better from the point of performance of base
table
modification.

If we can represent a change of UPDATE on a base table as query-like
rather than
OLD and NEW, it may be possible to update the materialized view directly
instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes on
base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on "change
propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe this as
SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify
which row
in the matview is corresponding to a row in the delta. A naive method is
matching
by using all columns in a tuple, but clearly this is unefficient. If
thematerialized
view has unique index, we can use this. Maybe, we have to force
materialized views
to have all primary key colums in their base tables. In our PoC
implementation, we
used OID to identify rows, but this will be no longer available as said
above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate (eager)
or deferred.

In eager maintenance, the materialized view is updated in the same
transaction
where the base table is updated. In deferred maintenance, this is done
after the
transaction is commited, for example, when view is accessed, as a response
to user
request, etc.

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command
to perform IVM.
I am not sure which is better as a start point, but I begin to think that
the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

In the eager maintenance approache, we have to consider a race condition
where two
different transactions change base tables simultaneously as discussed in
[4].

[1]
https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/
[2]
https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1
(Japanese only)
[3] https://dl.acm.org/citation.cfm?id=2750546
[4]
/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com
[5] https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

#97Yugo NAGATA
nagata@sraoss.co.jp
In reply to: nuko yokohama (#95)
Re: Implementing Incremental View Maintenance

On Thu, 16 Jan 2020 18:50:40 +0900
nuko yokohama <nuko.yokohama@gmail.com> wrote:

Error occurs when updating user-defined type columns.

Create an INCREMENTAL MATERIALIZED VIEW by specifying a query that includes
user-defined type columns.
After the view is created, an error occurs when inserting into the view
source table (including the user-defined type column).
```
ERROR: operator does not exist

Thank you for your reporting. I think this error occurs because
pg_catalog.= is used also for user-defined types. I will fix this.

Regards,
Yugo Nagata

```

An execution example is shown below.

```
[ec2-user@ip-10-0-1-10 ivm]$ psql testdb -a -f extension-insert.sql
--
-- pg_fraction: https://github.com/nuko-yokohama/pg_fraction
--
DROP EXTENSION IF EXISTS pg_fraction CASCADE;
psql:extension-insert.sql:4: NOTICE: drop cascades to column data of table
foo
DROP EXTENSION
DROP TABLE IF EXISTS foo CASCADE;
DROP TABLE
CREATE EXTENSION IF NOT EXISTS pg_fraction;
CREATE EXTENSION
\dx
List of installed extensions
Name | Version | Schema | Description
-------------+---------+------------+------------------------------
pg_fraction | 1.0 | public | fraction data type
plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language
(2 rows)

\dT+ fraction
List of data types
Schema | Name | Internal name | Size | Elements | Owner | Access
privileges | Description
--------+----------+---------------+------+----------+----------+-------------------+-------------
public | fraction | fraction | 16 | | postgres |
|
(1 row)

CREATE TABLE foo (id int, data fraction);
CREATE TABLE
INSERT INTO foo (id, data) VALUES (1,'2/3'),(2,'1/3'),(3,'1/2');
INSERT 0 3
SELECT id, data FROM foo WHERE data >= '1/2';
id | data
----+------
1 | 2/3
3 | 1/2
(2 rows)

CREATE INCREMENTAL MATERIALIZED VIEW foo_imv AS SELECT id, data FROM foo
WHERE data >= '1/2';
SELECT 2
TABLE foo_imv;
id | data
----+------
1 | 2/3
3 | 1/2
(2 rows)

INSERT INTO foo (id, data) VALUES (4,'2/3'),(5,'2/5'),(6,'3/6'); -- error
psql:extension-insert.sql:17: ERROR: operator does not exist: fraction
pg_catalog.= fraction
LINE 1: ...(mv.id IS NULL AND diff.id IS NULL)) AND (mv.data OPERATOR(p...
^
HINT: No operator matches the given name and argument types. You might
need to add explicit type casts.
QUERY: WITH updt AS (UPDATE public.foo_imv AS mv SET __ivm_count__ =
mv.__ivm_count__ OPERATOR(pg_catalog.+) diff.__ivm_count__ FROM
pg_temp_3.pg_temp_73900 AS diff WHERE (mv.id OPERATOR(pg_catalog.=) diff.id
OR (mv.id IS NULL AND diff.id IS NULL)) AND (mv.data OPERATOR(pg_catalog.=)
diff.data OR (mv.data IS NULL AND diff.data IS NULL)) RETURNING mv.id,
mv.data) INSERT INTO public.foo_imv SELECT * FROM pg_temp_3.pg_temp_73900
AS diff WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE (mv.id
OPERATOR(pg_catalog.=) diff.id OR (mv.id IS NULL AND diff.id IS NULL)) AND
(mv.data OPERATOR(pg_catalog.=) diff.data OR (mv.data IS NULL AND diff.data
IS NULL)));
TABLE foo;
id | data
----+------
1 | 2/3
2 | 1/3
3 | 1/2
(3 rows)

TABLE foo_imv;
id | data
----+------
1 | 2/3
3 | 1/2
(2 rows)

DROP MATERIALIZED VIEW foo_imv;
DROP MATERIALIZED VIEW
INSERT INTO foo (id, data) VALUES (4,'2/3'),(5,'2/5'),(6,'3/6');
INSERT 0 3
TABLE foo;
id | data
----+------
1 | 2/3
2 | 1/3
3 | 1/2
4 | 2/3
5 | 2/5
6 | 1/2
(6 rows)

```

Best regards.

2018年12月27日(木) 21:57 Yugo Nagata <nagata@sraoss.co.jp>:

Hi,

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.
IVM is a technique to maintain materialized views which computes and
applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018
[1].
Our implementation uses row OIDs to compute deltas for materialized
views.
The basic idea is that if we have information about which rows in base
tables
are contributing to generate a certain row in a matview then we can
identify
the affected rows when a base table is updated. This is based on an idea of
Dr. Masunaga [2] who is a member of our group and inspired from ID-based
approach[3].

In our implementation, the mapping of the row OIDs of the materialized view
and the base tables are stored in "OID map". When a base relation is
modified,
AFTER trigger is executed and the delta is recorded in delta tables using
the transition table feature. The accual udpate of the matview is triggerd
by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS will
be removed since PG12, so OIDs are no longer available. Besides this, it
would
be hard to implement this since it needs many changes of executor nodes to
collect base tables's OIDs during execuing a query. Also, the cost of
maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without relying on
OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4] on incremental matview
maintenance. In this discussion, Kevin proposed to use counting algorithm
[5]
to handle projection views (using DISTNICT) properly. This algorithm need
an
additional system column, count_t, in materialized views and delta tables
of
base tables.

However, the discussion about IVM is now stoped, so we would like to
restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at
least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables,
but using
logical decoding might be better from the point of performance of base
table
modification.

If we can represent a change of UPDATE on a base table as query-like
rather than
OLD and NEW, it may be possible to update the materialized view directly
instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes on
base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on "change
propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe this as
SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify
which row
in the matview is corresponding to a row in the delta. A naive method is
matching
by using all columns in a tuple, but clearly this is unefficient. If
thematerialized
view has unique index, we can use this. Maybe, we have to force
materialized views
to have all primary key colums in their base tables. In our PoC
implementation, we
used OID to identify rows, but this will be no longer available as said
above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate (eager)
or deferred.

In eager maintenance, the materialized view is updated in the same
transaction
where the base table is updated. In deferred maintenance, this is done
after the
transaction is commited, for example, when view is accessed, as a response
to user
request, etc.

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command
to perform IVM.
I am not sure which is better as a start point, but I begin to think that
the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

In the eager maintenance approache, we have to consider a race condition
where two
different transactions change base tables simultaneously as discussed in
[4].

[1]
https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/
[2]
https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1
(Japanese only)
[3] https://dl.acm.org/citation.cfm?id=2750546
[4]
/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com
[5] https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

#98legrand legrand
legrand_legrand@hotmail.com
In reply to: Takuma Hoshiai (#88)
Re: Implementing Incremental View Maintenance

Hello,

It seems that patch v11 doesn't apply any more.
Problem with "scanRTEForColumn" maybe because of change:

https://git.postgresql.org/pg/commitdiff/b541e9accb28c90656388a3f827ca3a68dd2a308

Regards
PAscal

--
Sent from: https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

#99nuko yokohama
nuko.yokohama@gmail.com
In reply to: Yugo NAGATA (#96)
Re: Implementing Incremental View Maintenance

Hi.
I understand.
Even if the function name is min, there is a possibility that it is not an
aggregation operation for finding the minimum value, so it is restricted.
I understood aggregation of user-defined types is a constraint.

Also, I agree with the error message improvements.

2020年1月17日(金) 17:12 Yugo NAGATA <nagata@sraoss.co.jp>:

Show quoted text

On Thu, 16 Jan 2020 12:59:11 +0900
nuko yokohama <nuko.yokohama@gmail.com> wrote:

Aggregate operation of user-defined type cannot be specified
(commit e150d964df7e3aeb768e4bae35d15764f8abd284)

A SELECT statement using the MIN() and MAX() functions can be executed

on a

user-defined type column that implements the aggregate functions MIN ()

and

MAX ().
However, if the same SELECT statement is specified in the AS clause of
CREATE INCREMENTAL MATERIALIZED VIEW, the following error will occur.

```
SELECT MIN(data) data_min, MAX(data) data_max FROM foo;
data_min | data_max
----------+----------
1/3 | 2/3
(1 row)

CREATE INCREMENTAL MATERIALIZED VIEW foo_min_imv AS SELECT MIN(data)
data_min FROM foo;
psql:extension-agg.sql:14: ERROR: aggregate function min is not

supported

CREATE INCREMENTAL MATERIALIZED VIEW foo_max_imv AS SELECT MAX(data)
data_max FROM foo;
psql:extension-agg.sql:15: ERROR: aggregate function max is not

supported

```

Does query including user-defined type aggregate operation not supported

by

INCREMENTAL MATERIALIZED VIEW?

The current implementation supports only built-in aggregate functions, so
user-defined aggregates are not supported, although it is allowed before.
This is because we can not know how user-defined aggregates behave and if
it can work safely with IVM. Min/Max on your fraction type may work well,
but it is possible that some user-defined aggregate functions named min
or max behave in totally different way than we expected.

In future, maybe it is possible support user-defined aggregates are
supported
by extending pg_aggregate and adding support functions for IVM, but there
is
not still a concrete plan for now.

BTW, the following error message doesn't look good because built-in min is
supported, so I will improve it.

ERROR: aggregate function min is not supported

Regards,
Yugo Nagata

An execution example is shown below.

```
[ec2-user@ip-10-0-1-10 ivm]$ cat extension-agg.sql
--
-- pg_fraction: https://github.com/nuko-yokohama/pg_fraction
--
DROP EXTENSION IF EXISTS pg_fraction CASCADE;
DROP TABLE IF EXISTS foo CASCADE;

CREATE EXTENSION IF NOT EXISTS pg_fraction;
\dx
\dT+ fraction

CREATE TABLE foo (id int, data fraction);
INSERT INTO foo (id, data) VALUES (1,'2/3'),(2,'1/3'),(3,'1/2');
SELECT MIN(data) data_min, MAX(data) data_max FROM foo;
CREATE INCREMENTAL MATERIALIZED VIEW foo_min_imv AS SELECT MIN(data)
data_min FROM foo;
CREATE INCREMENTAL MATERIALIZED VIEW foo_max_imv AS SELECT MAX(data)
data_max FROM foo;

SELECT MIN(id) id_min, MAX(id) id_max FROM foo;
CREATE INCREMENTAL MATERIALIZED VIEW foo_id_imv AS SELECT MIN(id) id_min,
MAX(id) id_max FROM foo;
```

Best regards.

2018年12月27日(木) 21:57 Yugo Nagata <nagata@sraoss.co.jp>:

Hi,

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.
IVM is a technique to maintain materialized views which computes and
applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018
[1].
Our implementation uses row OIDs to compute deltas for materialized
views.
The basic idea is that if we have information about which rows in base
tables
are contributing to generate a certain row in a matview then we can
identify
the affected rows when a base table is updated. This is based on an

idea of

Dr. Masunaga [2] who is a member of our group and inspired from

ID-based

approach[3].

In our implementation, the mapping of the row OIDs of the materialized

view

and the base tables are stored in "OID map". When a base relation is
modified,
AFTER trigger is executed and the delta is recorded in delta tables

using

the transition table feature. The accual udpate of the matview is

triggerd

by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS

will

be removed since PG12, so OIDs are no longer available. Besides this,

it

would
be hard to implement this since it needs many changes of executor

nodes to

collect base tables's OIDs during execuing a query. Also, the cost of
maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without

relying on

OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4] on incremental

matview

maintenance. In this discussion, Kevin proposed to use counting

algorithm

[5]
to handle projection views (using DISTNICT) properly. This algorithm

need

an
additional system column, count_t, in materialized views and delta

tables

of
base tables.

However, the discussion about IVM is now stoped, so we would like to
restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at
least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables,
but using
logical decoding might be better from the point of performance of base
table
modification.

If we can represent a change of UPDATE on a base table as query-like
rather than
OLD and NEW, it may be possible to update the materialized view

directly

instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes

on

base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on

"change

propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe

this as

SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify
which row
in the matview is corresponding to a row in the delta. A naive method

is

matching
by using all columns in a tuple, but clearly this is unefficient. If
thematerialized
view has unique index, we can use this. Maybe, we have to force
materialized views
to have all primary key colums in their base tables. In our PoC
implementation, we
used OID to identify rows, but this will be no longer available as said
above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate

(eager)

or deferred.

In eager maintenance, the materialized view is updated in the same
transaction
where the base table is updated. In deferred maintenance, this is done
after the
transaction is commited, for example, when view is accessed, as a

response

to user
request, etc.

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH

command

to perform IVM.
I am not sure which is better as a start point, but I begin to think

that

the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

In the eager maintenance approache, we have to consider a race

condition

where two
different transactions change base tables simultaneously as discussed

in

[4].

[1]

https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/

[2]

https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1

(Japanese only)
[3] https://dl.acm.org/citation.cfm?id=2750546
[4]

/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com

[5] https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

#100Yugo NAGATA
nagata@sraoss.co.jp
In reply to: legrand legrand (#98)
Re: Implementing Incremental View Maintenance

On Fri, 17 Jan 2020 14:10:32 -0700 (MST)
legrand legrand <legrand_legrand@hotmail.com> wrote:

Hello,

It seems that patch v11 doesn't apply any more.
Problem with "scanRTEForColumn" maybe because of change:

Thank you for your reporting! We will fix this in the next update.

Regards,
Yugo Nagata

https://git.postgresql.org/pg/commitdiff/b541e9accb28c90656388a3f827ca3a68dd2a308

Regards
PAscal

--
Sent from: https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

--
Yugo NAGATA <nagata@sraoss.co.jp>

#101Takuma Hoshiai
hoshiai@sraoss.co.jp
In reply to: Yugo NAGATA (#100)
Re: Implementing Incremental View Maintenance

On Mon, 20 Jan 2020 16:57:58 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Fri, 17 Jan 2020 14:10:32 -0700 (MST)
legrand legrand <legrand_legrand@hotmail.com> wrote:

Hello,

It seems that patch v11 doesn't apply any more.
Problem with "scanRTEForColumn" maybe because of change:

Thank you for your reporting! We will fix this in the next update.

Although I have been working conflict fix and merge latest master, it
takes a little longer, because it has large impact than we thought.

Please wait a little more.

Regards
Takuma Hoshiai

Regards,
Yugo Nagata

https://git.postgresql.org/pg/commitdiff/b541e9accb28c90656388a3f827ca3a68dd2a308

Regards
PAscal

--
Sent from: https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Takuma Hoshiai <hoshiai@sraoss.co.jp>

#102Takuma Hoshiai
hoshiai@sraoss.co.jp
In reply to: Takuma Hoshiai (#101)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

Attached is the latest patch (v12) to add support for Incremental Materialized View Maintenance (IVM).
It is possible to apply to current latest master branch.

Differences from the previous patch (v11) include:
* support executing REFRESH MATERIALIZED VIEW command with IVM.
* support unscannable state by WITH NO DATA option.
* add a check for LIMIT/OFFSET at creating an IMMV

If REFRESH is executed for IMMV (incremental maintainable materialized view), its contents is re-calculated as same as usual materialized views (full REFRESH). Although IMMV is basically keeping up-to-date data, rounding errors can be accumulated in aggregated value in some cases, for example, if the view contains sum/avg on float type columns. Running REFRESH command on IMMV will resolve this. Also, WITH NO DATA option allows to make IMMV unscannable. At that time, IVM triggers are dropped from IMMV because these become unneeded and useless.

Also, we added new deptype option 'm' in pg_depend view for checking a trigger is for IVM. Please tell me, if add new deptype option is unacceptable. It is also possible to perform the check by referencing pg_depend and pg_trigger, pg_proc view instead of adding a new deptype.
We update IVM restrictions. LIMIT/OFFSET clause is not supported with iVM because it is not suitable for incremental changes to the materialized view.
This issue is reported by nuko-san.
/messages/by-id/CAF3Gu1ZK-s9GQh=70n8+21rBL8+fKW4tV3Ce-xuFXMsNFPO+zQ@mail.gmail.com

Best Regards,
Takuma Hoshiai

On Mon, 27 Jan 2020 09:19:05 +0900
Takuma Hoshiai <hoshiai@sraoss.co.jp> wrote:

On Mon, 20 Jan 2020 16:57:58 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Fri, 17 Jan 2020 14:10:32 -0700 (MST)
legrand legrand <legrand_legrand@hotmail.com> wrote:

Hello,

It seems that patch v11 doesn't apply any more.
Problem with "scanRTEForColumn" maybe because of change:

Thank you for your reporting! We will fix this in the next update.

Although I have been working conflict fix and merge latest master, it
takes a little longer, because it has large impact than we thought.

Please wait a little more.

Regards
Takuma Hoshiai

Regards,
Yugo Nagata

https://git.postgresql.org/pg/commitdiff/b541e9accb28c90656388a3f827ca3a68dd2a308

Regards
PAscal

--
Sent from: https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Takuma Hoshiai <hoshiai@sraoss.co.jp>

--
Takuma Hoshiai <hoshiai@sraoss.co.jp>

Attachments:

IVM_patches_v12.tar.gzapplication/octet-stream; name=IVM_patches_v12.tar.gzDownload
�z�8^�<�w�H�����\v����@|���Cv0��Ln�<^#�@k!1������~U���a������^��VUuwUuuUu�W<��?y��*\�f��p�]i������ZCi(uM���TE��'L������p��'����ng���-���*�?���1�2/��N�?���{���w�a)�������'��3[�K�(���������TU���OX�k0�?\��=w�����Z�����)�i4���y]3�6os�����.\����)MV���?���H���_�]6�sp��C�}���_����W/�]��S���J���B���f
P�N�*+V��j~���]��)���39�������@=gR=Y�2��,��,��yx�gq���0��%��|�c�����w�Y�qq}�����o9s��_�.z�Ig��U`�,�AYg�c�s��������~�g���%w�<���b;N�y�W�����Az���������7��OX��|�\�3��Of\��q���/������{��'�)���*o.I�rt{m��5�.�<kIO��`@#
�tsg[~0,��1�����w�����F&���Q�)
3�-dA�(�7,�d���
?���,�[�r��)��0L�R�B��hX�Q�#�Q�CR��GV���R���dP$�t6.��%�WB_{�u+Pc��A=(<v�	��KQb�;����K!��k����Y��%�_	����{����X�&��C������/s�;�m��E��S�O���E,o��|1����-�����'�6�)��eN��kG��~O�@,I�]g�.4\?X�{L���D�|�Z?L��`1����X���Tu�^/��s���

^lh���{�mg������u����u����%{;t��<#�r�.����=|����A������q���I���.����..GW�0�����n/_��'�/6&����"{c��;0��X���]���A��f3�l����������p�a/���{�1�_���
?�'�-�K�I�M���	����kj�d?�����}�x<|7���h���GC�0
3���jUhJ�Q��
Q�����r
����y��l�I�����F���r�y��8t�|�e�gd�I�H�� ���Q���Y�?�-'�C������������P���P&d�,������r�;X��a�v�[���q6M����
_F���2��c��P�?�������m_l��S�
��r�������������gD/X������*��>�����`\�@8��n�g	��Zu��y��+<kE=�8���I��S����kQO�=���4]G�~��
��=��d���	������T������~V}_L���~une�����������[�:����muT��>����j��'(4��������8�[%���������/����N�Rh�6�o���w(W�|��������sp>����9XKb�5�3)2B]Z�E�����.�1t@�b�*��t�A�/&�i.1k��3���}��d'����������-a���x����
�7oz�\��7����dox��F H�������@cC4)/ES�ut��AS���v<���0�7���������J��#�y������tjbpj���q�F��nd���#�Y�	�W*�Y�e��F6�M��
CLT��B���g<_o���EG7��qZ��@���f>�a�%�z��R�q�r<0B�N�����E��,6F�\40$&*+����?y��u;���Ynh����~Lt�B�ln���C_C���U��J�a�9��l�m��
o�D�n���F��|3������U���r��((�X�����>�� ��Rb������y�#�qx�I<9�����q;��<�N�a��},��#���Q8�a�+��q@�y����*J>�����zW"��R�&vb�eY���^�1���e��{���v��������o^���0�W����E�_0��Z���f�l��i��J[���	��4�:��_6��<UkY�_5��F���Lg�z2��,T�|�o�#N\���P���)��8R��}��y��<p�M�����w��~��@z�Fe79��d!�����J�W�a��%���������
��'�������_��Io"
X���[���m�+���)U��-?���;Tc��*d{6�Y��~ZC�������t�������������������Q�T��m��jvFz}7%���F[U����}����y<Y�lQ�
u=/Sp4��o��^����^N���HO���)X�)�a8K��o������(A)��E�����eD'w|�c��S�1�]Z�2A�bm�/<��!4�Y��	�����Q�,����6�Y�<b�#A��@�����Do�\d�+E�5��V�U�Z�J�m�YM���.��V���N��������M���h*j��
GPQ0s�T�����E
c�� 
�Q6�u��"X�F����,���P�A�-��_��7
P�B?DU��t����;������R�����l�g���c�B=0gU��6+�\4��e����,H
Zu������s ��iV��4�0\>"��YE�U
8���?�����)�\1�1�����n���0����99��+HBPt����M)�2~JH�3�0�R�;v,��/���9vX)\��~���>-�.`"br�b�#�)�U�^���@`+�5�UH��7��xr��|���S���'|0{�����,@f���J+�/����<b+�)�0�Di�1���G��:!(J2P���ZT��
�=B\�	G%[A7@Q�����*�������H�9< �;������z8t�6(o(*��CZ��J@l��)�W�wd��s�*����`T��S����+�3^���@��r�a�e�A1�b����+`�(��K��i��:M���o���W|.0^=��	�uNg�D��Y5��T�QWj����^"�)��`�*����L��V����K�|����Ux����\���"K@��YT}^
�-������bSBY�Y�9T�U��g���!���qu�
��������R0(�XY���|3�&j��F�(�(~2�P)���<I,
Y�~�N��mOe������#�A�3w
*��>��?�QV�m@@��H������}�����5-�~�YR@����o������1�C��(�p^�U����b.���QLS�d��m��MA�^g����-0��������&�A5�����.K(^3��O^���[�?R����������]��c��j�
�5�63�{�$��E�
� �(���?�L:���BV��]����\&�������W�Fo�Kz��<y��B$Th�$�t�-?��!&�����jkZ���`u�\������Xo�j���:l��3@��9�v%f[�
&�]+�6t�RrD-B��u{�;��B���f�{7�;;�=�v�Y5��n6���eS��.�Z�+�j��+>B`�n����p6�:����D�>��E~�d���(���( �L�t�#��q��pk�� �3��N��,��m��X��rm���Y��
<�����}��/�������
��T����E�f)���*�5����g.����t�����#Xl��hb(D���6�Y!��]j���G���5�0��!���)�}g��-��]����@ft&�Y�2��`���L�I>�z6�A!K�_�V��Xvk���5��+��_��G��
�BU.����,[n�b������5���>���������E���8	�B������ZC��YS���F��npn4��C��~�^r������W�����[vEw��}@�o�2j�)��j�T�g����@MY��������x�*V�����������J��i����3���Ce
�8#�E ��X�C�o���h-T�m�_\_�0��K~�:e�K�J6��z�jup��*��^��g��'�$.�Iu{M��sF+^��x�A�� ���y�0�J�:k4�}��A"���)0��[-���Fn��F7
�����h����N�6��(�n��7���������N�~������L{�L�0�hxJ�[�aP������;.[y.��2��<��9���t�V���]B_���6������z��q_k���9�@_���>\�e�j���B�������dJ��zI�h��n4LB��	��u��)"�Ae
,�Ny��Y/Y7��@1pc/��s`��1�I�X������V?����T}*_=y�+.��i������7���pq����t%��z�NF��O��]g^�-S�!t��Y� ������k'��	����H��n�7��k\��iu��iF��������z�Q7P�
���Z]��e�}����������������z����&���<��3TO;/?�mf`S�|~���{p�(5��r|$�y
s���=D�������+\_�g!��\T�k(u�d��@��E���Q����
h���o�;.j�I3��	.@�`�P�!�]��
��-\pAe30q���]�����`�[���'���]>��$P�i:�M��>�S���$&�����]������mz��v�����M�Go��m�����H{��
pL[��5}v�1�%��-��@����}�Q��	��$\���c��2:�C���q�)�5��}F�y��K����p�PM�EB^��#-&<}���7�_
#+>s1�C�xK��P7�0�\�V;lAcs���<8
�0�	��v������)&Fp����(�T6"��R*�@H���CK�rh����������B��t�v}a$�3�^p�c��O8t9��Z\"�?0��5bj�����������8����q4J��b��=�@g�@�K�l���[�J�&�X�S-��-1l5�[����#�M62
�O��vR�D�)�=�1�%$���:������th�=���Z�Z�i(b��o��uo��/�g���~�v'��8P��iy�=��X�u-#_�`��t�2:��I�����@�����=��p����m�����b��r�-��G��\�6�<[���u��XJOC�hjJ���K��H����I�����`?��g�`X�����U�==��#�s:w���2h$�O�d� ��}5���5�-�I|]0Kl��~����~��R�|�
Y,?b6H����)� ,�M���d����4�S����Gd2�U$��������d\B;0�_l�w����:y�P& <J����/��'%�s��i)R�H%i�'���� ��]-�Z�7�K���w�p���h�i!����B����p�eq)U���)2�9y2�}$�:�����~b���������k�Y�������-���f��'G0m���e�M��H���k=E��t#aYh�

3n#h���
�}����$��@��$��4���z_���[-Z�m���$U���]R�\�������_�-V*����bu?�g�X�f�g�o�+���21�J���hgw,@8M���-�?�h� �EM��lh?
 
���������T������{|
8�~{6��&�[�����[�~0F�C~����G�����p���k��L�}�/P��r�s�@��@���mY������1��v%�!{�b���Q�FX�p��#��!�sh�c^-Z+�([z�����'����f�n>�6�]k�?T������lF�S��-&H�a7��Sd(��	�&�����;"[y�o&�5-9��qC�]Z)�FM}�����wj���O=�D������8�tx�W�XJ���?�����;���>�Q}'%���"�nwv�+� �nm�;�����&R���Bd���c���X� S�aC}�9���a s�^��@o��$��I�L��7������8"�gS�V~�g�1Zo�6-���:�"wOc8)���$�LLzM-�����&w���t��\qn�5&�{
���nR��Mb��K�?}���Jq�o��{����?�������;[�����v9�m�����V���j�`{�������RyX����m���dG|=%$�F��B����3���6N�G�g���G����=TSa�,�n) �*����	���_���x�A��O{X�|Qi;&�bt�'���*:;��Zo6{g���"(�o{��X/Po{�NhB@��+
���Z���A�����sn����%P���BI��{\8�P�����7�)9|��������P=��/��J�AE�Q��h&�p��qYS.�G9V�@5oNo+*��pN0��^�+E�\!���8�Y��hF�NaX
�@AS9�	��id�q�T}�p�m�P�vz@F�	Nl4�2��bc,�}	tD�0/�����w7��z��Qq'���^<�v ���07z��"�� \�V���O�_�5qs��c�.�whP������U@�6w4��-C�"��{��S4�\�8{��������1Nug8L�1��� �%�9���������a4�j�|�y&��V��n����!�H��!��T����9};2'h�U�����"�% S&2_Q�Tv|R�y���Y��:0����M�F��������:���M����A�AT4f����xn2��!���h*?a����+|%3�2����'��z``����Z�;fp0�K��A����\��Y[M��������-"���,+�EOh��D9ymG����8�@���,6J}��a59�����v"�fb�"���l�����j/t��Uek����l���]���sT��h|��2��c�|"��x���Wb�����6b��HP5�hj;f�E�*{������|J���<���/�3mA���7�S<B@
l�`7��~<Mc�:��3�kLy�g/^\�"lt�Br���?�����7�[KG`O�1��eD�k��2Z����a��b�?���]jz9��##8���*�K����ED�������x��a��^� ����F�K�~�u��,e�R��:��g0�����H����i������w�sF�~0�WM8�{��<���c�����e0��EQ�%��Gq��a4yN�
7s�a4~�G��A�)�|��A��-
�8��X����-b��`�N�}�(t>,����h��5�a )�N��4���8����G���,����������R(`���uc�!��?t���Q��h����`!��	���O$Q������s*���2��.5���/�%��m�\mw�J��nkw�RY���ic�%�S��k)9�[��6��=������<���$���FvvLF��q��]�+�g��w�Io�;�����>ed���_��N���0��[�y�@�{`�e��%���[Y�&������Y��w������Ky�s1;���7!�.)��Y7(�X�7D����a�"d�6��N�#T���E�X��	���3�{pcI;����Dog����0�|�t2�����p�p0���@��Z5x�����h��C�0�{H���-�c3��������4?}��C����Q3J�l���$��k�����>����V0/93��	~+z��&Hp%S����M�Y����X_��)d=��]��;2��~M2��}A���D�M+���"�W��-��q>ro���=�]��vNg/�|�n_����EJ�z�E���XC13
g���\�k|�wNa�-z�w��F����Ioy�x��}� ������Se�X���)��IPs(�[��Q�����#x����C�
��]���*���^7/�^s0��b�s���9�����U�%N@l?���ZZ
f�m�:��e�P�+���������d6���Bp�xA��l4�����(sa��o�>A���:�6��Z�X��1�{]��J��H1�"���5�*���(��'�0�����A�����������z����-�An���a�G��Q�������E��eC�i�v4��Xq�Z}XP��P�Q�&���
HF[@�*���2�����"Pt�:D[.��Y�4���B����s
}��<m���tM��l��u�A��/�j��:��1j����42q����u�����F�p��m`b��-LM8�)`@'2��4&DH��E	�������~���z�{����L�B��-I#�-�$�w����<;�"U���m�����m�����']h9#�=���U�����x���5��B?�=�dM��d�������A�V��j��0�)�D�D���
Y��(�U���3]@{i�H^
$��74�m��s8!��(�FcW��S6J���J�&���$������K?�|2����\���X��|2�m<iJ��G'��5��6�4�N���bmn>>t6F��2!
��g��-����F�rGp��}1(c_�Wts��CG_Q�*���P ���'�|R����ST����8��h`QF����a��M���
k!H��`���C���5���x0r�4j�'��lt'�����|�k�� vV��'kl�������T �����0�Ig6����
���Zj{��Q������A�?8?�<�����u�K��s)�Q�^��K{�0"kaW�.{�M��S+�Uq��N{7%`+�@K"���6�6FR�������4u��Ft���������?	�6P)��O�=�'�;Ep��&F������J!"�i�N���^Q�����);U��
���~�N����:g�s@:�#Z��qM6��P�{.x5\P��4�?��4)V'��=��}.\���@���7���WOn��TP�%�
@��M�i�[�oX�&8�5:����+����f�bu�Y������W!*?d#kd�
��q-P[*���i�4X�����h�����4J��_fkL	�����H�y�,`���h���r����{��C?����y��<��#{����>s\K�1���9����=��H-jt���g����P��S~����(x�9����=�A&S�@r��U�X^HB>N:zp�C0��� 1j�BGj��_���E>�	xR��l�	RgMm�nW���6�{_�C��Z��ud������N���S�Q�%�+�����"#���E��Q��a�7tZB��D�\d���F�z����=9	�yx��Pk���*��K�d�%
G�@v8���SN��PP��|{:kQ���+����]p�s[�#�Kp} c�G�^����� Q��f�=������������P(����V�]�QP.X���u�U]��FMv�[W����w]��r�:2>
�sd��#�WK�����l�����d�~8�������n�l����RV��X�(&��qk��f09��P&k8�����e� �T����Q�u���<�1zx&��V�Y[K�!--�/�j��gB�q��!0�w2 `����4I��1Z���d���7*W����� �`
������[�u����%�S��"�Ccs�$��i�Q���^�6v��x�:dC(k�^uX15�,�]M�F!m4������Mp�(7pY��Q9���;���*]*y�'F�iJJ9#s5 \ti@d>�Ai]��E�����Y����"�1s�Mlr`#��?�I�E�z1����^��d(��F��
9R4�����]3�9v������wgB\m4zO�+_�$�X[��*��i�����V����px��3k�3
M��<t�V#���*�k-�p��1`@�p��R�6��l�`7�F������hS�wV�4�.�Sk��f�@J�V�k�mp���h��yAew�~>x�8z������4"����0�h���$���3B�^WT��3�b5�K����i�D���*�6����O��]4� ��g���X~//g��=;��-;�t�����f-�2��$�<wxG��o���v���������a�)c4��i���n4�Y�$c��a&�����Y�OD#����G_�x-y��^y�e��d�}�b�\G3�/I���:�gi��������w(	�X^�A!�ZA�QC��A@`s1]���!������[7����L(��g�8qZ\�.ZN�7�q!��5t��)a���C������s�t��J�`�rs��������X(oX��� fPs��E0�� os�p]b�	|�����)�C^�,CR2��$k�"g@	je��si�[bK�[a8��&�;��n���p6����h��7�4k�@O#A���<�NQ}��Ta~�A���F�3O���8�F��p�����~��c)�*�i�L=�S�"�]qZ�<�jdR�N���P_v����#� �����@�E�\���Q&�O�������`����|���>�Q�A.	n���7�j����;ENg������#4t�����>y������>������S�mQw��o�UK��;r�w�R;���c�����Em������s�N�bcx�\�a�n
�)|%k��J�������I��7��9������Ie�	��|E�m��u>Jw'�_��8���t��vC���`O��^���c��|�������$<'W�4wT��Y��|0���7��~�]���5��/2����L)����\�Y��e0����L��������$�:��F}�&JL3�����^�k$�������M6�{���N2bgN��4��g���k�����������9A������CG����M���M�q�8k��u����N>yw����������$����g�����nL��PB�7�xo�.����$M���O{�5!m��������t�V��
�^(L���@��XA�ezm��d|���2�:�M�
��l�kq���r��:f�N�����N�^�q����Z[�G�5��D:?%Q�GIz����+��m�	u+���)�]�Q4�����_b���xE�����X�Oi��('���==G\�;a��C+�I��YZ�����F1�MEF
�����=/r��Q	p�1�t���-���mZ8x��O����~t��cLt>�nlpq���M�����:z�X�8�1M�N�Cb8�
m.9l�C������Cc��!��DA5�?����.�����1U��\H$4mr.�L��2y��V.9&�ZZ�����!A(�9���b)�{��Ao��Ju/������n���ws����u�j3�����Dz��x�����~?V�,*�����������������r}x��������{����6�#{�i83�����0���1[D�����n]���0�pr���\�1Ju-��7��0��(o8m�"����CVj�����3�A`�f�!���Q��g(S��OLz���b5gc6���$5�����_qp���6�������c��Kb�jz��`^A�j��v�����	��!�JM�7��$�8&M�IH���x<������G
��~�vnt�p�k��Im�����H�9����;�A){�N��ss�pB��w7
���h�Tl���r�w\����D�&��(�
��f-�9;?{x�� N����U�{d&�*48��K�#���G�����/���6�`>�1���ZA�$MF�1~��s���9a�=4g�-7�#���;��q���s��>0���)��C��U��)��*n��l�z��rr<�d<|�/�(U8Kfx���K@^>�(Tjp~5���
{�>�~>J~���!?���!]e���.=����d�Y��s[�?`9]�o9��cy5#�#�=��6H"Cx�d��]9��*��6��)
Ob�;�x�c���l��F���I�|>E�B6e����O�u�c!K�s�I]K$K�cS��jeK�"Y��Y�����
��|�d�%����i��R"0�h0MKF�<:>�?������M�xC$	��x��v
��(���	���k�x�.���������<k����Q�����-/�o������`��9@�n�^�t�rV��:��G���a�K�7G�^��;���<=
��z:}W���x �6� �������3A���q����$�N�~��Hp�����Eu2�P����"8��3-G�|X�����!vt8_V��|���������Y��^�����4�7h���>�T4G2m��U7���A���?[���9����0g�^��z^����t�6�3L_�W>de'
�� O0.�����i�%Wn�k����0o^y>��N�Z8�w���PI:�����`�	`jp�0y�W�V���"�BD!T������Qt-��@3N��N��?���}���(C���d��#����!d2�xc�U������%
�?�����9�%����t]k�����X����{�d���d��f��!aN<2G�!����"��
�b�i��E��6��U��f+n�qy�������y�N�"�p����z�*P��>�0����v�*q`�]'�z�8��}$p���(������Q����aW��!��M0A�f����l�v��e��.�'#7��'�D��8��Ry�v���$0E�k[H|�����v�H�V�)V���h�F7����O������~m�/H!e�p��_�����^�Ft�����&4g����hW�2Ap��|��.e��&RB��D����/�)��M��BUR�8Q0�"sj.n�����M��w�F
�2l2�u��6jKT��;gE��P��b��V��kk��Xf��p6@<����B	N��T�j=I_�w�������Q�'����.������+�����I�F�d�Q<�=�^*jo��m�b�+���+�pk5��@����P3�t��Ng����������N��������VZ4�����b�?���A�>�Q��#Y�-�|��_�(hk����E
��a�)d,q��-n�y��;/���G�g�����U^���D��8�1�ssCa��N2�V�n
d8�W�	6h�Cf��\�U���,�#��:1�d#���CMX�n�Q������:6y3�/���?�
���/�������'hEC��p�21�zQ{�k�(3��;��a�'�(��O
�&I�*���B0-w�L�_�7a�'V��A���p�`����r[0f<�a�&m�g���
0��v�ZB{s"����r,8�l�����T��{o�R&B�#X�EZE$4��rg��e�������NZ9}�����]F��n���������I��)N��n�\�gMv�n�f^;
p-X�_B������6���z��x���?B�S$����)����QC�|�x�G9H�}A2@+��Lh�m8&�\G�bp�� ��-��n+��X��p�6�LY�{[��YM�����_�0GQ=k�_��.���-�j|ep	�!��x�-�=��<������X6���������aw�
��0����d{�G���!u�~
��
\}9����Z�����0<���^��^$4]�s��!7���C�N�[���$r�r�b�p�B:^�-�k�Z�4W	1�slS���E���D�lTM�z������zxj��{m����j�������zU?yv|����G���Y�����n�U�h�I�F�Q���^ON��D��#��^���\�����z�s�(@v�NA�4%�C���Z�<�!	�0�HE�e��U�C���$dk���
�����������
�,>�	�|p1�C0��:�M%vI�@�g���X�T%�	�����Y�#�:�@�"�8��*�hf�VeP�(��J���T���:>G�]#U �'yQ
�
|��.J���\
�9�������YmSo-�1-��'��X���2���]����B����>��f#�e���1-l1����kc{Wt]�1X�RBe��7�eB�g4��g��M$���4K)9M�d��o��#K��eGy����??���h�/7|���^�0q���j��a��b�>t�1A=8�Z�����r<	�����I�`�6��{�����+�M�~u�9U��@^�e
���-�U&��67)UD���S�'�y�n(���Cm�3�'����M|/Rp���>9"1���	0QO�3��r	�����4�^r\�RncU���
YU)���R�������P��\we��2&���[m��J�G��.k�j����\�.k��f��6���cq]������Z4C�i����J!3C����Vs�bN�x�
9��U��j�tZ�"��_�^l�Fq��n�y��<�P���y���H`F��
=O�����A4&Fn��l�r0�~H"\v}f�!��6�����g��I(<8y~*G������LN��}QP�6�B��+�
�S��	
m(J�c���|2=wb6����P�eu�x7��m�������9�X�:���d�C�������\h�����%�M%��0E�)Y�t
EZ�C��'H��_�l����L\K	���.C�H$Z�/E"/7��^��J����T�X3�'�W���I}���S��7����i���/y=Bb��~����@^��&�!��ql%�����w�h�D�uF���2�Dd� �t����Mj%L(���5[�_�	W���5�Dh���"�ft����M	ZF%[*��'?`��
}���,�r~�m�D^��i�=��N���8>�[����y������7��<N$��F�1V_IT��$����~06=����0���M�C4��f�	������~��i$�����>�(�.���oh����#vDj�J�
�G�C��c
;�Y���L�u~p�7V�%�u����O��^�z��h}���,q���9���[p�z����"���J(5���7�X�I�f
��EN����{A����a(�L�=�{N���������M��h�c����	�������?Y�H����#7"�D�3��N!��Z���`R��3�)�5���nt�F�����d6�mPj[�GEs�P�;is>�9l���o�71web�j��x��&fn�)�9�>r�x�����dj{�N��&o� ����}�2��lI�����~r�x����m���"�)�"�^2����p&��:��d�kiVqVq���k���f����}/��u-[��y]8��~���e8M.%=���y)A���
��B�F���T��d��>^�����qF���VHF���������K���w���p�%��N�!P_�ux+�r���W���Q���G�4����0���SOxT��4�8�.�F�������s��!�����(S7-�����D!�9��v�G�"!�s��a#Q�(FG9�Y�M���JaY�F�����R��h�'1�����h�����m�i)s��w1W{���q�D���^5����O�W�������E�%�I*������K���a�
�Q�
�/���A��i��c`��6���}���H���*}@���A�e������~}�M�Xl������3��O7����+�f����Z��G��a��5N�O�?�&��}(�rEr7Au�zt.�����Ds�~������8��2�qI�k,�J|~r����O��,g8�;y�U�����i��V���V��d�#<���n
?������Z������8���Tf������Hwg�Lu�4��
g|��$z�n�,����k�F�>��u9��e�!6b�����l+8��
��6��*��i=*+h{�B��5jOY�$vE�v�t�Fi{V�H��Z�W0��6�y�j#�g���g��n����3~q��C����QCF8R���x�;����X���e�\�Z�L�J�3�"%��P�������9�m83��G�iU����������h�����=��fm�P&�88*rP��-���n�^�?0?i�7eO�f~����6L���s���[��)��3�PL�`��
������xs��M�g�n�b>,Y9*R���I�;��'�a65*�&�!�!����JI�L�L&���������<�;�|&�c�������)���+��6��,�����^��I0�_ld|����"+D��0�t
��CH�Z#6��n�8�jo�^��'�f�F��<=_�^���2&g�Z���;�J%�����i���I��7T��h��;�������s��e�����l��p�hQK��cY��i�r��3���_�ON�g���z������$����XL
E�����;�� �*�;g���6%.��~���lRS�9�.�NELa�1WK�58�)�r8%a�P���G�HRabx��}^qV[�h2xl�����]%�7�{&::+����:b�
�������� �Yc��$�h��j��&e���
�]]B�)Sr|������&�w7�FUnHm�U�G��Z��6(�^��- �$�������!�!S%2��D ��j�9mH!D�
�;;�����Y�Q4M�1�q�xC��b�6cy��yv��\9�(T
����3>CG	np!/���}
#q��i�tHp�������af�Y>�W\�8�����/�x�u�4���6�9����7q�����N�5<;1d
�m���'�0�	����J����cB��e���V">0�+���\��'���}���Br��dD���b"����f�X�����	o����gE��X	$�'���4��Gn=&+����t!�P0���_��@3�N��{�T�b0��x�V$Y���f�`H�\�!<��2��98���q#����\k��Q��W��*;���QS1��>��l��3�����|��>A�3g�&g�r�F���6�;����B>vUI�j�?��+����2�����0F����������
w4����S���(��k���g�p�������j8���E��-���}����7	e'��s^��H���V��=>p5���������Y�4���J���-���rJ�+,���F�Er�5E��~��D8aT����*����;�� �9���4��9�gI���F 9�bwE�7�������{�^������a$�(��7���M6P�rc=��)�H���A�2\��E����x��0����`;�����*�e�3xR�{(m,���me��g�{��"&�5��7I^_�C(:����~	����B
��t9>A������*c�c�]CW��s�(���L1����n��.��	:W��"~&(��&+���OK�{b7xn*a��!�����)�2����� ~�������9���kk|{�/�������H��oIM���a�
��f����'�NI������0��b��m^�"Kvl�U��8����}��+���<:x	���W/@~>>�'�r�������y�V�k�8�M�}$b�l�9o�q	�����A�����	��"`=���:�U�-�\p�f����y���x��������e���E4�Lm~Hkbh������#�����#`����/��;B��C���%�Va�����]D]�*!4f����'6I.�l����Q�J%� �K���T�����eb,��\�/������
�L���t�,���O��4�������'��������/cii\�B�(1T�^���
A�H��T�}0r�x�A����h�t�����MT���h=`�5�L�	g� ,~��EG�jB�t�q�4$����w=�2='':�����$Sh�i�%��
��
{C]����|���K�=H���F�����0?-}���6��~��!*�>�.�'�\P�'RK���������t~��a?�����dE��W��� ��!�zP��Jd��Ppc�0��W���Q��H�x�����\�n����w]�I��
c�s�x`��TO��~A����o�I�P��C�6YYA�m�f���TX���������9�~��{4�d�������{���&�����f��kC�+!?�X���V0���"�X���xr-��|�.{X�w�o<r�3~�������S�3w������s��G��,��
��1����;���f�b�!����
'��z��#5�X���XK�!�LR�)^29��"j~�'��_�p���3��Ui�$�s�+����u�{�`��=�S��9�_�=*QJm{tB���?��@��Jd��ABgN����f<�c��Sy�b�Ig�h��;�)�@n�����QM[��?�=8;xQ?��=i����<����<"nK��>j~?kb�q��������]B+��_zr���W���8�3�X�����C������ro����p<&l��@�r>�u��_�1W����.�g��s�*��QO�b�Y�W"v�����:*�=���c9�������J�x��wG��Gh�IcB�v�C���>'����o�*O��'�~l�t�%�8��&��[:�["����;@K@��������A�|�Bu��T��/�	r�N�A���?���m�p�Ip~N9!�	�m��9&\?����N�
��sI{�R���Oe�F48?H�)�ei�G�h"e�+�!o-�?��x�3
m���I*�L4M��;�������$�sV/�����[�.���n~�
jCU�������6&�&�Q.`�X���v���I���Of��XDNh�����|��-�)�C�as�0B2�ww�I������%�������&�5�J\B7g����n�P�e��\�g���>h^��2����1-!��tk��d�aV]������xw��I�fBtO������
L�Y�Y��a�>%�746.�����W���7K������Y=�\�;k���6L1C�%��(��^\i4��I���r=������a%,|
����3#���.3�-,+�Qe�Z��Z�;^�����m�
���)�R�T�f��������@��8��#�������H�n���Ni�nsec�~�c������9��F.�'d��'"*�)�6:&�}��eg�f�C��o�m����[�T���On�:�cq��G���|9�H-!y�l��np]�pL|
J���0YF�%�0l-�S2�n@o]N4F�	�H�]�X���I0Ngn#�i�~���f]��(~�<�>O0���D?�c������$�\'������~kzW�Y�D�����AL�i|���#������J��z,��L�bv-g������rCd�\,U�����?t2��p����`.�9]���5L�C|�k�7a^-��3��&��)K����ik�N��\��i`+O�j��"#�O��sL��["�;���x\��i�c�J��dI���q������f:OF0�Q�(�#|�zd�����w������Z����(0�<���t�2����\8�Y��������s)$�E����X���ctG>}��f�%���S4��e����k����H�
��t��L��\J(��4.��0�F� �=;�JT�?������o�S'M����S�^�y]��� ��4_�5^4���N���$rW^[��v7�����(|�)����"//��M��wY1���~�����g�$Jb�YI!@l������C2F��x9��c�Y��H<Oq���moQ�f`�5aO#A7^��M����&OO��#=����V�E�_�{ �u0I��8�DB}���N<���N��}j��=���WN�Y�>3������g������d^L�*��*v��_��gCy�|�J�*q�cW��Rm���[�����Eb�
,7�����e10xn�7���#�Hd���O��Ma�������W���br���o,n�I?Ex�K�����ac�.�a�7-�"i>��"�*N�a�)���~�S*v1�4^��j�������?M8&4��H�m���i�0g8����W��f+`�Gt���Q���`��AkMv����ae�A#|W<�vc/����us��i��`�!0�HO����9Rg��4����2^��u����9���:���4sm<B����)�k{�l[�g4�O��I�w��H��	O��-�i����
�$���\W�ov�3�%�08�D��u�Pu>Q�\|�1F"i����V��KT�i�j�6$���1��QrRx�{!#���hb�Wv�M��9��<�Q��N�����T���N[b��zV�N(X���4#�n������vK��^\�6#�"��s�C��U'��$RT������\���W���j�E���8>�l]�|B��EQ�a'N^l`�':��5�`���u�P�U�`���kB���Dn�C��1���3�k* 6j��b��
�j(d�p�o�r���L�4�S�����Rf�9+�c�<�������j��������'�����),�4�������#5���*��GOSi���u�v��Oy��pb�mo�|�Of�k�����<��Ja/��$��g��\��VF[�
����/��c��mm�<��l�&���?��x���.FO�y�s��
L������,�H?�(��+����+�� ��L9����>��9��`8g���P���{J8��@�,�/�cQ{)���:B�uQ���y�����0������P��B6�~�d���,l4���e0Cjnhm����.lE���0�>l��d9nJ�J>������R�����1='p������9"�S��qzK"�*\I�X���h����T��d|<gm�����e���.���~����XD�p��G����Fn���yM�I?��SW{��_��_�������eG<9E�:�C�1n�Cp�]��
��7�
�t���'�UH%���u1	G���@�	�d^�,%�C�B�8���zb����Fm�$Z��|��oNh�r���������2���������}Z�Q�j�u�����J�s)���N;�!�Lt0-���&Vn�Fj����4)w.��X�A������`��S�Y���:�S�z2�g���pF��������s�����	�����8j2[������d��3L�qx�v�T`6����EU.*�J?�=�/%�%LW�X���r������S���I��I*Pp9���_��D���O��o���Y�L�p�����x���E�������������`z�M���Z#'���c)^N\�K1����a��E�8���u�Y��i�������������57�� {J������_M����[Q��A9�l>��������
&%����L>3*$A�G�$f@&7�i&Cd�����}��`���b
�1�l��z��^��Mq���2�O�k��t!�~����!O�M�-��{���I����'[�����M�������0x�TG��:>��X��:�)��z1c������=J{U��i.{i-�����/gr#Xk9���Y�-u��hn��}�ptL�n�f�|c0��F-Z��11B&��G|VFKm���������Iv�cS�`2��"[�1#j�@_���
~�K�F���Ze�ur����Q ��'�V�tC��$���=��1G��2�T�{����N�����IaP��=L��������d^�&6��,�����1~��-�B���?} �8�����7I=���f��|��U^�q�.x/j\����F�0��o1��P/�YRs���^n�i����:��&�����# �q�Jos���?�&��6�	o=.��V�r���W���V���]���+�R.�lm�677�����X��_��6w����]�!���YoaX1�]Sy�����c�I��x������<�J�9��r���`55C���r��u��\�dQ!tfq2�vG������/�Og������Y�����^�����d��������t:���Q�o�(��?��P���(7ZT��t�`x t=�(�x/��tv��1�j��oW�1&�Ba�e_���rqGm�'�K�xTL�R���>D����yzx��s��_<�K��Iou[�3��{'���z������)��xD��b�������e
�����n���J+�l��n�����E�5���^!Z���=ZZ����2K�m��kc�[��/_����yz���s��+������Fb���B
��\F��<��:�;� �
��Z������)��_��p���WJ�k���?����:�_N@wN]t��r���sV\����E�z��
��p�$���0�!,"F8����{���+�p�]*��wk����et[�ZG�.d��_���O^H,��$��q���0{����%�sW�J|��{Xf}8�����U�(%k��������j������Nr��J�����Ck�S�%����l���2�Ko������
�}���"@�=�w��aE�GU��j�Hv��o��3�8z8~CE�6�7��D
.*EP� n��L�6�6�v�<�}=����1em����_��q�JR������fv��T���j�)�����"��eJ��������d��=�L�G��'AX�8]^Z!_����5;4�����>��m�"���+�xwoU���OY�d���0�D/����Q�\���hT����n1'@��J���/��)��sf�����4	F]v�!�1u��H��b�ML��Q:���3��+���s/D�F
��phU��i������M�8U7��^�
C�j�nk�+���`�B*`EM��������-a0$�(�O���IB�Ty{��C�$�&=6%0���AD�g���?{}t(�bA�������C�������q�	�����_�����m8�6��xY,�K����D8��f�9�z��/P%��Z���{���OQhM��$����WcP��$���'��wb-��>�T�����7���(1���;@5<�F�z�D3�s��o��cY��4�GG����xv��q�v����+7�C���xt�:O�����K�h/�U��KS�1�kZQj�����n�R��<:�D8��T7�I!��
�z�#���o<�,x���
p�(EO'�������7^�|��a0���b��d3�O�����l��_�55����
*��N�2���M��hT5��pJ�(�h�$���|�Qc���QJd�������r���t�e��v�2DVI��w��u��=D;�<��CV�"I��J6���*����P�Sr��N`�3;�:`c
&��H���h�{��''L������uI;�; )�1�*9J�m�+#z�[��wz�����_��ty��o��Y�C��$�&S���i����u��C�DGo^4�6��o�����Y�GWj���,�a?����d����p��u����`i�
-D2[�l�
��h[R�D_��Q�H��+����~>k����q���r".y��)�u����-��N8��Y{C���:V�B�^'���kF�9���E��g
��m:J)��I�O!�F����	�k����
���D��F������(c��������.''��`���s
��b���%�q�i\\$�S�s*o>��e�����x�W��������'�s�f�	�r�_��_��#m��f���5{<V�[��������/��(e��W��M!��p��oF���L��H��M�2
�xyA���]#Z���&#�P��N���N2)��h�E��2�x?e�Y�H�O.H<�(�G;n�7���:�l3�K�
Z@����ka�X,������x�9�d��A������F@O
U_��C��5/&�ZVT�����zZ$B��D�_�F~�X~�/�+�4��Z_��P�k������[_���6/����]_����k����
��67������k��b ���i�!9������%w���7�(EU]�/��Be��,o3U5N��G�sV���eP�y>t�2��%�|
Qb�����u<�o�G�v��{�4�F�6V���EWjL"yL#X[�z�9����U�/(y����7��/�"bA98^im���D5���������R�a�?`N_���iY�M�%_a������y3�"��sN	aw���^u{�T����nu'���k�r��Jf�ww��[�Z��R#VWa
`�sz��a�`2e���h4��)d�H�r�D(2	�Ix�(�A���Q�-��ae4�>��U���oE&.Prwo�z�G�	����yN�^��l���.�7����\�Gp1�m��b��H��Io2�$R;�yx���>m���w\nx
nO4�\����ZaW���8�%K���n���+��~e;���s�<��$O)EH�K��n��Q��	A�d8.���3`j0�|��5&�������Sy
��B�*!�����,������Z,�)���e���B�/�D�A��8���P�Z���p�x��Eb^f��	!�|������4+��q���k����Op*���F�a������/�����*b����~H?����n��&��K
�
\4q����+����m�#�����Gy�%�����Ns����
2Yn�}����g���m`
�~�iuZ���T�
w+�����9M��n�����1*Dh�O����Fc�CHi��q�jn~�T1	�M[���9;���H�y���?��]�B,�y���m��5X��q�j����DI`{������cD���%��]YC�i0�
T�)����7~�l�<'W�9���e^UZZ��P���3�den�o�Y4a�a���,&*�q6F.6ss&����I��oAy�����m��9�aJ���R���r�C�������!�3�yY���cBv1��(y��I^I�U�Q��y����nYf��$>�hh��R#20:NI6���WqnR��v����Hv�%���X<c�������v���I���I\yw��\y��
E��H24)^=�,��zZ�
�2���nr��T�R�e8};��l%�
=Vz�Ux��nD�&�6�
�P\��Pb��" _!�r
c���Z�5	
b�F2!A�/8�E��G����#QPo�?�p����J�R%�����-�����Dh��!e��|���9*`K���t�9�M�������I����J���>����n�>�?+�����]���R���T�����?������T�60��m�O(�+w�}���1�[�{�^C{�n�S�����v'����T�rw�V��l�[�������j�^i>
�����������j��������H�xY���>�M��Q���I����z6��p6m� ���������(�����qf�?^����;*�*FO��'MFO'T�F�{QN7�R�1�����\N�?h�m:I>��p*X���&%DU��`h���~(�K+d*��ig�pZ
9/����xd�K���3>���IcS�]��
��S�E�pAh�F���\��lj���E��������R����������m$y�Z������S�gp��<}P���[�Y$x���oQn}R�HW7	���~�D������h=�fE	��D��0��G*���2VY+O?�	|g
e�>6e�v'�� 4e\MM��~E�R��cV�'��~�X�"�����CP}��������6`�@0�|�o��������I�W����~����3R���z�n.W�K3D���Q�����O���<��U��E��C��y��ys]����tx�a[������u�`G���I��T��"x�e�R�U_��36���7��8���&�����6��w�7�9l��>�o��_�Uvk���_��m�����G�����`�tB8���v�T�v�����Z���)ouv��7z�o?��<�w���D�NYAO1?��l��o�	��������CN]��W9�<��~Dr>�_�=������9�l���Q���j����r���
�/���3�
�*�8/R0z���h����E�q��� �N'�6+�0azH7�n{hB���+U�0�T�R�p�o2��Kj�T����oh�����������lS8 ��y���IQ��[A?%ZJ�sc�J0���Iay�:���Q����8�<�/o���Cb�V�����j�����ap��|�5��3���8h�o6c�,Qf��#i�97�[���<k��O��O�7�k��ipn��"�� ��k��fK@s
���OF���A��������k�8�g$�=$p�G��+��1���yd#\a2�J I�d<��X�9����(�$8�w
(�>DFE� r�K�TuB����{b�\�4�)����{�	c�]s��n����:���xI����'���N�����i�9'N9Vp���p����6�Lf#�Q��((lU���Dt��������]�8	0$sd�(��w�iPI�LAB;�� ������b�n<�#��b�Q9����������0\`jH�``�asq"�������X�G%���$�}�#_�t���%n����XF��29Mj�I��p�M%8:d�s���&���?������E����=������Esh���e����j�cI9�y1��Wpb(�
�� {>D�*
�)�:';	�d��28Z��(�X�������GzK1��h��MJ.>g���SP>��^r��^c�B��9d�f�NFz�*1|�w�+�/� �	X�#�Ig��YZ���	q<y��i\�����
�j�����V����UI����_���d||fr�E��yB�2b���������TJ��N����c�����(-N)�b���d��t���Q8M�xa>]��[14�;/�N��Y���|��Z����|��.=w[�E����uI�V�@r�a����MA5r��A�����������7�p(`v�p2X-���O����b����o�������C�(������}u�$,6�j��<��q���o�t>�L����������<Fm�q��b�A��2��b*L�8����,upF�6�g��6�0�����Gm���O<O�H�t�f�8c���R4�M����Mu����H}sn#mXy$&xqe��K��:3����G@����k�~�x�Dg0��/����L���kk��JY�+��\�TO]#��J&h#�O��w�Mx+�_�0�8:�:n<u���f��i-^���HE�Z�U�G��)]��+�K����A�vr�����2��y36���W���i|Ux�]2Z�P4	���4�S���t_��
E[p�Y�V�f5��F�E��iw��9�3B����'e��C�4��\F��>6��v6�8�����p��~a2���5���G��(:&��>��J&���?�<]��-�,&R��
#n��A`�!s��`��*�{T����/LS��_}h���M���NB4����;J��M����cz@��%J�mfZ�B����H��fO���N8�u�Y�%[��:<�I�L��p���(�����@2����
���q.�ZpV`�������X�0���.�=���u�S�]�/���'bS���C�x��u�/���s�v�B�������V�[����ce�~���1���=j=�K�c/t�Gl�y$�wYwT-�E�78q=�3c������>$���M��W�}��N d�;�+-2��&`w����p�'���71�z����D��W���t��
�����������$�_��C�B�x$�A�T���GX�|�8�,���0�m�N������s2���.��������U������<�d����=H�kM����h�7@,0�]j���"�@;#�?�\�fQ��xJgQi�K��'��T��_����"�i����
���?��3���[��(���{��	������(/`��(-������
YL�3=��}������x#�x���y�D��m�ME�2�5?<~}t�<8��a����h9$k��~��������V��j��g/��k���>^�#�N�'o^��;z��~�8�SC5|bjo��NM�'*�S�������Y�7Y����PM�����.����AF�.mv�<1 y��>)3�q������1?^�������g���&3t��������Lo4��j�����k��~z��$���
RX�:��>�zj���7_�9�|L���_�1��b
?O.0?Ob
<O�x��)�Mj����S1E��,*�I����)���F31��a
<��x��)4�T�fa

-S����5�z��	u:a7����k���i����#C�o
h?��=�)���ZTv��.�M��-���K�#?#��VMZ�##���Rp�Uv��9�7��@B	��PN�W/��'�����J��i��	h����Vu����&�����Qgx�^S6{O�1�h���~0�v�<4�ed2��b�0�r$����Fd���n�l�1���V]���Ml��8�i<-*���s���_1M�+?��x��@��T?�Y�����th��P0���?-��|X8�!D��!������W��v�����M�@r#��_��fe���K������Xz,`Sc�JZ�jN�zO�B7�(�@k���+"�	��>�{!O.aM>;�I�\H�j��MS/Kv���~���{��:���<W�?���v��T�����O����xLPvB�j�u�S8���]�M�;�R����7��O��FJN-��g����W�n"c+��[T"�4����~��0M�ge��:��FgC�|�G'QjwN$M����	�-��a���G2nVj�86e�3���\�������s-���&|+AE��C
�=���I�4�:Mt�.*������?s������k.09tG����e���d���7�z��ys�y�{���������� ��n6�G^�:���E���J���g���8�[�.�l�$�w8�x���\x.CX��T�%A��c1���T�]2i����
���K�� �LW�����m��SP��T�'`����`r^I<�����m��X�A4=�af��vT����c�?o���8$�+a|��Cycc�L��n�����MQN+g��mb��K<�B�K}F���xie�������F�����y����KLr�I��O]�X({��K�GO��N�+�ljA�R}��1��}���6����Y=�r����4x�Z��v��?�Ub�v���P���]*���C���'�	�����<]�#C��$q�����3��Z��3�x7)����;���}�i��]�W<�d��N�
�}��_���������/=T����)�� �R���VJH�b��S6z�'G���
YxM���^�e��*Gk[!F�2/�!����M��hJ�R6�b+��#Q��{�D1};w�x��?J�v�����A��
��/"�kPB���:��icq�B�������>��=�LL�:��o���}	lV�w��~j{� OVv��2���Y��$�������ei�n�k�������)�/�]	�{`v�"���8s�(ar��,�4��o;��.^>��kI������o��n��i�7��/a�'�~97����o�h���\l�o�j	\7oO)�$m)��L%YK\��*}��������W
��q�~���a�K���}-7_�)^��
��{rgXwgXwgX��0�K����z�=������K����-c%��FhW�y=Fs��NY].KZ�U����W ��D
s�������'�*K�n��L��t�P��	{w��[��W�8��m\b0?K�AuZ�.��WkF/��wN��xT�������;U�Qa�����S����**������(�4r�r��ru���z!��l�9���H^���G������Ly��,�[��t�{����{�����������(�������4������w��!�3v�SOL�2A�S�F��F���VHr�
��QT�X�$�ce�6�������ti���\[��kr��a����Ma7�J�&+t�%�����bH�&J@
���I#�]?���7R���P������k=:yHMH��������3B���h���l�
�g	�"�*�i��?c��1[H���qzS���aM�#HX�X,d�iQ���
�����'ahz!mb��}6/v�L)\���rs���mo/;`rL���h�i�7�4���X��y#!qj�H��|��xl�����
�-�� ��
G�ki;o�Pr�R���
>���!���'������0���
�p����NOE�Q����YY}��=��Z�.�����kk�.50�A����#4L9B����SgC�z,��z�	��TN^L�,���E�I��|[�?p�<�xA�1�����4�H*�IZP��Ct��W� ���(��9�H�����	l-�j�-[wj���\��NOc��r�.�SA�V(G��?�$a�a��)JoC�iA�<|p������f�I$t*�x����u-Y�c��=�a��+V���d
hC
��IRP��E� �-����+\���,v��I��|q\��H�G�b<����iVkf	��a;OY@��*JJ&j'��XG�P�������B`j
�����7�Z�E�r�A8�Q<>���	�P.Q���F�J��V��b��Y�@��J:1��E�s�.-*�3���a�,������hj���_��S�e�E�M.��j�T3���nA��(�,����'�qK�����b��ru�k��OVF?��=V�T�{�Ze������`uK�i����+=���U_��x��	���7F�������1��}��(����RRB��/bZ���z� [���]_M{�
.J�������y)A��R�Qh1G����<��	����(����E��zZ���'AA+��m;{��f����h���Q��A8�t����:�����3@��"��]��L!l�
J/���y�V��=c*]����E��kTV��^�h��{�J�_�>v��9�64�U_�\�\��+��U^\�l�a�2�q��N��d�H��u��`)�,EZm5eF�3������������.Cm�Dq�-?�z,O������6E������Tq�:F�N��^�S��!�k�$.�(<|(cW�]��V$���U�����$���8�I�A��q��W�zi�-�2�����@�����
{�fl����7R��[�tv�q!y"/�=V?	o�UYr�,	�;V��*�����m�<�/\��qr��#�g�F�BE��P�dg�O���
������M����u�������@���9����b��O�/SDm/���Z|���x?	uN��w����h��\��r����i]��s���'
�������������M� 
;�Z�����j\���������3d*h��I����]y������R/u�X����g�|4���>*�6���q�b�����u��z����c���Iu��I�5)%��oNfr����|������=~�;���m|%��,���&�w�)��GFq�hp@�9K�G�/��U�����F�&��i4F?!��y�m���{o����q�S1�J<�����:�I�qN@�����
~!�|g�VC��IvF���9�w�����3���������.��Lx�AI����?���z>C0����a�P\�����T�>�0y�9���h�|3g
h
X���U�f>�����������1��9��'�����H�Q�F���|����q]�i���3���{:(��Y,3Iw�h��o�V���6F����@oS(h*�dtB���S�$�:���8Z��a[��Z.�x^'�����F��q���2i�-��I����4"e^
F�x����,�R�iV���f��g����*����Yz#|/�6�����5��G��5	���������*�%���u,a�g����}���L�����Sh��y)�IE@�N��D6�^�AQ6\�Cm'G�9�>U�����V�X�X
��)��h�z�#�}
;d6?��;NX!8��m�D|��r�:���
�>����-	�`Bq������������L���$�����a~�X8	�����>9j=�43 ��"AP�)�9"�����b���mO�Q��u�J!�X<�#����c�fHQ����2m�������s-������)�	@ �W�d�4�7%u*��5�s�;���b�x2�!��(����`c�l�;7�1���e�E��5G����4%�[�K�,*	2)
���f����SE�i���Mr�[��0'���l���N�_�Qm.����*��y��^�%��^R�M���".�[���e��=��L����]����0��#�fI�&���l���%��Y��V�n$1!\O0S!5��3��xj��E���k�Q��8��1��(�� 	Jl���l�HfB�)�$@�[n���1M,�����������f&+[�$��E�,t
��q{�����*V�Anh�J��_�nz�	���k�J��O
<hO��:s�5o�Q�$<����}��6
y���D$����Q&�b��S���������-��K��g�nd���#,d�)�?�hl4�N�
�*��KG�
�tm���"d��������������.��V�����);��Z4���_�g��R������ziP���f���I��7�X��\��a9*U�Z��(�5�`�x��|��E�L1�Qt�3G4��`����i�	k�������z��v����B�'���������:�j�~ �,9C�i&"�����m��i}��O�_"/����f|4�)O+Z�DzQz����?��x},{����MkY
���_NSE� ����oq�+.�U�������6�3��R�(K�,������w#��!���w���	dk4%\��<�` >�{X����8P�<���.D>���m��`,k��<�K��gk��g�k��,��.y`��NSn���>����\�a0�I��%�S(k�e���m&�RFZ�T�x��Cs�����x������D�A'���	�n�*FSTc]�TZ&�s�6��FI9[Z�~K���Z��z6���-�6�V/��a��Y�C�i^r���~�#N�Q�)����US��bU�L���D>�p0E����B�GG�p�2�UD�5��h@G���W��(U1�T;�X���j��Akb��8�<tms[^��uw���>�a/~��Y_:9o�JF!:����b�o>����O�#Q�o�����i�;)z4����C�'��z����-zx%9RB^���B�/�����Fs9c��_�Lp�:�z3�U�����
����c!�}7dW��O7c��A;�x3N�G���~<^�?�J!��*����-g�� 2��a�L��H�K8�%<V�A�q����x��l�|�;��FH���KKD�xq����������Br�p����y�[m�:����dd!B^����2��o�P.$����#$��J�������ab-Q#���3��
,�N�)`2�~�K�B�<#}�����D����N��c�l=���=���).��e�B�X��1�+�}��$��������Sl�3�+028�K�����a�GLe�H�}/8rN�:qO��l`c2����7���.h�<�w<�w.�������;�(t���a��|���P��*�k�`I@H���dB�t�{�'B������
�II"'^DI�:�Z�
����,J�{�)G�41�/1�� �����X�i��Z��'gM](�����n{ 3Eu�s��f��Q�?T���L����"��l�A7���hCn@[�%��p�NFd?"� x���>DFP�o�5����q���o�#��H����G��Q��`�e���C��
����N������D2�����[�'�T�b��4���egg�v�Z��fbgSv������������q��l%�-��0�d:��������Y/$G��&�/�md���c���N���������R��m����Fc�-��5H�������Ab���.f��E�i��I����4���^���������t��!5���XKp���)��A�	�f�k��,_����$6�k �3"�������,;�%�7��b�C��i�8,`\�|k4�/�S2s,��L+|!,�������j0�U:	+���]������e3�y�h���LS�jjKw"�F�����'E���f��*Z���K�� (���0�&�������Q�-��<>@�
q����a��I^���=��������TL�rK��v���1�O,�
���dp��e��N�^wVz��Q_���?`x ���ga�B����
?��-{�������{/�����?-����ZQ�J%VU��k��%�A+��h�����S-r��*�gx�g���pb����;�G��*s��Nx"Oogt���je��n�U��M�o7���7�tQ����ixQ�M�k����9&(������A���,�#C�����.��'�{���~\P���j��c��&(�X~�6^��3c��Q����ea�i�d��P��F����?��c�]G�b��f26!����\|���Z������)�������Bf,�rS�k��}��!i����a86,
'-��s0������g�������p0��������u������Ob�	��j�z"K�w�U.�\��u;�"��f��N���m@�ti���A&8��kbB���fp ���i�����������B�#��P�� �so�c�����^|�����~dIc�T4^����-�<�3<��V���)������Gm�=V~�����S�yr�K�h?���B:���	����q8������q��9����,d����4��d&��'��7r-y���L����L�)�sUT���k��f��mJ�Q+o����e$�����N><{����n�pD:�<(�rQ�R����������<�o�k
Q��b&�,_U]��|3OAi:��t���_�
Fo���'�����Y����'��LN�Sw�$�M����w������NeE�OW�O���
���Y,�.��N�yM�Sf*��R{]��l�rn����.�k��v�d���9���x��9gn��R�Y����m�r���
���;�:�|�#HA6\���������0z�&
���xI��'�wO�FP�*��c���5�����f�o�h	E�,����8����&A��h�������F��z�qH�6������1i.^LP;4�R�-r8'%:�L)��BK���X@���"�[�����L�R_@�#��uhc.7�=�8�8$��&<�z�me�
UAu��
���>4pek"n�N12�xE�E� �����Q,vn�����Y�&~�_���F���KI�B�
���)�LD�S��-���Eg�w���hZ_��#M,G��5i.|�����p�>��C��v������Y�V��Vw8�P���N����T�������
�o�V93�:�5��R<�~BfT�''v����D�:�T�r��@J�N�R�0��Y��"2&>`|�������\-]�bbH�k�e��!BJ�=��ys1z�����@���q���h�/�G�[�mEri�q��� ����:�z?� �����w�s�x���ccStqy�*�Ua2��u��u��T�!4"e���,����$�9�r�L�j���t��:��A�d���t[4����K:��%����>�{1���]#�Z�L��>,����An>�D1��x�����9�������AHo�eX�C�r��,G`��3��$Iw�Kt��4���r��^�V��rZ�o��\=(��{���6�Y�oJ����p��.���m�ww��	�Y������`��T��Tv*[��������N��'U�Jg�����W�O��h:��Ek�1����n���:�N���������N�����v+[{�ve�����v*�.HY/�O9
�����������j��������H����qH��&p,����n�$���z6���z�����*��������(�������<T�xupv��*�=�T�ID������m��r���$d��S�oN�Y�l\u��R.w0�o�%Gi_{���
��iH��� �/rny�;�)�����SbBy���S6��r�w�[�!�%��w��W��m���2l����T�K�S^4m6�s����/����:�!
D8B=kI���i��rL��L�[��m�=uHC[��<�9n�N��F�b� y�Qg�c��������x����������	�"MB�YHf�������X]�mj�����
J���+A�!4��H�.K@Sg$����[�t�77s*���`��`�X����l��+V����)�T�����x��?�^o���:a�����R5�%k�����
z�k]����|Q~���&Rd��,�r����yY�`��[�J6u����4h�S*�����VhBygkA����|��P��9J��[��x<��4s9������A8�����(�8rF��@�cS���'��}q@z ��F�M�[�K����	�#�!`4���)����M�m���(�������v��E��0M[��
3��@F'D�0��@w���/Nxk�Y�0/�w��*���
G�M������p9���]������������l�}��HN�v�C!C�t��xU:��eF
��z5.���h�d^2�zeG[�����7���C��A1u�}J���c���V��$�~{�������[Dd���
m
���=���~��l�����e��l)�G�c�f��8v�)=���:�W�t:��E��N��.pr���,8��(K+j��z���WO��q�_�q,�1����R�}��|��:N��I�k��"��jM(���IzE�;�yAOZ�H���{&�y������x�>>���������������K",��*���� :��[���q�l3&���g��~zQ_'���<;u�,�h��+Q��m�(��U���
i���&8���g��D��G��vk����Nq���i�x�����FKi�����/�����#��MLs�|�#�����N�k=s��������#Grm��O�m�U���VL���4�P�E?���e*��Eo�>J���[�d�Z}�`�o]���Gd�k���-�����2�lO%E��.g��#����W<N^Fcs��t���{B����4�HZG�q$�<��t6������!����>��H��
� �(�dzVRkM[T0q0=A�ehsb��Z���M�LO����Mm��S�l�k~�l�E�u�{<wg�nS��clt��~F���`�g�)r�F��8:�;n<-��
��g����K�@Z���^,�8�-��d����G����*��ru@�����W�@���p�\��<k+��V���L�{��`�P���v��A��B����f�������NG^�a$�;e
S��]�{�8!Q��T"��U~7e>$n�Bz��p�����5��i�Ah����:�|�}�f�4�����ns�i����:9�c�D�u6�������mP�����;�-�B����
��'�OP��N������j�%�k���nNk����>Tb���R\@l*QLL��6@V����)�y~����o�BY4z�{�h��Xa�	�5�#�\�����������1#�I��MN9����H�Tpn�G���� ���6w���I�����#��S���G����PP�J�J<(<Z/'��;>yZ?���uX8�Z��h�y)�����!��a������N)���t:�9��3��������9Y@���<1�1�Vwr�k���N�C����~*�(��B���G�E%��;�`	c��^����i�Q�����!��\�� `���	L��-��fXIvw����r�Z��X��
��� ������&bV��gXT7�	y=����5^4������������$��<��>���.�L����0(*��~&���59�y��j�K�}\���{��{f���P�t�Q���(�^�K�[�a�P�C,���gx7W?����'���g�-�D)����M�~���u%���nf1��5��>-`Y���X�����y�&��!�_7
6(����g�l�#�R]�]uZ�����
">��o�D�>k��.�CN�^.�
2!��2���}45��d�jd��S]z�	~��[u�]���]�b��[%�d�����/%H�z�������X�������U
�g#�X������W��1XX0cu�<���E���������v��ZW*�r�v��|��;�L�g����
�k�U�(sP���ZaK��7�t��Xw���R�8Eh�8Z���#��1���@����oI���O|��e��z�X�9]���3J�%a�M���B�48@��VX�
�WZv{X�)
���&F���	����e9+i������Z](��1R�"���_J��J/G[$��:�I��u��H�d��lAwFY����}C�������.����%����%�����������.�
�����7��J!����{�p���R�[����v����M�������o�X������+�� �����RN�a0l��W���^��t����)�(u?v�5J�(�| ).������)w�����|��t�4#�_E7B[�M�:h���&{�:\�IUl�Em��������������m�.�b�3��p�V_5���y��Sh���[>{���+?����������~�j�i�$�;�0\"��L��e��]h%����xD[\I��^�������`\?zezR�[�l�����"������6�%�tg3��7]j^}'��*J��6�����]�?���~��#��wTJ��� ����bY>������j���]>>�o�l���7d�nJF����7�R��5Xg��h"�:��SGA69����Eg���
e|�o����>�����n�X��}�S����-�
�vgoim��6L�l��DH��x��$a������	�hU��n�k����=���z	���)e���(RO��S���wur��'VX>|O����7�>n�����M��=��"N�#��n#h�7��Ogu����� ����;4�����)�J-1�(.��c�Y����B��P��a8��zc-c�����X|�!��7�����H��Z������V�@�s�'�������1.�3�Ry�����-���R��s<,ZOC��i����Z&n�]�O�=7_V����\�����%I�Xc��ql3��Hb�\
9����P�C)��]2��j
�C}��FAk�;��&�q�b��Gg[h�ay=4�p~9�<��J�&��
�^��9�a�-F�}M�SkQ����&$�V�I�*��5B�w������bL��Go����l�4��o��M�����A�e�7_m!t�6I������HZd�$}���)�Pf[�3L"3�~�e����GZd��Z#�"����7��1:L�A�I�m�:P�7lO����Lcw(��d&��9�� D��
E�3�KA���/�G���4��Z�I�>H���O1���m'��A��:�XF���22g�}�z�j ��q6[4���m�b3�b�g�9}�$�g����
�7^F��|IE�g�	��~U����6fC�L�b�6�nN8��iE#:�'�
���+�����;��F�>�A:`�I7*���F|P�y]D�����J����^��No����������i-�j�����.�G��Ka#��1�hv�|v��&c��K�]�C�*�{)����'j��Ye�G|q�b��e�~:>~�z�HDJ�ST>�	_�D��_f:�����:>������F�*nt\������i���6C�LEg��8D�um�(����3�q��6C�m1>� ���G/�O�^�����)a���"���U��8yP��,��yqv��D���}�<B�����q�2	)�p�=|5�[&K�����7�6���
����#��2&������k�� �����)�2��L#�_�u�P?��8UY�B��%s�L3A�����m>iO�:���ya�,��	F?�+ m����}X[[�9%���{�^�I��4�������1�z�n�lOC���BL
����N�	2�}���&�~�a�9�(8������������+�\/�(�%���J)���i��Hd{8����*?����L���D?��97l_K�*�m����s](����{�2}���a��oL�O-w�q�`��#F�v�A�(Oa�b���Y@K��Ey�G���1��L�����C��Q��	���l����Lo|.@w04>p�&-����T�
��{cS�*�+#�Q���=�>)MN����O�C���(����s���c ��B%��$����{��YT,�����`m��Y,Y�7q|�nl����O�x�������m�O����{��`����P�b0��'�/����g4���[�e`��ct(�����dA�	A�hbD�����>�Fc�ny�e�O�<-���,I�����2�)�8D�_�U
[^���Nn��Y��G��X�c�v�St���W�>��H�$IG�2��ZN:h;�Vw�n0�O��6/�������1J�*��\m�h%PM�O���x�36~�T��#[�������D�9��a,��r���^����qp�:�R����J�~�Q:aG���M��v��&����������{�����m�n�{�*b���l����&p*�}�/���X�P[���������j��LE�����O/J��%9D73��I�H��@�0{�j���0�p)����*a_��\�X��.����!��kW�Au��|(��,l�J��������=����kcM�u��$��/H�#c����O6��0��������cm�����~��-����n�kN�kN!��-4���#?���F8��`��5��I�<N�im|Q�x��~%S�Ld8f���A����kS[\�%[B#��<�5��p"��)m�{��+m&;��7�o-;l�������SS��%�/`�z]9�������t7��tw�����������&2����R�U�����������G����J{{ok���n�wk� ��T���n���sa�U�����U����o�����������rO�����s ����pa��t�n�09�7�*9j�CHB����"�=u��R)�"��|/*�0�!��;R����m3��vF!�����\�C|b�<:�L+�zm�
F�3�������������f�<��nA��������7�|��P��R*��I���\��9��:����X�L�(�	qF0X
_'+��0'!�1�_�+��X;#myT����Z7��%�Ly�K}Pc�J@2�`�r����`I���os��G����z�^?�0�X��J:�1�d����\�%�.�������<���0�bb����Qp�KFL�dDH=��I� �|8;�X�hk(8;��}T$1j�i�;�����*���wA�����a�!_��k�is���zC����)�k��t:�>x��}�h[������Ag�&����j�������&x��,���6u�A�a.�������Hj!��"��n� ���o��kQs�������V�
R�7�C�������5�qJ����N���c"MEp����F^0������eI�]2"&*�r�0�{���@�9U��������������T�ik��Tl��
�Y�/;%�c[�OP�5A(i�p�P�M�W���*9�9�7����\�R]1�%��@�����`����k��B8j#!����4
�������|����QBs�Fx��8��#^���8j��$>_,�"�-������W[�Y�����[��50�����kG})4Y�C�U��[��!3���da��
�W=~�tn=:{�YfN����9��2�t�Fi�-�0�VX������dl�`6�����G������g�z�%��G0�a,����t�
A��99�����'}��0� b����I��N�M4�$��:hOg,�I��	R��v,�]W��N
�?>�
�
�:fxi��y7�O�;N��� �$��fDrn�M@��1��m��#���������]k��2��\H�>v������r_A����r����.;z�9%�}�������6>��bi��X}�'������������w#���Y��[l�����q������fJ��]���D���L$�]���Q��+���
��5��rlK���i���8\���Y+�d���WM	��5��1	�qo^G�f�
m�,e@zD��x!)�y���,�G�g=:����KfJ���F�����5`��*\�����%*o:3
L���)���BR"em� >�.�@��4&��d����V��K.@}'y^TcX-��u��_�}��=^�hw��7�%��&�_��J��v���{��Es��!���N'���s��I�:�����������;km�Ya��IW�M���$��I�Pv��8�=F����	�U��+8�==q���A�iz��b&�k��������]l!�g���$��� [�b(�B�RPw�uH%E�s��4vx�x���O��{u�&���r��
v�ciuH����H�W�g����.U\w���~�LAFl!E���]�p���LcC���y�c������g������]��]���k)��O=bg�������:~J�Io,�%���
�������
r7��`B3;���w�z���"��;�;X���`�8W��*;��;{�A�~p�CS�G�i���BM�����-���iP	>�H�Gy�;�"���X��e���^g
9���^�}����)m �������e�+���\�>�=��������lDh3�Vo�h���8)^���x��3���&�$:U�|������e�|�4��d��1,a��E�A��p����+oZwZJ-h;����`(��~sL�0�
$U�t��,*�x!w$��N��U�|9�8���v���J��_i�T@�#n�rh�z�^Qq�>8�X;w�7��N������������n����^�'
��H�w�7O"��J{�Zgw�uC!����6�'F���! Q��<}�^�5.�cb�����A����;�p��������������DG��-$lH���Z��b�(<\�!��������6N�N���|�a�(����<��{v�������NXf�Y;����8q$net�a2�D)�B+8���{j���"{�:����dr��B�h4����`�:��1~��B2�NY��0��n�vU7=R�M��N-9�X;�8����rFp��G{�����p��LND�
G$U�zv�c���4�l��]����E���1�Re�U�6�3����c�p�v�c��{0�A�L6nX��;�e�>Z>�|-|�������ad�"w�B0��Ir3�E{M�,D�I�����)�<�t������)
�v��:�i�iJ�r�������u.�N�/�Z��lx���n;0n��������$Fh�D�$�z6N�
��v��(Q-��Q�A\2�������1k�F���!��B����y�~t��]����f8����������7
�����J�)7����h�����>]��'��5|�Wzxr�b�o���'����8�AjhN6��>����q��IX��T�_Un�>!��&����KN.����q���e12Vl���b:�����n^�7�1��Y�����1�4�lh3�}���)2�9E��<��������������-O�c���[�f0F�dh�������*��S\��@�"���Y�;��b��� �f�jx�B����5@Uk�����n�w89�P��I�*�C4,?�L[���*F-���&!�w �P��(��A�]#��=��\�����B	�^�����~5�p�A�NC�j�gd����0���}����xh0Y]8��w�`�'�X�z��ShEw�?��lj��&���%}>���N���qx�������"������<��|gfx�6���[��5���[ep�l{��A��l���E|Cu���*��]��Z-�5k[�exd~Z<0y�I�#��n�d�ZK��Jy�H*��m�I:0T��1����),
�X�����������^��^�5�.�Z���*��-V��"h��}\��(��#�--���}�0���'t�6�8���}��YF��u� 	S��a����O�����������(����>�4oA��&�$����e�RA�V�o�R�-�r���l6���6`��b��n����\��	>�KQWN(�
�2I���]�f��������X"+��������r����6�������;�ck{�X�^i��1��b��<�g)�8�4W1Y��$V:lkb���D�~si'�kS����L4$���^Tb{y�)�$����cH���J9NTxo�RO>�F�����{��x�Z��ta{f���7���xJ�pRrr�-�����������.1&h{���#��g'���R���z��!���:�;y��G��IP��O��b�I�"Pk��Q��������%����2V��2�t���L���v� �U�E|I�.OSe�N��~"�|Ey@�>_������"z�:���{�w�j���H��=2������d�o�Z������2Z��K��X���!�v���t���}���@v�����u��T���=���i���7,(th����������su|"��,l��g��i�o]������^�'�5������������W��e�g. ��t��u����:x�B�3"��\W��P��f��+�/P�|� ���wu�y�>�����,��SZ�4%���;"�h=���$��P��0����Z/�c�K�|ec��ns����m�|>�h������#��1T��t����� 0�Yz�mQ9��CA���	�-����V0����+=��;�� ������o�v4�w���'BH^����8���$r��	N�)���V�����U!����jO�����>u,��5XY���'9rf�R�n~��`�@H3����!|�Z�/���|~p-u=!����^z��`�C�ls!��K���tI��Ea��[������ob�$�&��W4�9��6���E<n
���-�	!=�
����jV��Y�k�|�>Q�I�f��}�c�%���em��_���9 h�*�:V�p���d�o�&]
5W�(��_h-rE��PE ��*�o���+_��9�����~�X����|����!=��?�+����!���JW �����/��q;d���Z������PS$g����J�v��^d#ok�I��5������1pn�a���5�p�!��9@N���5I��K#~
{w�(�Pc�VO.6�a�t��2x4A{�B�G]n'i=PR ��	�������D�&R��dDI���"��������0fy��L(�w���%�!_��Z�X��������������c�F5b~1q1� Sm�Qbk�i��F�?��*|��
���FK' �!Tl8��`��Q�����~S��dA(�Q��(VT\�8���|c��8��XZ���zZq�=Dw9����YR�-ql��rW��3�<3�����D�#c�&<Q�����JF����I��n���6���A;�U������P���Y��:�G#RU�{���e���Fc]W{��JSg�~�}�2�=�����Jj!\�,��W�Skt]$��%P��1KTr{���G�
��%�'5T��S�S�9qoO���^���DG`=�M0*�/5�`�;'�����*;6�� 8����"wj1�������c�G�v�r�)7mm���w
����n$�0���J����c��OQ�G�*ic�����L�t�><�����1"�x�t!�>�ld������@�S9�|�3�8���T��B�=���9>o�0BP*������H;XEj��G��b:N����t
3�.����P(���k��.�����S)�1�Nu��fo?q������/Nw�a���{[D�p�r��0�%���[y�5����I�I�������1�(&l�'�oO�����5q��������w�-��EL�G^B5�\�J9�E.[Tn�x���_�I
�y/�JAI�&x�#�k����`@I��@��f��=d��4���|hI��c���~j��a���m�sA�t&�f7���r~��5*�rs�YAqS��U�z����H�4�P����A`�xR�����Nd���� ����\,�+��Z�)K��a��=�c/T|�uT~���5��������n,A�����O=�������?��
$��H!���4B���D�K1���
y�������4�xb����k8TQ2_f���������QJiP�����g|��
k��N�a��5���^e��iW��y�:���}�����\��7��AB�/�U�`�GY�'�E
�P�DB�#��p6��{���e]&���q��14�n�Tt������/'��{�Y��9f��O
-�/���$��p@F�7���{
�4���DK��-=d\uH��c������9�$
~��z��������x�B\��y����������X:Ym�,�aN���X,���C"X��Y\�n]�
�K~�P�#��E���k��I��]�.����R��NGD[�6���PD��P��nr�!��l(BIp@����A�8�o������t��:L����}��t�x8���C��,C�_[,6��P2ui�=����vo�8%a]��&a�=l���r#���=�-����#M�O$&@Nk�AM�@Y2}&(��\%�m�o���h�yEy�w�(m��v�����C�������DL�K:�55H���}���~_�=��3��;k��MqC��a��T#���#{	s-���n��k,FGy`�����
����gIH�k��s�$�JQ�$5c>�����(�3����s�h`�����J0�Q���6G���<'ld����.�B2���s2�����{8�����V����,+nT/������aoE���4��9_�
�V�V�][�����m5���2�k�� 0.�n�e`9txm����9@i�����T;�Q�U���-'��_�^���,On�v�J�T���e!���W$��z�jN��:�x`��I�\I��	w��L	�+���K?^������bT�"X/�x�G��&�M�?���b;�W�T�i:�P�d�\F�5��+�{��@ZhO��Y�`�dN������&���X�_We�$W�����\"y��M@z���'V}>,�J	�b4��8���-'�hB�I �B���v�����f�*q�
�v%5���v�-%>���8;t��m|�������uh.���HAU�=��1�srq��_E�wj�GO!����U���Z�"7��J�����"������w)<t�s�w�H�)�%��(7�����[�`��Y�b�����q��qj��$�B-64��z��6�>PW'���96���x�
xN� `8V�
��������_jSr�������?���k*+|���#���	J��H������	e�NS{Mbj;�@#r#�\!��gf$��H;P	�_#�����b�����F�I�)��T�X��E�sh��dH�5.���c3�;�
<\A��8mF���J`��o{�A>����X���V	a��E������H�U�
�b�U���F���m���&�DK��i�R����(��K�t���l^�J���#�C(�����,�e{�����(c���~����/�K�'{��Wfc��iS�1�=%�s���B��Mjw�����+*,���]���d�����l+�M~����O��������0?�?;x���yx����I���w/��'�!��s���P]�X������D�|�
���S�2���MX>1?rnY�'����S�����<c�M�Q�(����S�����J��x�\�EIW���c�}��n�_����x�a�����b�f�*������0�A1L�}u�	P�����Q�����8����'<���F�o�f�~���g����	eM_k��	��t��f���B������sx\?9�=����a�L���������'���������/P�/*� v��n���/f0Y!�F�7j��cT�.?����_ ��O�;h���0�q7V����7@�L:ZK��.�A�����l�=�^��e,��y���iG%G�����M\[�n�IEar�)�'���0��-�CQ����m������w�t�g�(L�o�c�9��b���<YY���V_�����"	�����]����9'T���=q�-��[��;G���U'�#��e���&'�)
��[�
�Ln>���E���QdM����L�����u67�������c{��HX�GZ�tQ�|�t7������e��'79q��3G�>�y������������q8��4E���=�*�LV��e���t�%�>�j�n%��x��K���:�=����Qk�	��Wk&RpW]#)�j�����#n��9cq�����#�P��Mq���7�|�!��:�R��ZTN�E�������Iu}]r\��d����0����I�.^���F��M��(���D���C?h9E3%��L��R
C	��m�wx�`�nd��?*!.
5���U��	?�mN�P������o@AN�'g���%���^?�<$�G�+�($���h�c�2bA-�x���+i�~	����P\t������ �Uk���W�)V����u��J��i�KW<o
7��S�������YL~�9k6��|�^)���t0:l\��k'���\	����Ec�7�o$_aM�.�d��gN �����lZB�E��	�S�:��a!���_��	�p��8/��4+��[�Q�~�]L��pl��dd��osL�B:�A��i�m����r7�Y�����bJ6^�(���u�Q�>s�>5q�.��K� I���E�p� 60���]@�������vKY�/E�:���r
�)���hC�)Z�fl��J��J|�2��+x�8�(H����������!��@��j��t;�"Tv�a~�^����bj��Y@�*��LQ	X6G��G7,z���9�M?&�=<f�5���Z��f+=��
"���[���L��.�`�c�k�����
�,	��7��p���]�y�X�Pv���}D<
��v7�zvMi���>�k�����*Q#nC���H�O4E$q��%����
B���Dr��QB�c�M���9W�.�3w(M]
.J���������I�B(4���������"�'�5��GO�S�$r�z/#J��P�U����9q"�%u������F�6���u}%��e�`E#i}u&=22P�d�)X`�������'EJM��$����``�B���gz��S��W^����,���;T����U��n>�����pH�-�m}�5�;.{��:jF$�7���:Op���Y��Ve�3]��n!b��L�������� ������������"��K[ur���x��]5��
����u`����X��0S���u��*?���
b��(��1"Bj�����.�s.B�9��s5����3j�9�4���y����2���+���y�FS�(��I�t��}���;*��z��1�<%�l�7�h���Xk����MYDK�c;�p'�������������0�t��q���G�,�L����4�/��j�J�03��Y�Yg��j�������yQ8����{
��q_�(T���N�����_=���`B���`�41�P�VA4�� ��r��������wO���R:�x9�4���jr��Y�NgcI�����_v/�u2F1��Z��0�/���b��Y3���L�����U�����R-�/Q�-���l��u+���+��S8�e �����$�&�E���0.�D\�����2G�
'���wa�[�9Go��M6��n�s-"y�1f��=�
cp�}�q�_
���O\3��8�����$Zh���i�T�5:�'���Gz�mR���TA���O�_(��x��'����2�)Sr��*W)��j�J.������oL�o��A�\)ot:�
�d�
,����n�2ls:�G������>����[�O����nuG~W����*���Nek�Z��S��U����*����4���J�	�����h�>F���������������r�S��k�����\�������[��V��{�V[��s��ueW���?U�%�a3�o���:
����qH��&�(�J�Q���IX���z6����6|T�U���Zy���6���r�tF���?^a�|x��R�'ys8��=���VW;R����1����R��plJ�\��36����Iws��������t�k������U��6��Xq���U���-��I�W�u��oN�j�
�Q~�PT���>�or)y�S���z�3
���ak�T
Z��ZXN�4�R�I1������s#�$�.]�x�y���{��K��|6*fj(�6�Qao�y/&��O�}������N?�����z��X�����c23�EoG����������5�{�'�])_yB�
X��Q1�|D#R7�K�vE��9G��OY���9F�������8�b������>�wO�_@	��������g��~����ZpA�e�_T��p��;�:5�X�Y���m�x$�_��"��;��F�G�gL-=m�c��J� ��@��v	s���f�`����~��n��g��e������U�?���?�q�������S�J�K`��@&8&<�x�A\3H�;F�F�.p���=y�*5<L���5i)X>����^����O�)��h3��IQ���m�P����'����v�	B���-���1�������?���d7���l�j���h_��������$��0c�7n����������E�p��W{�c������6X�:+����{<l����7��/~9��#��)L9}P|�=T�!�@_�9�oH
�t�Pg=^�A��;�Z�����i�k�&����Vg����r��J��,W���>w�L��?�s����%���0�t�&�i�)��?�	����^v|Ro<?Rg?���k�#���"�TG�7��t5?�6M�#������������O(��{�qtxRY?:;x�7�'�����?U�%�>H6%�%��������<��������hU�X�uq�������������S���e�;�O����O�
bq
P�q��a��Hq��$�����okN�)���D\z����Pro�
�>��|�2n����������_��q6�z��	��vE0�Z�����������5�B���������9{��T^Z�.L#���FeF����������*1�W'g
�q��o<�X����$_4N��[�T�7U�
����p�I�K�X��o?���
b��W����{��be�z�5��A���g!3�LYN�
;�._Z��;�y�R�Y��n�S�K��-i��~�S�	�2����S��"��`xI3�J�|t~N�W�b�$�p=8�&)���o���h~�>����ze�r}3�Zz���@����$���r�����_�� &����4���&����K�����j�Zm7������w����#��vm������V����[+[;�a��m���V��m��T�����V*Y�����Y�T�������Y�Vx 
<��08i�5W]��k�+������������$���~e��������OUm�
&"����R�V���9�s]�����������k+F���G�2:��"����Z�3��j+�Z�Nc>"��Q��+��T�l��v%$Q�A'�x����X�Z��
�����H����6���	/�~#4�e{�V�3����x�L�)�E0��f��A���
9�Y�\4���`�j��
#���-������inC�|�X)�	|���k�X�_��[��vq�$R;o��������bz�����J�f�����^��K�
� V�����^�]�=_�Z���Y�h��R�OA���

Y$ut���@�:5�i��9>y
��(V)V����d���u]i�L���:��hL��:�
����k�LzRvRq+9M�	)�6����V���yG���R���t�!w @c�2���
����*����|������o[�s+����d�eC��!A1@���m�T�8Z�;����v�+����s�n�<��<_�z�(k���~����������H]�<\k����W|_@���Y�+N�����/~:8��W�/��`-k�d�{���4� �c6-�HF�3���x�p%�wV���K1�����M�d�E3����Q�<�����. .�;Y2Z�2.�����l�?��O~�.Ezz*�tk{��	��yG��z}S��M���g���b����|#y�>:���9x���wu.�E5������|g:���#@9���������M������C4����w�����%�����4�#��rR�R9�'��i�.WM-�4���.WK-�����.��ZO>t���r.B�����e���	���;���Z���v����:7���C�B�Y��������Xz-�^����4���v�7B���%>���1d����'m�S8���EK.Or!� �A��ss��u�-q�'����*��;t���+^]f���P���l���S��N�.��:�k��0��Up���r.�D_�~_>hA��/��>�f�C���������,����S�vz��G�5��u�}{4A��+JF@B���\��	k����2[[�_���z
U_B�����[��5����6�R-���f�-W�)���_��,����<Z�;�*����O���#��_��!�GW#����9>�D;{'�]IJ�������}b��%����[�Zl)�b �+D��|�����j/a�|���=������G�Y��3�>��{��U�*�o��1@Uc���J0�O��,� 78��Rs��6����k�+��p�\ai�(eV`6	��EZ���}�w�O���A7	*��.�y2�g���3��H������w���6S�>B�b�H�������������I���LU���T���,;
]T�c0i��R�(�i�t�p�����oUt��x%ERi=���4.p�^T�V_Mg3)���%�5���Z:��P�K�w2�����mJ��xxq�b��WW?O �*����8���)�f��H��QsJ������-�:���>9j=W|O��s��NLT��;M���'���2`��:��T���w�Vq���4���v�������>w	���{V�\��A��1Z1^�3o�-��C4�`���I~�7�$�q�����=Yfc�"���a��BR���@����I@dL/RvN��ww����}�l���2I��7��R��$�]l5�Mj���D-s�����l���I|����%v\�����^�/Zr�����t�z��J�YI�����
�����6H,;M�^��"��W�
�^�4^����V�-��&�jJ14���RBChEg�,�������:>�E�'Y;Fbp�P����%U�-{VOz���^|��� .b�p�.��Q�]�����]��{�?����o�]��G��O5�C3�����)0�'~���Xh�	���:gtz$���Ww�D��f��9b]�p5F]���v-�����9�������s���X@�V��-e�������v���lz�o|��E������6���Mn�_��o���Ub����c��t
c��t
c��l
������@,B�m�G�6������&��:�W�����ZT_s�T/W������U��T���G���n�E���0-.����Ja����P\�]������)]�����j�e�U�o��	�r`�S�w��[� �\�=��d����W�����W$�����?|w����%�N�-]�9����������T�����
�N���(��U��e�&e�U'��V���zy����nC�r��� ���2����d����@|���5J��(~_#�|�����2YB�����U�
��IEbl7�7���F�iY�R��)M���,Oi�a?oQ���1be�u����h-4J�z����������+��h4�Pf��M��J(��f���"���V���K�O��e���Ds��_�/����4����������\[RW9��
���v���K����< �;�KXT�"�U{�f_���������+/�T�[?�z����nW��B����d��_�l�(��>����f��EA��5M��������
!`m^@�����V+��jW��+.���[����Wd���`R����2��1ym�,��4��D1��mu�nk_�����vk	�D'���96K�R������TO�	�k�O�S�����~I���#qe7Q�u��0�05FQ�kk�SE��as����T�/N�����Xoi�s�����3x����9A���FOA'B���t��{�����������������=��:S/^�N!��p��>�� 3��66��6�,3|�c8�,������?�Y��j�����T�����R*����������&pD�1����q�3|�����;�y�@{����
��Gh�bO�1�|-6P���k�wG�����C��;�g�4�����\��F;��&��v�$�P{[�Ot;�����$��H�o��(gj\�G�
���k5f��j@�9�W��
K��8�_q{���eZ��E�#�(���@[��Ot���1/�d[��d��W�d������h��R��"�r�K�k���W
C�$YK?�.8W������1�N��H��o%���J\�J�+�����S�������O���|Nl���XR�����X_�yM�>��u�W�����?Ib�W�����pi�������n3��������"��\������@���������m��9��u��e����GI�[�����CILS��p��X�@����/����%oS���kL�����:�&Ewz�`xB��6wEl��4���~6���W�k��W�Q�uq�w�������3%���6�v��Mi*S2��i!oq�0���W�=���������Vw:��A�7�����J��������/��V�%9��]�d�����n��j��&���uh��#���Ex|KJ�l�����X������8�/�3��4K����������'[7�����k��
��<�W3���}��:�����lloO��N�&�6�ny�_PK��,����5]J���%�G������f4����������k3\�V4�E{�-����;
����J����a�	���<��uA����o�c�����N����5��IO&���D���
H�7'p��|]3���q���",YG�����j���W����W�����+��������.��+V[��W��I�t�����Tm|eP\�d�&]��Q�������Y�����oY��U�(C�3+�����!o��\_�����+�������9'�k���RT�M�
G�Y��oU����n.>7V�N����s�R�S��qUj7�v,u����}sZ�o��g�I}k�@�b��Mh�n�	���N�+���ER�y%��>���1�%C5r�PV����-w�5{-��
��|�~EK��?7��M��}e���	����_���������l����Se�+����~�j�7:�;��?��o�e����]T��� ����mE��o:*�������Pt����um�oA0��q't�	��\5�@0�oY�����,�@8��qo����~��A~����.��-��?Rb����r����f�����5���.�7������u��
|�e���u�m�[�o�t�%��/�_��(�goL�A�.�����O�p�D�:���P�,�j��$�~�����.��,p����;�����5�o���?��b�yv]��Z�7@�o���xs����f��XY����r�5��xw*��|��9�z�����)���Zw��oB�����B�?C��o��j���7�B���oB=���/YfR7�Jrg�����)�VB��Q>�V��,5�MGY��/��x��������������6��-�r��������)�T'w6S_/�>���L]��m(a��S�������9�/fe�y�aV��i�Y	��Y����n+�J�b�������Wh�H��(+n"N������r�d�S2|���}����[���%_H���������&Q�����}�3��G������$��^lG����4+w�-9�,P��;�,������8�,���2+��]
���e`�I�z�����{��ee�-|�GK�r������X}��l�w����q��P�.-��>�?����������g]�b����dixn8���~�����(7nB��S�E�����z�>��]>���������<����f����R�r����e���	�w��k��V��,���CG������X^-�5�^,��
����yu�-	�Z/q�@�yr�@��8��8���1n����3����;�?����'|�������yV���Co�V&���(\M�O�]���pxR5w����r�mM�$�����'lA��t��4bn��ku�XnD�F�5�����zg�%3��a��3D��	�\�*�v����j�Yv^_�o����/!+�O"m�����Mb���>��A����}~�%qg��#�g/�;�/	>u�_�������uU�jRMa�&�,�h��^k	&9��jvyn�z�%���Yn{d�)~c�~c���o��-?�����[n~��W�����r{Cdn�m#��*��"P���n�yi�	�h2KN�KF��?���@�����TE���?
��h�i�����6���
�vb}�0������FW�~i���m�`"*���R'���'G����\(B�(Y�@�z���i�p�$�-�V�C�X�
��,��&A�,���7_�`h�nL�Y�f ���
�P����
UB!w$��IF�j4c������[���MM�|��u��F��_<c*@f�r7�kt�]��&����=<q'�����������h8
�u0�|���������F�h�m��)�0�/D�Da3P�����T7�~���P?99>y��0L�MT��K�q�Q�G_|W|��^�r�{���ul���v1���������l���%,�����(����^=V���u��C��u�)������O���TT�<,�����b~����\r��h<��z��|A�UH��j~�O�8eD@�����g�!����.m�E@�w�/�:�e~�����#u����i�0������]�0<W�I����i��v���$p�`����:V��Lc��_�z_���$��&���O�s(���MZ��,��H��h6���EV�O�_�BI���d�p�����0�4 D���f���za�*N��\���E�{���r�m�����Vb�U��J��[�������\��z�D��4���l�,(p���A����h��;^���uZqU��`����{;�������0��B`9��^NYY�'T[fB,e�Rq��G��@9k*g���t�)��c;O�`���b����3��	l7��#d�%i�u����g�g��3C�F~`wq/.��������I�p�
�\��zBZ�^����vc��"K9�Pi����������o����o���f����0�,��Q���/�����K�\iZ�j"%�Y=����AI|�k�ZM*�N'�^V���G�9X��rx�;�q��=��Q�@���:����+��2Cuh�:<~}t��_������S����h�{#���`^���_<;�'�f�����tn{���������`�qiQ��$a�vb�����\�$kK���
�p�zY�E\�G�3����� X���N`)F�|����p�������oX�
�NN���������^�i�//g��=;��%&����]�I����l�0����I8�yxpzx����6��lg2�t��H�����!����m���g�iE��L�Y�m$
3�k���DSB2Sg��tz����<�MU� ���>}TgF��q0	������o�����D�\o�	?�N�U��tJ���~��]��)�lm�6Q�_���������_���^qWm���+<T�	`�9�4��x����C��
{�&��T�g�����Za��>�]�+AQ��r�������|2����L����b6y<�I�6i�6����
��n��p���_�q8	��v�"x]����`i�I��?��pt������ �P���k�J�iw/��%{ C���W�i������EZ34���aE�����%�SM�S��60������Y�A�P;��2�cE��v+��WJ�`kw���.@�x#H/�(]�T�;j?��H��'>��������n%eU�U�~��O�1@4h�:P�0�Gt���������J�<��sCX�.l]5uB
v^�2��J��n�����x�	/��1�s:�(��bek���S�p��a �x���( �=V^��@N&A����&=��i���0�O�8���������G!��`��m0�?�a�|�U��V���[�-�u��].<���e:?��V�����R���Q���W��e�������� 1�*��X==8;H��n�\)V�hvRvR?�y��Vh��7��^�� �ah�O����P�%��S�5�:|��)���	Y TV�no��6q
J�����0���e�0a+��S<�i��{���4�6��S����;O���h��P��_+m3;����M�ti��h����<���A
�R;v���zP^�4T,*�#T������!QT�Rx�������M�j�~�j�-yD��|g:���#@!!���+6�#|qk�b#���tF����Z.'W�
,�#+����F�r�+m�������"��x�JiP27&<���
�XF��c�%�X�������N�R����Q���^��������X<�:HL.���H��: TF=`	��y�u�}{�M��gLpq�l����&�����2�(������iF�~�ZF�����R��[��\eY;�e�����f%��y����_d����v�cD�z���#O�U+N�#��|}��g���>�V��G���j�P/2����cGICZ�����5��$2����\f��E��Nq�>w�����6���H����f�y^30��v���{bX����"M�u����+U-3b��+5/\Qv���yiN+����VK:~����UN�;J�x6�8� �/������(=Z�����x/�W��������X���Qs�4x�#"��c��Z��4w��?�f�N���|rj�E*Q���J�t� �n�~M��[$���������$�z���H=Y��\�������FH�������~g��n2����T|��s��S��^-����'d5M'.�N�h��d��(e{7A��J��$�vG��w>��N�a;D]}4��fG0�W\T�N/N~S���@XtmI�9<�	(�!�X9R2���'y���0�T�q�P5���$~����{q��U��W�6�8v��.� 9����8�����@�?���b��
�%�^��Gq�C���4��g��r�]�c~�ES�;�����uZ8�.6���h6�6�E�t��O��������L�w��PfE���;}����Z��*���D'�b���R;Pj������"�F�S�w���-[������d����W�z��[R���[��r�c���k���CO=��=O�B)>c���$b�N�y]c�~}�]M��jm�>������sY
��}Q���)��B�b�uN������&��z�6��j�?V����d�
���y�`z��J��r���z����F4��\��Hu��B�����������@�H�|�s��^��Z�8����;F�y��D��k(^�D. vXT����U���_!1�J�7�����U*/X�(Y)y������e�n_N��������+M�t�a}�\�������^m��n�����(�����eO5t��d�<�q���TY��"����K	�H/�b��!>{}tx�8>rO���TM���K�����/N�������)��\��5z1�{z�,O�/�$�ay���w��{��u������]���sk��%�1�����3u��E���^T���G8�<^�����5��0�|�cH�_C�a������^=}������y���F�d�����W?(����U����IZ�j����8��cQo�7����n���J���v"^�u%>Z4��'��4>'m���|Nz�%�r
I^�z���*�7�R��+���iCM��i:�ov��W��Q,��SA,;���F�?��a�6�2f56�mp��^5'��ww��pw����p��y8������+w{w���qwg��ww��q���pN���8�������ww���qwg���8�PCy{7qV7x�7q��zw��pw��ww��q�[���8jn�&n�^�������;���?�w[7qg�M������ww���qwg����9�n�&.���������;�����3����;��g	p�w�HN�y�r�wg��ww���qwg�������������V�����;�����3�����q�u!�u��������sw���1ww��s�c����������[���c����;�����c�p���v{�r��r��r+w{w���qwg��ww�����[���:ln�Vn�n����3����;����?�w[�rY��M������1ww��sw���1��s���7}+Ck����[��K?b��]��'��z�][�+T�s@���t+��D7O���Z_3��y�|�T������(�wJ����$�����W���D��T&i�u5�<����b�v��f�k�����>�DVop��Z
+n�Vw���p4��4h�C�~��H��W�%�q_�y~�������������v�d%l=W�B�E�z�A���#�FZ����qdl�����������"�"���Q����oC�P0�?s+WC�I�:�"��d�9xM`�����U�'����h.z��{H'�������i�O{4��:�`m�5*/:�io�YkWO����e�&��s5d���1�����e��T|W|�lo�Oa����Rj����h;/�Nz�������N��U�=c�2��z���J���C�)u�?�L�N�[��sWO�r����T��>������B�V���@�}����[�5{�:|�@���w����pp8��7'��Ppx�����k�pyd�P�lx��U�Ta���@{�cHn&
���|����*�� d�aE�U�SD��t�������mnt���g[���������O���I�H����6N�N�8+�q�VU
�w�!��{�"zE��4���l�,4�����\q�����4��eY��&�~�q��l�����N�|���9�F�� ��hX�y��8��2�c^I��/�& ?���������t�>��f�{����}���!��_i0;8dU�����8�u�����qc�G�Y����v7�&���L��,OK��A�|�J�6����!�\�G�N_���O�v|�������p������}���������jQ79�Vs�B���m7F;�1����@+u��\g���m������_������bC��G���QyGQ�I��E��t9����*�>���R�E�o����`���U%=��>�};�A���ao8��x�x�8{p���i�,�3�8�GuEl'��m�����^����ux���(�u`_�r��^�V��r����7F�I=(�+���Ng�3j�!�!��f�vg�1lOBz��|$j�%`�4�v�Z��G�vw��	�Y��������N�O��Ne���]�m��\��nU�����Y��R��F�y�.Z��Q�6Ft��&��j���v��~�V�jg/j;A��t���Vgk�[�l��� ?��cU�U��C�OUa5s��C���|������>�M�Q��������S`�g8s�e�4l�Ge��������(�����Y������V���J��
�S���=���
�SY�s@6sX�A4����@�������T���J��66b'a�t�h�1	�����]���_�������z������9US�4"Z�NQ�	�
�pB`�o�p�t�~�����\���v���yo��s����2�v��*��[;;�����^���������v�>����UmV����;j�>���g��'/7O>��n����I8��O�'?���z��p~������~|�z��c0C���'9���
��4�|�c4����n/�w�LB�	�P������\4^sz9��F����kV���g�Y�z�$_��[��=����"}�Q�������;�I�ap���J������4V�O��/��
�	��������M���h]f�'��&=�#��?>��OJ��iVw�XT����j��N��w�X����A*T@cy����`�����*�{�ea����5�?yZU?zZ?:���x��
@������N�����);�Y<L�����@!��05"��>@T��u��jL��!h�>�����A���Q�G���p�����Kh���T��F%P�=�yo���9�~���>��A����:��x��
a�D)�J
����r�u#mY�����k�m����W��"��<�����M���4�M����-
Y�c!��+�i}��=�"%���ngh�X�y��y�(��x�X�+�Xa��`�����Z��"�2�a@�?���Bv�X��.���
�������o����'�O��9V}1�K��&\�����=����������������j����$�v��?_��k	�wo������}�����+Z����O/����'���	����k�-��?;���~O�s+�q��	2����.�%���k�1��1K��o��h�2/�[��~T��E����6�/zJ������9q����1*���JK���l�Y[gcp5p��c�6�7��BMd��s�N��Q(��[	�!*���eK%��<��r0�QIv�2��in6$���2I��N"�m(3��3##��`���7w���[���;pXq$�F"���Hr�����iLP
�{�&g��V_�T����c����i���w8������ht��#�q����(���[�'rZ����x����]t�O��Ax��48)g���[����p�/��A��:�A6�3�\��J��yos�&�nYM�vV�L[�0_Z�����`U����/4"�X����2�����4�*�&��S��Gj.t��^���c`X�q��C�9UQ�iyf�c�'/��D%���+\�� >�6���=��-H]v�z���������������V��ovS��=k��?[�}���QzK�S6#� |eg�!x��-1������6��.�,�J��[
c�q�2bLw*{�Z�5;{��a"�R�
���B>�
-������P�y4�Z�s�k�(mb����u(�����u����mI$�l�%S����z��O��e�d?�8,
43�X���/��f
��v�Z�j�'��-���d�(Et>
d+��:�(��)�/�;���0�*���SJ�!m)��g0�Az���5\~�`�Kw�]A�k�V��a#�"���qC�
~(�sJ�(H�o�%aX_-�X��V\B�L�\�1��D��;���{v�������>�fl�*Oj���1����)��;�������s�\��#H��W�1r���0�(��N��p��I	N���{���Bv>�jI��]j�R,���mG�����?��jy��8G�
��:-�NV5���gRc*��kU>^^��@��S�����Z�J9W�Wwe���@iu)�8s��Ysm
Bb��9U��xj����5�Y��&�����#>��<�����G��F?.|�*R����p�2]W3���(.��M���Z](r�6]j��V��r���i�p����aJ��tN�|<Q �$��'���$ �4�4/�sE������w����S8=$����H�,��d��gx}������MS��Hz�T�d#��}���O�o��!�%��e<-Y�"G�:8�!f,yO�Y�%=�b�D��(eUjm@nfF��N{���v�b�}�c6<���e��0��>ffy����
r��hz�Z/V���9��;`.|���?>Zc������[�K`��� �����.�.w�[5�.��	��s�c��U�����
��������n7���a�UusT�:K���	#b���1�Y��a}��T���N`5c���_��]L��,~��.~C���3%���0{���H�WOh���O@����|=�l�kP^��d��w��1\�)����|��
k���J���%*p���-Hu��������}y���(!GpW�#OH�����=��"�f��`�O��?��	|�-8�qt���pFL'�e�O�|9�0����ugn�,�Q��ii:
?�g�fU�C!�t����D����Cb.mpV�� �lg��a��2�1D�Bi���`���TK@,dTcD-�+N��A��A�9�XW�����#��<�
>��1�)8J�@
*���#�'�-U�����s_G��A�y~�H�f��L9U�*�8O��2O�����%)�� ��M�qv����#LR:�z;Db�uK���u�	8o�1Y��QG��l�S�=z���	�E�h _��������7a���(PI���R(`?�j7��FYH�ID��!���dap�z"t��p��[X,����9SkJ1��6�k�FPG�_G��dmU��^�9r��%/(���d!��C}�O��F3�Vm��i���M������M�dZ��u�F��pT&x'g��1��i�)\^3
u0�����jn�1��h���n0&Sr<m�����&�	//��?�ru���^�"�#�bM�X�Kqo�>�r����{D���4���7[���lU�N�!�����cNO�ZR���c��M@1�Ub�K1L�Df6�e?O:�_�
��bE�i!��	����,��V��Rk�R{2��:f	���;�QL����R�^���f����N���S��N�Q�����3,;x�+�U��Z���#J��T ��,E'`�����F��R���=���$��k2s���I}@b,�h_�N�/We1���6�k������@��`����&w-�E���q�v!7Zdu�
k�������,��$������a��^t���l�������
�M���������b��q�����~��	x����~,���6FRp2}�/�G#�6��7������/�x���c=��}�(`�W�1~;�
��?���"@���O����R��'�%47�(�X0��6(M�(M����Wra$}�(��	�&*%$j�4���t,��$�����9����Vv�WX���N�>���{����sD����=1V�@M>��9
��u]fH=?:�������^�%��������B�bv�����c�"��m">�S<J��!�;VT6�r���
�L�T����t���$yp���8!Q��7`��2NH�Ds- ���]�A�NV���N	8j&d��@n`�{Z���w�$�*L���P���>��*9S�2�5dQ��x 	tTW������B'����T)�Pk��	��8:y�=<~n���.|Z ��>x������G[1������	T���=�JOoL�8�':'��T!����!0�i�� ��X��26��M�\�?��]�V/��d43�#��B,���7����7����f���?s�����Z�U���������QE�\L�����Y�f0�j�r���N�������gu�mf�4�'^��)��fFD��v��3mp�a%�ix�%64ti6��k"�����s.g>a��EE�PI���x����{9K��2���h��gvB:?��
�2��Uay�)�h+\�wr��V����I��n9Fq	����z�$�m\�1N`Y�)l��7>�Zv!�7�_Q}%�Zp9-j�������?�4c�;�&�RuuZ���6i��7�6}��D��Yx��9('A���:� !��&�<6��w����Q���<�"���`�c���4���B������oG�N��O�'��6` ��S��k��N���;�95�g����m��1�:�a����w�l��P��?MG�>:����<R��q[S��H��kk���g,X*04��^���O�B,�+�tDa3�b�E�	$�������#,$�[+<�jn �*�e^J6��C��9O2��������w��iSW�-&�[kC����p1vy&�Ob�5J.���MKVD=��@RQ�;����^�e����,O����������l�����UQ�����P����
��$��|��
���7��R�� ��)��
FyT��
�A��$������S��e�WP8�	���1}�T4�X��<��9�l��9�P8}l�X���$�2Mv�J���d*���RnpV���c��eb��\D�y�
����m�i�e�1�I���f�*eT���t�H
�y�C��qI���qR�����T:Xk�A(��[loF�s�Q�
i%��m�����9l���a,Q�`������6�����J�@N8�H�`<�j���'f�����LN������f,m�����:.�P��g��p�7ba��!��� c�6�6��@cB�0���)�~�L���)����e~������f��@T���������a��eW�vp��}�<c���R��eu�3��|��6���E�uCo�\(���?Ww�\�<L�e���-��I���5����8�U��|��;�������};�0o�5b����#*���h�
����U)����`���[P��y��:��YiUC����mT�.��N�Mp$���/!�za�����S`'��rkn
i����*H��e��DY�:����S��$��Z�*��"|n_�����AB��0���,���f
����rKlrk�I}�[h�[�����������9b��sp��.�����*��OY�d�K�.�zW\ha�����`/bl���H�'a2�������������6��\��H�f���N��A��T�`�62�"s'R�2�L�4����@����X��5,%�-�F�>/dN��o���P���VMu�T-���M���{}Y��T�/���S���2\��0�)����9jf@�u�,�ZSV0h��1A(d�U��#��Q�������������������
z���<�z�FuX�}�����1>�}�Z�#��R�]�x9",�W�3x{���W���Mk��7�Dq9��M�`��
aDkZ�*�Tf��}�O��f1T������C������u�%Q�(|���Q��5lx���O��#��%���x��u��G�O��C��z�^��}q��1������I9|u��F��rT�/����|��w�s����W����P�I�F0��j,H������j�����3�Y����k'�Y��06U�Gu�������p�F��V�����[Ia��e2�����e�mM�����&1HU��iQf����0T��X0��h����t~�+�",pF��������"Acn@���S�1.yKQm^L�mrX�,'F{���]	]5^��Q�T����r��]9��"�gN	��{<���^���w�B�0��d^������zeJ�>�g__���7W�!)��p�_x�4{,��2����a�\
��=��$L��D4�����]����-y��?\������sc�������!�dQ�c�S�o;3
��������� ��[Pu��YX!�g��|]H��"���1��
��y&k��Qr�{GT
���=�������#������]��4~��1��np�����^lr��=m-��~�����c{l���=�����c{l���=�������8���
#103nuko yokohama
nuko.yokohama@gmail.com
In reply to: Yugo Nagata (#1)
Re: Implementing Incremental View Maintenance

"ROW LEVEL SECURITY" and INCREMENTAL MATERIALIZED VIEW.

Hi.

If ROW LEVEL SECURITY is set for the source table after creating the
INCREMENTAL MATELIALIZED VIEW, the search results by that are not reflected.
After setting ROW LEVEL SECURITY (similar to normal MATERIALIZED VIEW), you
need to execute REFRESH MATERILALIZED VIEW and reflect the result.
(Not limited to this, but in general cases where search results change by
means other than DML)

I propose to add this note to the document (rules.sgml).

execute log.

```
[ec2-user@ip-10-0-1-10 rls]$ psql testdb -e -f rls.sql
CREATE USER user_a;
CREATE ROLE
CREATE TABLE test (id int, data text);
CREATE TABLE
GRANT ALL ON TABLE test TO user_a;
GRANT
GRANT ALL ON SCHEMA public TO user_a;
GRANT
SET ROLE user_a;
SET
INSERT INTO test VALUES (1,'A'),(2,'B'),(3,'C');
INSERT 0 3
SELECT * FROM test;
id | data
----+------
1 | A
2 | B
3 | C
(3 rows)

CREATE VIEW test_v AS SELECT * FROM test;
CREATE VIEW
CREATE MATERIALIZED VIEW test_mv AS SELECT * FROM test;
SELECT 3
CREATE INCREMENTAL MATERIALIZED VIEW test_imv AS SELECT * FROM test;
SELECT 3
SELECT * FROM test_v;
id | data
----+------
1 | A
2 | B
3 | C
(3 rows)

SELECT * FROM test_mv;
id | data
----+------
1 | A
2 | B
3 | C
(3 rows)

SELECT * FROM test_imv;
id | data
----+------
3 | C
1 | A
2 | B
(3 rows)

RESET ROLE;
RESET
CREATE POLICY test_AAA ON test FOR SELECT TO user_a USING (data = 'A');
CREATE POLICY
ALTER TABLE test ENABLE ROW LEVEL SECURITY ;
ALTER TABLE
SET ROLE user_a;
SET
SELECT * FROM test_v;
id | data
----+------
1 | A
(1 row)

SELECT * FROM test_mv;
id | data
----+------
1 | A
2 | B
3 | C
(3 rows)

SELECT * FROM test_imv;
id | data
----+------
3 | C
1 | A
2 | B
(3 rows)

REFRESH MATERIALIZED VIEW test_mv;
REFRESH MATERIALIZED VIEW
REFRESH MATERIALIZED VIEW test_imv;
REFRESH MATERIALIZED VIEW
SELECT * FROM test_mv;
id | data
----+------
1 | A
(1 row)

SELECT * FROM test_imv;
id | data
----+------
1 | A
(1 row)

RESET ROLE;
RESET
REVOKE ALL ON TABLE test FROM user_a;
REVOKE
REVOKE ALL ON TABLE test_v FROM user_a;
REVOKE
REVOKE ALL ON TABLE test_mv FROM user_a;
REVOKE
REVOKE ALL ON TABLE test_imv FROM user_a;
REVOKE
REVOKE ALL ON SCHEMA public FROM user_a;
REVOKE
DROP TABLE test CASCADE;
psql:rls.sql:40: NOTICE: drop cascades to 3 other objects
DETAIL: drop cascades to view test_v
drop cascades to materialized view test_mv
drop cascades to materialized view test_imv
DROP TABLE
DROP USER user_a;
DROP ROLE
[ec2-user@ip-10-0-1-10 rls]$

```

Regard.

2018年12月27日(木) 21:57 Yugo Nagata <nagata@sraoss.co.jp>:

Show quoted text

Hi,

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.
IVM is a technique to maintain materialized views which computes and
applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018
[1].
Our implementation uses row OIDs to compute deltas for materialized
views.
The basic idea is that if we have information about which rows in base
tables
are contributing to generate a certain row in a matview then we can
identify
the affected rows when a base table is updated. This is based on an idea of
Dr. Masunaga [2] who is a member of our group and inspired from ID-based
approach[3].

In our implementation, the mapping of the row OIDs of the materialized view
and the base tables are stored in "OID map". When a base relation is
modified,
AFTER trigger is executed and the delta is recorded in delta tables using
the transition table feature. The accual udpate of the matview is triggerd
by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS will
be removed since PG12, so OIDs are no longer available. Besides this, it
would
be hard to implement this since it needs many changes of executor nodes to
collect base tables's OIDs during execuing a query. Also, the cost of
maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without relying on
OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4] on incremental matview
maintenance. In this discussion, Kevin proposed to use counting algorithm
[5]
to handle projection views (using DISTNICT) properly. This algorithm need
an
additional system column, count_t, in materialized views and delta tables
of
base tables.

However, the discussion about IVM is now stoped, so we would like to
restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at
least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables,
but using
logical decoding might be better from the point of performance of base
table
modification.

If we can represent a change of UPDATE on a base table as query-like
rather than
OLD and NEW, it may be possible to update the materialized view directly
instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes on
base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on "change
propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe this as
SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify
which row
in the matview is corresponding to a row in the delta. A naive method is
matching
by using all columns in a tuple, but clearly this is unefficient. If
thematerialized
view has unique index, we can use this. Maybe, we have to force
materialized views
to have all primary key colums in their base tables. In our PoC
implementation, we
used OID to identify rows, but this will be no longer available as said
above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate (eager)
or deferred.

In eager maintenance, the materialized view is updated in the same
transaction
where the base table is updated. In deferred maintenance, this is done
after the
transaction is commited, for example, when view is accessed, as a response
to user
request, etc.

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command
to perform IVM.
I am not sure which is better as a start point, but I begin to think that
the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

In the eager maintenance approache, we have to consider a race condition
where two
different transactions change base tables simultaneously as discussed in
[4].

[1]
https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/
[2]
https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1
(Japanese only)
[3] https://dl.acm.org/citation.cfm?id=2750546
[4]
/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com
[5] https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

#104Yugo NAGATA
nagata@sraoss.co.jp
In reply to: nuko yokohama (#103)
Re: Implementing Incremental View Maintenance

On Tue, 4 Feb 2020 18:40:45 +0900
nuko yokohama <nuko.yokohama@gmail.com> wrote:

"ROW LEVEL SECURITY" and INCREMENTAL MATERIALIZED VIEW.

Hi.

If ROW LEVEL SECURITY is set for the source table after creating the
INCREMENTAL MATELIALIZED VIEW, the search results by that are not reflected.
After setting ROW LEVEL SECURITY (similar to normal MATERIALIZED VIEW), you
need to execute REFRESH MATERILALIZED VIEW and reflect the result.
(Not limited to this, but in general cases where search results change by
means other than DML)

I propose to add this note to the document (rules.sgml).

Thank you for your suggestion! We'll add some description
about this to the documentation.

Regards,
Yugo Nagata

execute log.

```
[ec2-user@ip-10-0-1-10 rls]$ psql testdb -e -f rls.sql
CREATE USER user_a;
CREATE ROLE
CREATE TABLE test (id int, data text);
CREATE TABLE
GRANT ALL ON TABLE test TO user_a;
GRANT
GRANT ALL ON SCHEMA public TO user_a;
GRANT
SET ROLE user_a;
SET
INSERT INTO test VALUES (1,'A'),(2,'B'),(3,'C');
INSERT 0 3
SELECT * FROM test;
id | data
----+------
1 | A
2 | B
3 | C
(3 rows)

CREATE VIEW test_v AS SELECT * FROM test;
CREATE VIEW
CREATE MATERIALIZED VIEW test_mv AS SELECT * FROM test;
SELECT 3
CREATE INCREMENTAL MATERIALIZED VIEW test_imv AS SELECT * FROM test;
SELECT 3
SELECT * FROM test_v;
id | data
----+------
1 | A
2 | B
3 | C
(3 rows)

SELECT * FROM test_mv;
id | data
----+------
1 | A
2 | B
3 | C
(3 rows)

SELECT * FROM test_imv;
id | data
----+------
3 | C
1 | A
2 | B
(3 rows)

RESET ROLE;
RESET
CREATE POLICY test_AAA ON test FOR SELECT TO user_a USING (data = 'A');
CREATE POLICY
ALTER TABLE test ENABLE ROW LEVEL SECURITY ;
ALTER TABLE
SET ROLE user_a;
SET
SELECT * FROM test_v;
id | data
----+------
1 | A
(1 row)

SELECT * FROM test_mv;
id | data
----+------
1 | A
2 | B
3 | C
(3 rows)

SELECT * FROM test_imv;
id | data
----+------
3 | C
1 | A
2 | B
(3 rows)

REFRESH MATERIALIZED VIEW test_mv;
REFRESH MATERIALIZED VIEW
REFRESH MATERIALIZED VIEW test_imv;
REFRESH MATERIALIZED VIEW
SELECT * FROM test_mv;
id | data
----+------
1 | A
(1 row)

SELECT * FROM test_imv;
id | data
----+------
1 | A
(1 row)

RESET ROLE;
RESET
REVOKE ALL ON TABLE test FROM user_a;
REVOKE
REVOKE ALL ON TABLE test_v FROM user_a;
REVOKE
REVOKE ALL ON TABLE test_mv FROM user_a;
REVOKE
REVOKE ALL ON TABLE test_imv FROM user_a;
REVOKE
REVOKE ALL ON SCHEMA public FROM user_a;
REVOKE
DROP TABLE test CASCADE;
psql:rls.sql:40: NOTICE: drop cascades to 3 other objects
DETAIL: drop cascades to view test_v
drop cascades to materialized view test_mv
drop cascades to materialized view test_imv
DROP TABLE
DROP USER user_a;
DROP ROLE
[ec2-user@ip-10-0-1-10 rls]$

```

Regard.

2018年12月27日(木) 21:57 Yugo Nagata <nagata@sraoss.co.jp>:

Hi,

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.
IVM is a technique to maintain materialized views which computes and
applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018
[1].
Our implementation uses row OIDs to compute deltas for materialized
views.
The basic idea is that if we have information about which rows in base
tables
are contributing to generate a certain row in a matview then we can
identify
the affected rows when a base table is updated. This is based on an idea of
Dr. Masunaga [2] who is a member of our group and inspired from ID-based
approach[3].

In our implementation, the mapping of the row OIDs of the materialized view
and the base tables are stored in "OID map". When a base relation is
modified,
AFTER trigger is executed and the delta is recorded in delta tables using
the transition table feature. The accual udpate of the matview is triggerd
by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS will
be removed since PG12, so OIDs are no longer available. Besides this, it
would
be hard to implement this since it needs many changes of executor nodes to
collect base tables's OIDs during execuing a query. Also, the cost of
maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without relying on
OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4] on incremental matview
maintenance. In this discussion, Kevin proposed to use counting algorithm
[5]
to handle projection views (using DISTNICT) properly. This algorithm need
an
additional system column, count_t, in materialized views and delta tables
of
base tables.

However, the discussion about IVM is now stoped, so we would like to
restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at
least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables,
but using
logical decoding might be better from the point of performance of base
table
modification.

If we can represent a change of UPDATE on a base table as query-like
rather than
OLD and NEW, it may be possible to update the materialized view directly
instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes on
base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on "change
propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe this as
SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify
which row
in the matview is corresponding to a row in the delta. A naive method is
matching
by using all columns in a tuple, but clearly this is unefficient. If
thematerialized
view has unique index, we can use this. Maybe, we have to force
materialized views
to have all primary key colums in their base tables. In our PoC
implementation, we
used OID to identify rows, but this will be no longer available as said
above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate (eager)
or deferred.

In eager maintenance, the materialized view is updated in the same
transaction
where the base table is updated. In deferred maintenance, this is done
after the
transaction is commited, for example, when view is accessed, as a response
to user
request, etc.

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command
to perform IVM.
I am not sure which is better as a start point, but I begin to think that
the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

In the eager maintenance approache, we have to consider a race condition
where two
different transactions change base tables simultaneously as discussed in
[4].

[1]
https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/
[2]
https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1
(Japanese only)
[3] https://dl.acm.org/citation.cfm?id=2750546
[4]
/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com
[5] https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

#105nuko yokohama
nuko.yokohama@gmail.com
In reply to: Yugo Nagata (#1)
Re: Implementing Incremental View Maintenance

Hi.

UNION query problem.(server crash)

When creating an INCREMENTAL MATERIALIZED VIEW,
the server process crashes if you specify a query with a UNION.

(commit id = 23151be7be8d8f8f9c35c2d0e4e5353aedf2b31e)

execute log.

```
[ec2-user@ip-10-0-1-10 ivm]$ psql testdb -e -f union_query_crash.sql
DROP TABLE IF EXISTS table_x CASCADE;
psql:union_query_crash.sql:6: NOTICE: drop cascades to view xy_union_v
DROP TABLE
DROP TABLE IF EXISTS table_y CASCADE;
DROP TABLE
CREATE TABLE table_x (id int, data numeric);
CREATE TABLE
CREATE TABLE table_y (id int, data numeric);
CREATE TABLE
INSERT INTO table_x VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
INSERT INTO table_y VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
SELECT * FROM table_x;
id | data
----+--------------------
1 | 0.950724735058774
2 | 0.0222670808201144
3 | 0.391258547114841
(3 rows)

SELECT * FROM table_y;
id | data
----+--------------------
1 | 0.991717347778337
2 | 0.0528458947672874
3 | 0.965044982911163
(3 rows)

CREATE VIEW xy_union_v AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
CREATE VIEW
TABLE xy_union_v;
name | id | data
---------+----+--------------------
table_y | 2 | 0.0528458947672874
table_x | 2 | 0.0222670808201144
table_y | 3 | 0.965044982911163
table_x | 1 | 0.950724735058774
table_x | 3 | 0.391258547114841
table_y | 1 | 0.991717347778337
(6 rows)

CREATE INCREMENTAL MATERIALIZED VIEW xy_imv AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
psql:union_query_crash.sql:28: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
psql:union_query_crash.sql:28: fatal: connection to server was lost
```
UNION query problem.(server crash)

When creating an INCREMENTAL MATERIALIZED VIEW,
the server process crashes if you specify a query with a UNION.

(commit id = 23151be7be8d8f8f9c35c2d0e4e5353aedf2b31e)

execute log.

```
[ec2-user@ip-10-0-1-10 ivm]$ psql testdb -e -f union_query_crash.sql
DROP TABLE IF EXISTS table_x CASCADE;
psql:union_query_crash.sql:6: NOTICE: drop cascades to view xy_union_v
DROP TABLE
DROP TABLE IF EXISTS table_y CASCADE;
DROP TABLE
CREATE TABLE table_x (id int, data numeric);
CREATE TABLE
CREATE TABLE table_y (id int, data numeric);
CREATE TABLE
INSERT INTO table_x VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
INSERT INTO table_y VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
SELECT * FROM table_x;
id | data
----+--------------------
1 | 0.950724735058774
2 | 0.0222670808201144
3 | 0.391258547114841
(3 rows)

SELECT * FROM table_y;
id | data
----+--------------------
1 | 0.991717347778337
2 | 0.0528458947672874
3 | 0.965044982911163
(3 rows)

CREATE VIEW xy_union_v AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
CREATE VIEW
TABLE xy_union_v;
name | id | data
---------+----+--------------------
table_y | 2 | 0.0528458947672874
table_x | 2 | 0.0222670808201144
table_y | 3 | 0.965044982911163
table_x | 1 | 0.950724735058774
table_x | 3 | 0.391258547114841
table_y | 1 | 0.991717347778337
(6 rows)

CREATE INCREMENTAL MATERIALIZED VIEW xy_imv AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
psql:union_query_crash.sql:28: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
psql:union_query_crash.sql:28: fatal: connection to server was lost
```

2018年12月27日(木) 21:57 Yugo Nagata <nagata@sraoss.co.jp>:

Show quoted text

Hi,

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.
IVM is a technique to maintain materialized views which computes and
applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018
[1].
Our implementation uses row OIDs to compute deltas for materialized
views.
The basic idea is that if we have information about which rows in base
tables
are contributing to generate a certain row in a matview then we can
identify
the affected rows when a base table is updated. This is based on an idea of
Dr. Masunaga [2] who is a member of our group and inspired from ID-based
approach[3].

In our implementation, the mapping of the row OIDs of the materialized view
and the base tables are stored in "OID map". When a base relation is
modified,
AFTER trigger is executed and the delta is recorded in delta tables using
the transition table feature. The accual udpate of the matview is triggerd
by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS will
be removed since PG12, so OIDs are no longer available. Besides this, it
would
be hard to implement this since it needs many changes of executor nodes to
collect base tables's OIDs during execuing a query. Also, the cost of
maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without relying on
OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4] on incremental matview
maintenance. In this discussion, Kevin proposed to use counting algorithm
[5]
to handle projection views (using DISTNICT) properly. This algorithm need
an
additional system column, count_t, in materialized views and delta tables
of
base tables.

However, the discussion about IVM is now stoped, so we would like to
restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at
least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables,
but using
logical decoding might be better from the point of performance of base
table
modification.

If we can represent a change of UPDATE on a base table as query-like
rather than
OLD and NEW, it may be possible to update the materialized view directly
instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes on
base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on "change
propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe this as
SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify
which row
in the matview is corresponding to a row in the delta. A naive method is
matching
by using all columns in a tuple, but clearly this is unefficient. If
thematerialized
view has unique index, we can use this. Maybe, we have to force
materialized views
to have all primary key colums in their base tables. In our PoC
implementation, we
used OID to identify rows, but this will be no longer available as said
above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate (eager)
or deferred.

In eager maintenance, the materialized view is updated in the same
transaction
where the base table is updated. In deferred maintenance, this is done
after the
transaction is commited, for example, when view is accessed, as a response
to user
request, etc.

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command
to perform IVM.
I am not sure which is better as a start point, but I begin to think that
the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

In the eager maintenance approache, we have to consider a race condition
where two
different transactions change base tables simultaneously as discussed in
[4].

[1]
https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/
[2]
https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1
(Japanese only)
[3] https://dl.acm.org/citation.cfm?id=2750546
[4]
/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com
[5] https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

#106Yugo NAGATA
nagata@sraoss.co.jp
In reply to: nuko yokohama (#105)
Re: Implementing Incremental View Maintenance

On Sat, 8 Feb 2020 11:15:45 +0900
nuko yokohama <nuko.yokohama@gmail.com> wrote:

Hi.

UNION query problem.(server crash)

When creating an INCREMENTAL MATERIALIZED VIEW,
the server process crashes if you specify a query with a UNION.

Thank you for your report. As you noticed set operations including
UNION is concurrently unsupported, although this is not checked at
definition time and not documented either. Now we are thoroughly
investigating unsupported queries, and will add checks and
documentations for them.

Regards,
Yugo Nagata

(commit id = 23151be7be8d8f8f9c35c2d0e4e5353aedf2b31e)

execute log.

```
[ec2-user@ip-10-0-1-10 ivm]$ psql testdb -e -f union_query_crash.sql
DROP TABLE IF EXISTS table_x CASCADE;
psql:union_query_crash.sql:6: NOTICE: drop cascades to view xy_union_v
DROP TABLE
DROP TABLE IF EXISTS table_y CASCADE;
DROP TABLE
CREATE TABLE table_x (id int, data numeric);
CREATE TABLE
CREATE TABLE table_y (id int, data numeric);
CREATE TABLE
INSERT INTO table_x VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
INSERT INTO table_y VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
SELECT * FROM table_x;
id | data
----+--------------------
1 | 0.950724735058774
2 | 0.0222670808201144
3 | 0.391258547114841
(3 rows)

SELECT * FROM table_y;
id | data
----+--------------------
1 | 0.991717347778337
2 | 0.0528458947672874
3 | 0.965044982911163
(3 rows)

CREATE VIEW xy_union_v AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
CREATE VIEW
TABLE xy_union_v;
name | id | data
---------+----+--------------------
table_y | 2 | 0.0528458947672874
table_x | 2 | 0.0222670808201144
table_y | 3 | 0.965044982911163
table_x | 1 | 0.950724735058774
table_x | 3 | 0.391258547114841
table_y | 1 | 0.991717347778337
(6 rows)

CREATE INCREMENTAL MATERIALIZED VIEW xy_imv AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
psql:union_query_crash.sql:28: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
psql:union_query_crash.sql:28: fatal: connection to server was lost
```
UNION query problem.(server crash)

When creating an INCREMENTAL MATERIALIZED VIEW,
the server process crashes if you specify a query with a UNION.

(commit id = 23151be7be8d8f8f9c35c2d0e4e5353aedf2b31e)

execute log.

```
[ec2-user@ip-10-0-1-10 ivm]$ psql testdb -e -f union_query_crash.sql
DROP TABLE IF EXISTS table_x CASCADE;
psql:union_query_crash.sql:6: NOTICE: drop cascades to view xy_union_v
DROP TABLE
DROP TABLE IF EXISTS table_y CASCADE;
DROP TABLE
CREATE TABLE table_x (id int, data numeric);
CREATE TABLE
CREATE TABLE table_y (id int, data numeric);
CREATE TABLE
INSERT INTO table_x VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
INSERT INTO table_y VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
SELECT * FROM table_x;
id | data
----+--------------------
1 | 0.950724735058774
2 | 0.0222670808201144
3 | 0.391258547114841
(3 rows)

SELECT * FROM table_y;
id | data
----+--------------------
1 | 0.991717347778337
2 | 0.0528458947672874
3 | 0.965044982911163
(3 rows)

CREATE VIEW xy_union_v AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
CREATE VIEW
TABLE xy_union_v;
name | id | data
---------+----+--------------------
table_y | 2 | 0.0528458947672874
table_x | 2 | 0.0222670808201144
table_y | 3 | 0.965044982911163
table_x | 1 | 0.950724735058774
table_x | 3 | 0.391258547114841
table_y | 1 | 0.991717347778337
(6 rows)

CREATE INCREMENTAL MATERIALIZED VIEW xy_imv AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
psql:union_query_crash.sql:28: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
psql:union_query_crash.sql:28: fatal: connection to server was lost
```

2018年12月27日(木) 21:57 Yugo Nagata <nagata@sraoss.co.jp>:

Hi,

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.
IVM is a technique to maintain materialized views which computes and
applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018
[1].
Our implementation uses row OIDs to compute deltas for materialized
views.
The basic idea is that if we have information about which rows in base
tables
are contributing to generate a certain row in a matview then we can
identify
the affected rows when a base table is updated. This is based on an idea of
Dr. Masunaga [2] who is a member of our group and inspired from ID-based
approach[3].

In our implementation, the mapping of the row OIDs of the materialized view
and the base tables are stored in "OID map". When a base relation is
modified,
AFTER trigger is executed and the delta is recorded in delta tables using
the transition table feature. The accual udpate of the matview is triggerd
by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS will
be removed since PG12, so OIDs are no longer available. Besides this, it
would
be hard to implement this since it needs many changes of executor nodes to
collect base tables's OIDs during execuing a query. Also, the cost of
maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without relying on
OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4] on incremental matview
maintenance. In this discussion, Kevin proposed to use counting algorithm
[5]
to handle projection views (using DISTNICT) properly. This algorithm need
an
additional system column, count_t, in materialized views and delta tables
of
base tables.

However, the discussion about IVM is now stoped, so we would like to
restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at
least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables,
but using
logical decoding might be better from the point of performance of base
table
modification.

If we can represent a change of UPDATE on a base table as query-like
rather than
OLD and NEW, it may be possible to update the materialized view directly
instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes on
base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on "change
propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe this as
SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify
which row
in the matview is corresponding to a row in the delta. A naive method is
matching
by using all columns in a tuple, but clearly this is unefficient. If
thematerialized
view has unique index, we can use this. Maybe, we have to force
materialized views
to have all primary key colums in their base tables. In our PoC
implementation, we
used OID to identify rows, but this will be no longer available as said
above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate (eager)
or deferred.

In eager maintenance, the materialized view is updated in the same
transaction
where the base table is updated. In deferred maintenance, this is done
after the
transaction is commited, for example, when view is accessed, as a response
to user
request, etc.

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command
to perform IVM.
I am not sure which is better as a start point, but I begin to think that
the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

In the eager maintenance approache, we have to consider a race condition
where two
different transactions change base tables simultaneously as discussed in
[4].

[1]
https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/
[2]
https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1
(Japanese only)
[3] https://dl.acm.org/citation.cfm?id=2750546
[4]
/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com
[5] https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

#107nuko yokohama
nuko.yokohama@gmail.com
In reply to: Yugo NAGATA (#106)
Re: Implementing Incremental View Maintenance

Hi.

I understod that UNION is unsupported.

I also refer to the implementation of "./src/backend/commands/createas.c"
check_ivm_restriction_walker () to see if there are any other queries that
may be problematic.

2020年2月10日(月) 10:38 Yugo NAGATA <nagata@sraoss.co.jp>:

Show quoted text

On Sat, 8 Feb 2020 11:15:45 +0900
nuko yokohama <nuko.yokohama@gmail.com> wrote:

Hi.

UNION query problem.(server crash)

When creating an INCREMENTAL MATERIALIZED VIEW,
the server process crashes if you specify a query with a UNION.

Thank you for your report. As you noticed set operations including
UNION is concurrently unsupported, although this is not checked at
definition time and not documented either. Now we are thoroughly
investigating unsupported queries, and will add checks and
documentations for them.

Regards,
Yugo Nagata

(commit id = 23151be7be8d8f8f9c35c2d0e4e5353aedf2b31e)

execute log.

```
[ec2-user@ip-10-0-1-10 ivm]$ psql testdb -e -f union_query_crash.sql
DROP TABLE IF EXISTS table_x CASCADE;
psql:union_query_crash.sql:6: NOTICE: drop cascades to view xy_union_v
DROP TABLE
DROP TABLE IF EXISTS table_y CASCADE;
DROP TABLE
CREATE TABLE table_x (id int, data numeric);
CREATE TABLE
CREATE TABLE table_y (id int, data numeric);
CREATE TABLE
INSERT INTO table_x VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
INSERT INTO table_y VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
SELECT * FROM table_x;
id | data
----+--------------------
1 | 0.950724735058774
2 | 0.0222670808201144
3 | 0.391258547114841
(3 rows)

SELECT * FROM table_y;
id | data
----+--------------------
1 | 0.991717347778337
2 | 0.0528458947672874
3 | 0.965044982911163
(3 rows)

CREATE VIEW xy_union_v AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
CREATE VIEW
TABLE xy_union_v;
name | id | data
---------+----+--------------------
table_y | 2 | 0.0528458947672874
table_x | 2 | 0.0222670808201144
table_y | 3 | 0.965044982911163
table_x | 1 | 0.950724735058774
table_x | 3 | 0.391258547114841
table_y | 1 | 0.991717347778337
(6 rows)

CREATE INCREMENTAL MATERIALIZED VIEW xy_imv AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
psql:union_query_crash.sql:28: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
psql:union_query_crash.sql:28: fatal: connection to server was lost
```
UNION query problem.(server crash)

When creating an INCREMENTAL MATERIALIZED VIEW,
the server process crashes if you specify a query with a UNION.

(commit id = 23151be7be8d8f8f9c35c2d0e4e5353aedf2b31e)

execute log.

```
[ec2-user@ip-10-0-1-10 ivm]$ psql testdb -e -f union_query_crash.sql
DROP TABLE IF EXISTS table_x CASCADE;
psql:union_query_crash.sql:6: NOTICE: drop cascades to view xy_union_v
DROP TABLE
DROP TABLE IF EXISTS table_y CASCADE;
DROP TABLE
CREATE TABLE table_x (id int, data numeric);
CREATE TABLE
CREATE TABLE table_y (id int, data numeric);
CREATE TABLE
INSERT INTO table_x VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
INSERT INTO table_y VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
SELECT * FROM table_x;
id | data
----+--------------------
1 | 0.950724735058774
2 | 0.0222670808201144
3 | 0.391258547114841
(3 rows)

SELECT * FROM table_y;
id | data
----+--------------------
1 | 0.991717347778337
2 | 0.0528458947672874
3 | 0.965044982911163
(3 rows)

CREATE VIEW xy_union_v AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
CREATE VIEW
TABLE xy_union_v;
name | id | data
---------+----+--------------------
table_y | 2 | 0.0528458947672874
table_x | 2 | 0.0222670808201144
table_y | 3 | 0.965044982911163
table_x | 1 | 0.950724735058774
table_x | 3 | 0.391258547114841
table_y | 1 | 0.991717347778337
(6 rows)

CREATE INCREMENTAL MATERIALIZED VIEW xy_imv AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
psql:union_query_crash.sql:28: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
psql:union_query_crash.sql:28: fatal: connection to server was lost
```

2018年12月27日(木) 21:57 Yugo Nagata <nagata@sraoss.co.jp>:

Hi,

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.
IVM is a technique to maintain materialized views which computes and
applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018
[1].
Our implementation uses row OIDs to compute deltas for materialized
views.
The basic idea is that if we have information about which rows in base
tables
are contributing to generate a certain row in a matview then we can
identify
the affected rows when a base table is updated. This is based on an

idea of

Dr. Masunaga [2] who is a member of our group and inspired from

ID-based

approach[3].

In our implementation, the mapping of the row OIDs of the materialized

view

and the base tables are stored in "OID map". When a base relation is
modified,
AFTER trigger is executed and the delta is recorded in delta tables

using

the transition table feature. The accual udpate of the matview is

triggerd

by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS

will

be removed since PG12, so OIDs are no longer available. Besides this,

it

would
be hard to implement this since it needs many changes of executor

nodes to

collect base tables's OIDs during execuing a query. Also, the cost of
maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without

relying on

OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4] on incremental

matview

maintenance. In this discussion, Kevin proposed to use counting

algorithm

[5]
to handle projection views (using DISTNICT) properly. This algorithm

need

an
additional system column, count_t, in materialized views and delta

tables

of
base tables.

However, the discussion about IVM is now stoped, so we would like to
restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at
least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables,
but using
logical decoding might be better from the point of performance of base
table
modification.

If we can represent a change of UPDATE on a base table as query-like
rather than
OLD and NEW, it may be possible to update the materialized view

directly

instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes

on

base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on

"change

propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe

this as

SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify
which row
in the matview is corresponding to a row in the delta. A naive method

is

matching
by using all columns in a tuple, but clearly this is unefficient. If
thematerialized
view has unique index, we can use this. Maybe, we have to force
materialized views
to have all primary key colums in their base tables. In our PoC
implementation, we
used OID to identify rows, but this will be no longer available as said
above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate

(eager)

or deferred.

In eager maintenance, the materialized view is updated in the same
transaction
where the base table is updated. In deferred maintenance, this is done
after the
transaction is commited, for example, when view is accessed, as a

response

to user
request, etc.

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH

command

to perform IVM.
I am not sure which is better as a start point, but I begin to think

that

the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

In the eager maintenance approache, we have to consider a race

condition

where two
different transactions change base tables simultaneously as discussed

in

[4].

[1]

https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/

[2]

https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1

(Japanese only)
[3] https://dl.acm.org/citation.cfm?id=2750546
[4]

/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com

[5] https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

#108Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Takuma Hoshiai (#102)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

Attached is the latest patch (v13) to add support for Incremental
View Maintenance (IVM). Differences from the previous patch (v12)
include:

* Allow to maintain IMMVs containing user defined types

Previously, IMMVs (Incrementally Maintainable Materialized Views)
containing user defined types could not be maintained and an error
was raised because such columns were compared using pg_calatog.=
during tuple matching. To fix this, use the column type's default
equality operator instead of forcing to use the built-in operator.

Pointed out by nuko-san.
/messages/by-id/CAF3Gu1YL7HWF0Veor3t8sQD+JnvozHe6WdUw0YsMqJGFezVhpg@mail.gmail.com

* Improve an error message for unspoorted aggregate functions

Currentlly only built-in aggregate functions are supported, so
aggregates on user-defined types causes an error at view definition
time. However, the message was unappropreate like:

ERROR: aggregate function max is not supported

even though built-in max is supported. Therefore, this is improved
to include its argument types as following:

ERROR: aggregate function min(xxx) is not supported
HINT: IVM supports only built-in aggregate functions.

Pointed out by nuko-san.
/messages/by-id/CAF3Gu1bP0eiv=CqV=+xATdcmLypjjudLz_wdJgnRNULpiX9GrA@mail.gmail.com

* Doc: fix description of support subquery

IVM supports regular EXISTS clause not only correlated subqueries.

Regards,
Yugo Nagata

On Fri, 20 Dec 2019 14:02:32 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

IVM is a way to make materialized views up-to-date in which only
incremental changes are computed and applied on views rather than
recomputing the contents from scratch as REFRESH MATERIALIZED VIEW
does. IVM can update materialized views more efficiently
than recomputation when only small part of the view need updates.

There are two approaches with regard to timing of view maintenance:
immediate and deferred. In immediate maintenance, views are updated in
the same transaction where its base table is modified. In deferred
maintenance, views are updated after the transaction is committed,
for example, when the view is accessed, as a response to user command
like REFRESH, or periodically in background, and so on.

This patch implements a kind of immediate maintenance, in which
materialized views are updated immediately in AFTER triggers when a
base table is modified.

This supports views using:
- inner and outer joins including self-join
- some built-in aggregate functions (count, sum, agv, min, max)
- a part of subqueries
-- simple subqueries in FROM clause
-- EXISTS subqueries in WHERE clause
- DISTINCT and views with tuple duplicates

Regareds,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v13.tar.gzapplication/gzip; name=IVM_patches_v13.tar.gzDownload
#109legrand legrand
legrand_legrand@hotmail.com
In reply to: Takuma Hoshiai (#102)
Re: Implementing Incremental View Maintenance

Takuma Hoshiai wrote

Hi,

Attached is the latest patch (v12) to add support for Incremental
Materialized View Maintenance (IVM).
It is possible to apply to current latest master branch.

Differences from the previous patch (v11) include:
* support executing REFRESH MATERIALIZED VIEW command with IVM.
* support unscannable state by WITH NO DATA option.
* add a check for LIMIT/OFFSET at creating an IMMV

If REFRESH is executed for IMMV (incremental maintainable materialized
view), its contents is re-calculated as same as usual materialized views
(full REFRESH). Although IMMV is basically keeping up-to-date data,
rounding errors can be accumulated in aggregated value in some cases, for
example, if the view contains sum/avg on float type columns. Running
REFRESH command on IMMV will resolve this. Also, WITH NO DATA option
allows to make IMMV unscannable. At that time, IVM triggers are dropped
from IMMV because these become unneeded and useless.

[...]

Hello,

regarding syntax REFRESH MATERIALIZED VIEW x WITH NO DATA

I understand that triggers are removed from the source tables, transforming
the INCREMENTAL MATERIALIZED VIEW into a(n unscannable) MATERIALIZED VIEW.

postgres=# refresh materialized view imv with no data;
REFRESH MATERIALIZED VIEW
postgres=# select * from imv;
ERROR: materialized view "imv" has not been populated
HINT: Use the REFRESH MATERIALIZED VIEW command.

This operation seems to me more of an ALTER command than a REFRESH ONE.

Wouldn't the syntax
ALTER MATERIALIZED VIEW [ IF EXISTS ] name
SET WITH NO DATA
or
SET WITHOUT DATA
be better ?

Continuing into this direction, did you ever think about an other feature
like:
ALTER MATERIALIZED VIEW [ IF EXISTS ] name
SET { NOINCREMENTAL }
or even
SET { NOINCREMENTAL | INCREMENTAL | INCREMENTAL CONCURRENTLY }

that would permit to switch between those modes and would keep frozen data
available in the materialized view during heavy operations on source tables
?

Regards
PAscal

--
Sent from: https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

#110Yugo NAGATA
nagata@sraoss.co.jp
In reply to: legrand legrand (#109)
Re: Implementing Incremental View Maintenance

Hi PAscal,

On Tue, 11 Feb 2020 15:04:12 -0700 (MST)
legrand legrand <legrand_legrand@hotmail.com> wrote:

regarding syntax REFRESH MATERIALIZED VIEW x WITH NO DATA

I understand that triggers are removed from the source tables, transforming
the INCREMENTAL MATERIALIZED VIEW into a(n unscannable) MATERIALIZED VIEW.

postgres=# refresh materialized view imv with no data;
REFRESH MATERIALIZED VIEW
postgres=# select * from imv;
ERROR: materialized view "imv" has not been populated
HINT: Use the REFRESH MATERIALIZED VIEW command.

This operation seems to me more of an ALTER command than a REFRESH ONE.

Wouldn't the syntax
ALTER MATERIALIZED VIEW [ IF EXISTS ] name
SET WITH NO DATA
or
SET WITHOUT DATA
be better ?

We use "REFRESH ... WITH NO DATA" because there is already the syntax
to make materialized views non-scannable. We are just following in this.

https://www.postgresql.org/docs/12/sql-refreshmaterializedview.html

Continuing into this direction, did you ever think about an other feature
like:
ALTER MATERIALIZED VIEW [ IF EXISTS ] name
SET { NOINCREMENTAL }
or even
SET { NOINCREMENTAL | INCREMENTAL | INCREMENTAL CONCURRENTLY }

that would permit to switch between those modes and would keep frozen data
available in the materialized view during heavy operations on source tables
?

Thank you for your suggestion! I agree that the feature to switch between
normal materialized view and incrementally maintainable view is useful.
We will add this to our ToDo list. Regarding its syntax,
I would not like to add new keyword like NONINCREMENTAL, so how about
the following

ALTER MATERIALIZED VIEW ... SET {WITH | WITHOUT} INCREMENTAL REFRESH

although this is just a idea and we will need discussion on it.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#111legrand legrand
legrand_legrand@hotmail.com
In reply to: Yugo NAGATA (#110)
Re: Implementing Incremental View Maintenance

Yugo Nagata wrote

Thank you for your suggestion! I agree that the feature to switch between
normal materialized view and incrementally maintainable view is useful.
We will add this to our ToDo list. Regarding its syntax,
I would not like to add new keyword like NONINCREMENTAL, so how about
the following

ALTER MATERIALIZED VIEW ... SET {WITH | WITHOUT} INCREMENTAL REFRESH

although this is just a idea and we will need discussion on it.

Thanks I will follow that discussion on GitHub
https://github.com/sraoss/pgsql-ivm/issues/79

Regards
PAscal

--
Sent from: https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

#112nuko yokohama
nuko.yokohama@gmail.com
In reply to: nuko yokohama (#105)
Re: Implementing Incremental View Maintenance

Hi.

SELECT statements with a TABLESAMPLE clause should be rejected.

Currently, CREATE INCREMENTAL MATERIALIZED VIEW allows SELECT statements
with the TABLESAMPLE clause.
However, the result of this SELECT statement is undefined and should be
rejected when specified in CREATE INCREMENTAL MATERIALIZED VIEW.
(similar to handling non-immutable functions)
Regard.

2020年2月8日(土) 11:15 nuko yokohama <nuko.yokohama@gmail.com>:

Show quoted text

Hi.

UNION query problem.(server crash)

When creating an INCREMENTAL MATERIALIZED VIEW,
the server process crashes if you specify a query with a UNION.

(commit id = 23151be7be8d8f8f9c35c2d0e4e5353aedf2b31e)

execute log.

```
[ec2-user@ip-10-0-1-10 ivm]$ psql testdb -e -f union_query_crash.sql
DROP TABLE IF EXISTS table_x CASCADE;
psql:union_query_crash.sql:6: NOTICE: drop cascades to view xy_union_v
DROP TABLE
DROP TABLE IF EXISTS table_y CASCADE;
DROP TABLE
CREATE TABLE table_x (id int, data numeric);
CREATE TABLE
CREATE TABLE table_y (id int, data numeric);
CREATE TABLE
INSERT INTO table_x VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
INSERT INTO table_y VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
SELECT * FROM table_x;
id | data
----+--------------------
1 | 0.950724735058774
2 | 0.0222670808201144
3 | 0.391258547114841
(3 rows)

SELECT * FROM table_y;
id | data
----+--------------------
1 | 0.991717347778337
2 | 0.0528458947672874
3 | 0.965044982911163
(3 rows)

CREATE VIEW xy_union_v AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
CREATE VIEW
TABLE xy_union_v;
name | id | data
---------+----+--------------------
table_y | 2 | 0.0528458947672874
table_x | 2 | 0.0222670808201144
table_y | 3 | 0.965044982911163
table_x | 1 | 0.950724735058774
table_x | 3 | 0.391258547114841
table_y | 1 | 0.991717347778337
(6 rows)

CREATE INCREMENTAL MATERIALIZED VIEW xy_imv AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
psql:union_query_crash.sql:28: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
psql:union_query_crash.sql:28: fatal: connection to server was lost
```
UNION query problem.(server crash)

When creating an INCREMENTAL MATERIALIZED VIEW,
the server process crashes if you specify a query with a UNION.

(commit id = 23151be7be8d8f8f9c35c2d0e4e5353aedf2b31e)

execute log.

```
[ec2-user@ip-10-0-1-10 ivm]$ psql testdb -e -f union_query_crash.sql
DROP TABLE IF EXISTS table_x CASCADE;
psql:union_query_crash.sql:6: NOTICE: drop cascades to view xy_union_v
DROP TABLE
DROP TABLE IF EXISTS table_y CASCADE;
DROP TABLE
CREATE TABLE table_x (id int, data numeric);
CREATE TABLE
CREATE TABLE table_y (id int, data numeric);
CREATE TABLE
INSERT INTO table_x VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
INSERT INTO table_y VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
SELECT * FROM table_x;
id | data
----+--------------------
1 | 0.950724735058774
2 | 0.0222670808201144
3 | 0.391258547114841
(3 rows)

SELECT * FROM table_y;
id | data
----+--------------------
1 | 0.991717347778337
2 | 0.0528458947672874
3 | 0.965044982911163
(3 rows)

CREATE VIEW xy_union_v AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
CREATE VIEW
TABLE xy_union_v;
name | id | data
---------+----+--------------------
table_y | 2 | 0.0528458947672874
table_x | 2 | 0.0222670808201144
table_y | 3 | 0.965044982911163
table_x | 1 | 0.950724735058774
table_x | 3 | 0.391258547114841
table_y | 1 | 0.991717347778337
(6 rows)

CREATE INCREMENTAL MATERIALIZED VIEW xy_imv AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
psql:union_query_crash.sql:28: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
psql:union_query_crash.sql:28: fatal: connection to server was lost
```

2018年12月27日(木) 21:57 Yugo Nagata <nagata@sraoss.co.jp>:

Hi,

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.
IVM is a technique to maintain materialized views which computes and
applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018
[1].
Our implementation uses row OIDs to compute deltas for materialized
views.
The basic idea is that if we have information about which rows in base
tables
are contributing to generate a certain row in a matview then we can
identify
the affected rows when a base table is updated. This is based on an idea
of
Dr. Masunaga [2] who is a member of our group and inspired from ID-based
approach[3].

In our implementation, the mapping of the row OIDs of the materialized
view
and the base tables are stored in "OID map". When a base relation is
modified,
AFTER trigger is executed and the delta is recorded in delta tables using
the transition table feature. The accual udpate of the matview is triggerd
by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS will
be removed since PG12, so OIDs are no longer available. Besides this, it
would
be hard to implement this since it needs many changes of executor nodes to
collect base tables's OIDs during execuing a query. Also, the cost of
maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without relying
on OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4] on incremental matview
maintenance. In this discussion, Kevin proposed to use counting
algorithm [5]
to handle projection views (using DISTNICT) properly. This algorithm need
an
additional system column, count_t, in materialized views and delta tables
of
base tables.

However, the discussion about IVM is now stoped, so we would like to
restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at
least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables,
but using
logical decoding might be better from the point of performance of base
table
modification.

If we can represent a change of UPDATE on a base table as query-like
rather than
OLD and NEW, it may be possible to update the materialized view directly
instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes on
base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on "change
propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe this
as SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify
which row
in the matview is corresponding to a row in the delta. A naive method is
matching
by using all columns in a tuple, but clearly this is unefficient. If
thematerialized
view has unique index, we can use this. Maybe, we have to force
materialized views
to have all primary key colums in their base tables. In our PoC
implementation, we
used OID to identify rows, but this will be no longer available as said
above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate (eager)
or deferred.

In eager maintenance, the materialized view is updated in the same
transaction
where the base table is updated. In deferred maintenance, this is done
after the
transaction is commited, for example, when view is accessed, as a
response to user
request, etc.

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command
to perform IVM.
I am not sure which is better as a start point, but I begin to think that
the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

In the eager maintenance approache, we have to consider a race condition
where two
different transactions change base tables simultaneously as discussed in
[4].

[1]
https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/
[2]
https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1
(Japanese only)
[3] https://dl.acm.org/citation.cfm?id=2750546
[4]
/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com
[5] https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

#113Yugo NAGATA
nagata@sraoss.co.jp
In reply to: nuko yokohama (#112)
Re: Implementing Incremental View Maintenance

On Tue, 18 Feb 2020 22:03:47 +0900
nuko yokohama <nuko.yokohama@gmail.com> wrote:

Hi.

SELECT statements with a TABLESAMPLE clause should be rejected.

Currently, CREATE INCREMENTAL MATERIALIZED VIEW allows SELECT statements
with the TABLESAMPLE clause.
However, the result of this SELECT statement is undefined and should be
rejected when specified in CREATE INCREMENTAL MATERIALIZED VIEW.
(similar to handling non-immutable functions)

Thanks! We totally agree with you. We are now working on improvement of
query checks at creating IMMV. TABLESAMPLE will also be checked in this.

Regards,
Yugo Nagata

Regard.

2020年2月8日(土) 11:15 nuko yokohama <nuko.yokohama@gmail.com>:

Hi.

UNION query problem.(server crash)

When creating an INCREMENTAL MATERIALIZED VIEW,
the server process crashes if you specify a query with a UNION.

(commit id = 23151be7be8d8f8f9c35c2d0e4e5353aedf2b31e)

execute log.

```
[ec2-user@ip-10-0-1-10 ivm]$ psql testdb -e -f union_query_crash.sql
DROP TABLE IF EXISTS table_x CASCADE;
psql:union_query_crash.sql:6: NOTICE: drop cascades to view xy_union_v
DROP TABLE
DROP TABLE IF EXISTS table_y CASCADE;
DROP TABLE
CREATE TABLE table_x (id int, data numeric);
CREATE TABLE
CREATE TABLE table_y (id int, data numeric);
CREATE TABLE
INSERT INTO table_x VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
INSERT INTO table_y VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
SELECT * FROM table_x;
id | data
----+--------------------
1 | 0.950724735058774
2 | 0.0222670808201144
3 | 0.391258547114841
(3 rows)

SELECT * FROM table_y;
id | data
----+--------------------
1 | 0.991717347778337
2 | 0.0528458947672874
3 | 0.965044982911163
(3 rows)

CREATE VIEW xy_union_v AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
CREATE VIEW
TABLE xy_union_v;
name | id | data
---------+----+--------------------
table_y | 2 | 0.0528458947672874
table_x | 2 | 0.0222670808201144
table_y | 3 | 0.965044982911163
table_x | 1 | 0.950724735058774
table_x | 3 | 0.391258547114841
table_y | 1 | 0.991717347778337
(6 rows)

CREATE INCREMENTAL MATERIALIZED VIEW xy_imv AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
psql:union_query_crash.sql:28: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
psql:union_query_crash.sql:28: fatal: connection to server was lost
```
UNION query problem.(server crash)

When creating an INCREMENTAL MATERIALIZED VIEW,
the server process crashes if you specify a query with a UNION.

(commit id = 23151be7be8d8f8f9c35c2d0e4e5353aedf2b31e)

execute log.

```
[ec2-user@ip-10-0-1-10 ivm]$ psql testdb -e -f union_query_crash.sql
DROP TABLE IF EXISTS table_x CASCADE;
psql:union_query_crash.sql:6: NOTICE: drop cascades to view xy_union_v
DROP TABLE
DROP TABLE IF EXISTS table_y CASCADE;
DROP TABLE
CREATE TABLE table_x (id int, data numeric);
CREATE TABLE
CREATE TABLE table_y (id int, data numeric);
CREATE TABLE
INSERT INTO table_x VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
INSERT INTO table_y VALUES (generate_series(1, 3), random()::numeric);
INSERT 0 3
SELECT * FROM table_x;
id | data
----+--------------------
1 | 0.950724735058774
2 | 0.0222670808201144
3 | 0.391258547114841
(3 rows)

SELECT * FROM table_y;
id | data
----+--------------------
1 | 0.991717347778337
2 | 0.0528458947672874
3 | 0.965044982911163
(3 rows)

CREATE VIEW xy_union_v AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
CREATE VIEW
TABLE xy_union_v;
name | id | data
---------+----+--------------------
table_y | 2 | 0.0528458947672874
table_x | 2 | 0.0222670808201144
table_y | 3 | 0.965044982911163
table_x | 1 | 0.950724735058774
table_x | 3 | 0.391258547114841
table_y | 1 | 0.991717347778337
(6 rows)

CREATE INCREMENTAL MATERIALIZED VIEW xy_imv AS
SELECT 'table_x' AS name, * FROM table_x
UNION
SELECT 'table_y' AS name, * FROM table_y
;
psql:union_query_crash.sql:28: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
psql:union_query_crash.sql:28: fatal: connection to server was lost
```

2018年12月27日(木) 21:57 Yugo Nagata <nagata@sraoss.co.jp>:

Hi,

I would like to implement Incremental View Maintenance (IVM) on
PostgreSQL.
IVM is a technique to maintain materialized views which computes and
applies
only the incremental changes to the materialized views rather than
recomputate the contents as the current REFRESH command does.

I had a presentation on our PoC implementation of IVM at PGConf.eu 2018
[1].
Our implementation uses row OIDs to compute deltas for materialized
views.
The basic idea is that if we have information about which rows in base
tables
are contributing to generate a certain row in a matview then we can
identify
the affected rows when a base table is updated. This is based on an idea
of
Dr. Masunaga [2] who is a member of our group and inspired from ID-based
approach[3].

In our implementation, the mapping of the row OIDs of the materialized
view
and the base tables are stored in "OID map". When a base relation is
modified,
AFTER trigger is executed and the delta is recorded in delta tables using
the transition table feature. The accual udpate of the matview is triggerd
by REFRESH command with INCREMENTALLY option.

However, we realize problems of our implementation. First, WITH OIDS will
be removed since PG12, so OIDs are no longer available. Besides this, it
would
be hard to implement this since it needs many changes of executor nodes to
collect base tables's OIDs during execuing a query. Also, the cost of
maintaining
OID map would be high.

For these reasons, we started to think to implement IVM without relying
on OIDs
and made a bit more surveys.

We also looked at Kevin Grittner's discussion [4] on incremental matview
maintenance. In this discussion, Kevin proposed to use counting
algorithm [5]
to handle projection views (using DISTNICT) properly. This algorithm need
an
additional system column, count_t, in materialized views and delta tables
of
base tables.

However, the discussion about IVM is now stoped, so we would like to
restart and
progress this.

Through our PoC inplementation and surveys, I think we need to think at
least
the followings for implementing IVM.

1. How to extract changes on base tables

I think there would be at least two approaches for it.

- Using transition table in AFTER triggers
- Extracting changes from WAL using logical decoding

In our PoC implementation, we used AFTER trigger and transition tables,
but using
logical decoding might be better from the point of performance of base
table
modification.

If we can represent a change of UPDATE on a base table as query-like
rather than
OLD and NEW, it may be possible to update the materialized view directly
instead
of performing delete & insert.

2. How to compute the delta to be applied to materialized views

Essentially, IVM is based on relational algebra. Theorically, changes on
base
tables are represented as deltas on this, like "R <- R + dR", and the
delta on
the materialized view is computed using base table deltas based on "change
propagation equations". For implementation, we have to derive the
equation from
the view definition query (Query tree, or Plan tree?) and describe this
as SQL
query to compulte delta to be applied to the materialized view.

There could be several operations for view definition: selection,
projection,
join, aggregation, union, difference, intersection, etc. If we can
prepare a
module for each operation, it makes IVM extensable, so we can start a
simple
view definition, and then support more complex views.

3. How to identify rows to be modifed in materialized views

When applying the delta to the materialized view, we have to identify
which row
in the matview is corresponding to a row in the delta. A naive method is
matching
by using all columns in a tuple, but clearly this is unefficient. If
thematerialized
view has unique index, we can use this. Maybe, we have to force
materialized views
to have all primary key colums in their base tables. In our PoC
implementation, we
used OID to identify rows, but this will be no longer available as said
above.

4. When to maintain materialized views

There are two candidates of the timing of maintenance, immediate (eager)
or deferred.

In eager maintenance, the materialized view is updated in the same
transaction
where the base table is updated. In deferred maintenance, this is done
after the
transaction is commited, for example, when view is accessed, as a
response to user
request, etc.

In the previous discussion[4], it is planned to start from "eager"
approach. In our PoC
implementaion, we used the other aproach, that is, using REFRESH command
to perform IVM.
I am not sure which is better as a start point, but I begin to think that
the eager
approach may be more simple since we don't have to maintain base table
changes in other
past transactions.

In the eager maintenance approache, we have to consider a race condition
where two
different transactions change base tables simultaneously as discussed in
[4].

[1]
https://www.postgresql.eu/events/pgconfeu2018/schedule/session/2195-implementing-incremental-view-maintenance-on-postgresql/
[2]
https://ipsj.ixsq.nii.ac.jp/ej/index.php?active_action=repository_view_main_item_detail&amp;page_id=13&amp;block_id=8&amp;item_id=191254&amp;item_no=1
(Japanese only)
[3] https://dl.acm.org/citation.cfm?id=2750546
[4]
/messages/by-id/1368561126.64093.YahooMailNeo@web162904.mail.bf1.yahoo.com
[5] https://dl.acm.org/citation.cfm?id=170066

Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

#114Takuma Hoshiai
hoshiai@sraoss.co.jp
In reply to: Yugo NAGATA (#108)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

Attached is the latest patch (v14) to add support for Incremental Materialized
View Maintenance (IVM). It is possible to apply to current latest master branch.

Differences from the previous patch (v13) include:

* Support base tables using RLS

If a table has the Row Level Security (RLS) policy, IMMV is updated based on
the view owner's policy when a base table is updated. However, when a policy
of base table is changed or created after creating IMMV, IMMV is not updated
based on the new RLS policy. In this case, REFRESH command must be executed.

* Use ENR instead of temporary tables for internal operation

Previously, IVM create and use a temporary tables to store view delta rows.
However it caused out of shared memory, and Tom Lane pointed out that
using temp tables in IVM trigger is not good.

Currently, IVM uses tuplestores and ephemeral named relation (ENR) instead
of temporary tables. it doesn't cause previous problem like below:

testdb=# create table b1 (id integer, x numeric(10,3));
CREATE TABLE
testdb=# create incremental materialized view mv1
testdb-# as select id, count(*),sum(x) from b1 group by id;
SELECT 0
testdb=#
testdb=# do $$
testdb$# declare
testdb$# i integer;
testdb$# begin
testdb$# for i in 1..10000
testdb$# loop
testdb$# insert into b1 values (1,1);
testdb$# end loop;
testdb$# end;
testdb$# $$
testdb-# ;
DO
testdb=#

This issue is reported by PAscal.
/messages/by-id/1577564109604-0.post@n3.nabble.com

* Support pg_dump/pg_restore for IVM

IVM supports pg_dump/pg_restore command.

* Prohibit rename and unique index creation on IVM columns

When a user make a unique index on ivm columns such as ivm_count, IVM will fail due to
the unique constraint violation, so IVM prohibits it.
Also, rename of these columns also causes IVM fails, so IVM prohibits it too.

* Fix incorrect WHERE condition check for outer-join views

The check for non null-rejecting condition check was incorrect.

Best Regards,
Takuma Hoshiai

--
Takuma Hoshiai <hoshiai@sraoss.co.jp>

Attachments:

IVM_patches_v14.tar.gzapplication/octet-stream; name=IVM_patches_v14.tar.gzDownload
�ZW^�<�w�F����m>��/��w����p���6��>�"�@g!QI�����ofV$��^r��U/����hw�����k�K��}�/�fS�;\xW�Z#�Mmj����Z��~��j�����?���k��c�+�u�cpws��o~�}�k�eR�F���'���[�=�Q�;���j��+n�����-�G�(}�q��j
�w���M��)_������+����>�u���s��hi�)���u�4������������X3�����1
D�G2���f��!_���o��{�����V��~���>��7�U<�:��6S�R�\�XQi+J~���C��9���3�|��L�~f���3��,p�TO���,��y�����Oa�K���|����'���B����$?`�r�?���z�ig��u`�,�A[g�cW�g��������~����w�<����8��y�W�����Az?����u&l��.���E�\.����gs��
�8[s����������UUVL���K������q
�k�Z�Se	4���
�t{o[~0,��2�����%w��k�F&���I��*3�-dC�$�7,�d���
?���<�/o9����
�4�y�2o(B�U�0�Q�!��Q�C�R��;V�iJ���xk2hX:��������7�o�	��	LP
���z�v�J����NA��/�R���Fx'y�g	��}�!�^�F�`*V�)����~PU�U��t�o|����3pkD
���!_q��k� z���W�v�*'�����@�gV V���3�m4\?X���@���|���"~��[�r��;`�OR�]{�r_,<�#�7���b��~.���u��Ao��un����]����g���h��?��Y��w���������{\C��G3������>]���)]]���0�?����^���O�����S����������it������<
����Ag��~?M��pH�G�������7��M����5���.����16AWEO74������N������4OF���?���T�^�*`h��(U59*�TXZ��s��;�3��%�;8~R"��sQ.t�`�Cw�.�<�����&D"�#aPLX���rp�*o99r��O����M
�p�P&d�,�"�����v�;�\��	��v�#��E84��yNzF���2����w�,��|g!�����1g���G0r����W��������gD/Xb�����*��>����Z�s�����]$`�[k����%����u��y��f���o������s�Y��������O8�����J>k8�����	������w��?����������uV�t}n)}��U��������������}@@�!�;n�jP�����Z%����������(����N�R����w�����b(��6���O��L�����EM�L��PW�b0��d��lL���y'N���	:�%VmR~F"��O��o���wu=�pz���%p<�y��p0z����%����5��`���eo� v��,x�|�Iy�u���0�u�mO@,}'���x�w=�<|O%A ��-�AS�-���$�{���7��Ev�3?�fF�:�T��V��53�l
�,#���J���5��z=HX8L��_���2��5��
�^@(1�#�����G��a�j�����e��-6f�\d�R\���������iG��c�����o������(%��~:?��o�����U*
��yZ���(�}]�M����������F�n���<+QP[������:O�{��')���'�X�"�Kz��@OZX:^1��I���I�Y��S��a�&O�Y��Y�/H����J�R�����*���D�W��/,�����L���^`u����,��Vn��'T~��X�W�����������E������y��[���^��9�V5E�m4��������Z�m�k�����#�da���H=�TO�g>�7!� .�tE(���Sb�q�>b��:rV������C�w��~n�AF�F��8��l)�����J�W�a��%��`7�e���|f/�	p~cZ-���W6	�MD_���^�7[�����UYqZ���k�2�]X@��a4�k���nm�H)y���#�a� �j���W*�v�����b��a5y#���a��7��4���%�t�Y��0�4V���z	B�;no��S���fq'0����	Vf�����=���"��
�1������Y�L���p>ZFDp�q��:*�j�q���rV	�W;�~�Y#
�)���(�CH�5~!��Y&+��m�5��y����^����/$$ �z\�"3NW��7��V���[�J�m�yU���.��V����^%��������j*j����`�����	���C���48&�\���_�`���G� �����[F��gm��y?DU���^zv
�)�\�'$� ����=d�=�%����9W���Y���������,:���I�K�V���UUc!��l��`4����'�0����b#����E��`���T�o�[1`��[�b2,������`9�V�
����|q�
���%$���X)�;�����'������-��G�%�\DL.R�`}o���ro\o� p��U�i?ao{��t��r��T���'|p{r���Y��V+	�Vp_D��	y�VS�a�<��3���'|juBPT��6����IlAp�	�z�M$b�l�Ea6����a���~�E$�7�`h��Z���	��p.�mP�O�1�����*$����rS

n�3�����U�}�����
V
!�	\W�V�w��U�����@�b��,��/�k�p|T
����,�������Y�k�X�V.��9)q���P1 H�V�Z�R��.xvQ'��a]'HV�dinmT����3�-D`%U��K�����N����������5��~�~�����
��D�Jb����u�N����������7���i��s�CJ+`!O�-H����\����rP��S�.�/����`��A���s����Y7$��-C��GhI�
�4S�����P�M�lP}�l�s~D)��!i�&5A�|�#���~.r�x���fb����@������G&���S* ������	���f5^mb��(�3
��3HV�/6AS,CzSR=|�G8�D��'GI2�L'���R�	C���PUSSj�����R��*Ro6PE��J)�c-aD��|\v�X�z-G��o����/Df|�
��������Z���It0CB�C�-���h@��K�vW�.V�e�0�z>DM��9�(p	�E!Tv7p9
Yz#�Y�;F������e������V������!���ey���*[��b��_~��wUm6����5���������k��V3�V5�nV���Z�k�FKm64Q�T]���X�w�o7+�������6�������b�-�����:0��w���0�q����ZV���AMY������x�6V��z�R�������-���9�C������c�d
�/c�D�?~T8��M*G[������_�\�0R�������+�J6��z�z}��,�
�{����*mO�b�Hj������������J�B%A��W���aB���
�}���J$���Fr��9�Q����
�Y���N�m�pu:��)�����]����\b�3���S�:�����Y��)�
��x�>jQx�pH������O��=�o/�C�m�FE=��Gf��7sy��%������z������!�!r�����!�}�-��1d�������x&����9^$�IE7�nV*s2	���&w�d��;`(�*��(���Q8����Shn��a��F���(�B����b�wW��#��w^�����gZsi��6�^�W��c�+�Fx�MW"��������\�ue�2E=���e��(�,���q����W�v����g��e.����T����5��5^W��zC���h��jKk�[�����}^�g�}5�En���P=�X��\�m�S�w�����r�|�������V�is�G"�7���X���B�b���ab��0��exC�e���Vw��A6k�)^��3L����m��oE~&8	Q�C��N�"e��"Q"�*�c��~�B*��}���\�����`[���'U!�C>X�$P8i�6�����S~�����s6Z��?6�N��-�{��)�g/6����X����D��sUk@`��L^�����H	K�d��=7������]L%�����w���&�K��3�����3r���\�����{��
�5Q	iDY���������r�3+>w�XC�x+��P��`Z�N�� ���,���GNC�,����+[��b�����6ei�r;l�s	�
��;�@rhiR-�}dv��� ������#y���9X�r��F���rH\�4�U��#3����:�FkX=�!
6�����Id���>��=�@g�@T���p����hU\����[,`�[b>�&tj�vA+��w��ne������4�xm�m	Is�!���t��8�� ��j��R��Q�<1��s��
��_Hg���~���hk�}fZq���w�e��L����\G������\3]��
�t�Ow_�$�����j��%��%�����g�r�Xw��-}O)J�4����K��H��
�$��J�s�o��$`V�Bo<�K��1V�0��w�&�-��A"��D�L�I{#�O�c�Y��u�*]�����
���{y8�%+d���� ]?r�@X�T~)�������4�+z��J�#r��_���)&�v���[h?����� �9�L�h�q�����������pQ�5���"��T��
�x/��2�!YX�N_-��Q�%	�Z@��G�
h$'t�-|W��o�`f0z�% �K9F�EU���D���HFI#�����L�n�B�.=�AP�{��Fx�� �)��m���`]����V�r`����)h8Fca�"��V�%U��k����g�,����M���aRe��*u��D;!�0��o��3�Q��b��	e��&�y6�_� $��+�{�tA�C�?�����	���������8[�F_�=M~6_c���h����x�x�pC��Ut�&�q����Z%��������>E&.�����j���z���j��m�����04�a�"�B�]�����z)�E�J.eO�^ru������-����+>U�4\��	A�?rPT�o(�f����F�7��ad2��mca���W��L%k9�����%�`���/���?��X��/|���,��v�����Yr$����y�!8H�l�������>_��������lO�L��kuuuUu-�p�m�O��R�q1�zR���{uAQ�tx���X�����	=-�wY/����K��
������/��U�S��3��7����B���1~�9�3�x�Pv�f��0��J/DN�7u,Hbu��$��a�������C��f�����?A�-�`��P����	c'B5��:�G�^Sg��]����6��;W�x�����ak�6(���e����}����].o�b���[�������_���;��{����ni+(�wZ�����^9���7wa��*����U.?�L���m���dG,=%$�F��B����3�f�g���A��c��R��C5M ��PiU�l<M�wK��xte����G����))������EKUtv�i��l��,�4�kYvD+�����%�k��^�5h{AB��y�@K^3Y�7w���<�Yu��+UUf����v�_(��PC�������u��j�G����Q1�%��8y��3���H������r�?����ys�xaQ]�`������0W.���Y����B�23�vr�c)L���&��!�A��-�E��	BEF��M&8��l�h������%��C��pDj�����n��`���Nk�fz���a��?����h����"�p�[W'ly��b����C%�DpY�E�g �0
(LZ�k���i����-l�G]������k��Fv�o
�Tw���d�Cn1z�]����K��AQ�.�F��FD�G��g��j`��b��
��LB�T��!<@�	P*���#�`���P�=��[^� b\]2e"�����'����!��5�������I�B7��u@_T����d�I��d�
���|h>���c���(��O��cE�
_�L��������aU=00Zb�~-�38�G��A����\�Y[M�������-"���,+�EO`��D9ymG����8C���,�8}��G`5���w��s��L,��P���������J�E�5p��v�UJ�DcQ����A@]|��zY�@�t��S��wd���9�<�,i�����9������x�l����@m#��-�WK
F"v�G��,���*���*~�����uy���_�'��|[
��o�����l0���G�`Jen��hyg�N�)�����4����������o�;�i}?V<�9.�����h~��}/Z\"��J�=>Zf����E�2rY���mq2q8_\����wJh���_	6�	��[�_'��K�����������5g���x������E}���~���z�K�~�u-�I+����C�������"e��M>�z�z\���4�	�)�j?��~����:������^��9�+kH�I���7��&���T�f�G�K8*s���O��"��T;W�la�����x{N���&����o��.F�����$��������'��~�u�E���|_���;~��#�E�����/�u�Vb1��}�O��]�E�.�`��6�\=n�=��U��o���#�D9]P�����d��y'n�)�-�����J��W,nw�wAv]�f�ic���S�L�)0��T"���ZKx���	~[���$���FvvLJ��� m�&WvO�y��� ��w���;�}���a����6�������h�?�����|K�H������ ��o
5�I�����wa1a��bg#&=Y�oB�]�����Tb��s�������U��{�4��G�������)���3�pcI;�������ig�~�[�
v:}L��]w�p����@�Zz�����h��C�0�{H���-��,��~������4���@�%\$�}���Q���D�I�( ���f���+���T0'����	~+x14&Hp%)����M�h����X5��)d=%����Fb�9Rb��~M���hB��6D�M+���}Q���]��q>q/�*�{�������^���+��rQ3����(��v�[
c���1�18�W�=���J��|����M~GP�j��^�i�[7��`_H���~��3�T�.��odJ�~jE�6��:��x}pO��IN.k�\������������us"�63�9W'����eU0	P���mr�d2/rk������AX���S����mdf��3QZu��u���$���<M���+�A�Gt�K�O�C8���+�27�{���"����^����L�I��JR�;D?ctl��	 k��:���I��-�:������y���/
rX�����x��d���-���mv���a~:b!�j}t�A]�B]a4'[��� m�+���O�Na�:OQsc�}T���~������k(T�T��3��_�h�pm�lB�f�Z0Z��}!��x�����OQ���K�����L�"){J��w�hs�
�p�r����� tBsoLcB��X���{��5���!;���A��X ,���a���0I�y`������@��d3��fq�8�px2�DZ�P�D�|l<Cm0�v�����6�8�d����VkM2f�F�B��[h�I^�'~�������������c�����:C{I�H^
$��74��.��s8!�_������)�_Z��w��$o{KOrd]���]>�S=�J.������n<��6�4%�8��������M��F��E�is�����02O�	�p �u�oh|����f^�y�as W'����))���%M	\}g/���C��F�ld�I9����3��Qp>�������h]\4���c�gpE�-6}29 urN9��#'�6y����Fw�Q��z!��j�x
bg���a#�~0��^���W��l���&��8�������[��E��G���_��=��R�u�(��s)�Q�I��K{�2"�h��A�([G��Vz��F���vJ�V��FAD�
m�m���+������i��^���������q�?
�6��7L�=4��9Ep��&F�����tI!"�i�L���^A��dwB�������?�����F�yZ��(�Hg�c�!��9�d\��M4��^
�������&E$��.��|�����5|��Q���_�:��J������[��
���.����8�Mp kt�@�MW����-����3��
����BT~���������Z��Tt;�9	�X'��<A��K����i������XM}��R�Y�4!���y�3�:������~��{��0�y`�%F�v�Y�}���lc2�
7�`d�Q�{1$=�Z��x�O�G>	?����P��s��
��h0�L�{Ik���bd"�g8�V�L�,4���Ab$�������5�%�M1&�|h���*`c�D��M���])T*�u�JUz*3&VY�����''���)���������A��������������<�/�����������$��5�{���� ��K�����UJ���h�t���p<��������'��lvNJb�m'��bH`�QBmI�d+�hE����wT{��z��Xm6��U��d
�rSv cRCQ32���w	_DA�`i���Wu�fo6�qM=��"C�59?$�G���(���u`���_-=2>Lord�q'md�*ed�$���sW#+"����1�;wKY�#cQ�����������p�B���4�������"P]Cp��D���^x�����aC,��P��������5t�R!�8z��;)0w7�Oq��f�-��32�B��+9���`t0d�F�<`����-���O{�K1�g�����b����(I	w/l�w�B2���H.�:��XU���&|��4`�hE�&�N���W���;$K��r+�J�r���l�[��|�\
]��gPZ��y���
�eG�kG�{�;��);\m�� C'��2D�b��^�������,1
7��F������UT�sz�^���<g�X����3!��?�#����}$�X�*��I�����V����px��3k�3
M��<v�V#�����+�-�p8���<@�T�M��#�!�M���4 &�;�����z�
�7�����:������l]�����z_�]�l��ay���4"��-5a��^oI��Og���:>�����j\�����a�T���*�6����OO�]4� ��g���X~G���������j�e���=�L7����%�<M�)������[A�d�].�4�(���t�OJ�H�x���MnZ9I�a�
D=�xr�eV����r  ���?^K^��G�b��%�k��R�����K�p!�U�Y�r,7�)�q�J6��fFPH�V�x��jm�\D��(r4+@Qx�����gM�C_&���Z�8-�Z��������:p���g����jN����9���B�tT�g�h�9YD�a�r
�q,�7-C�u|H!fP3���5��x%oZ��!����3���N�#�ey��II�f\���~���b�%6��Y$��,�n�y����d���M,�"�p6����x����4kL�J#�P��yn�����u�����$?���(���7�Q���_��#6E�P=�RU����z�$�Av�vtV�<�jdR�N���P_����v,$t������k�!yp�D�I�yp��,z�X�O5]��3J��z98p�>��U[lF��9(r6;?�
�=������zq����<������g`�F��O��AD��?��������wW�}�
H��xj:�6BRz�����'���$�����#Y���SD��`x��f�������'�:���{x��QX����3�1�>�_��B�9v�[�}���/��2��.�h���+��W;��or.�������%��w3r#Qpc���5�� 
�BrV�a�������o���K���"U���b�=�hV�F���?F�zt��z�P?Me�Y���F���cu:��Pf}�Qh�C(sL2�X<�4@�m�������7T%{�sg��u�g�	[���/����$�&4PP���*����w�����������P�d��g��Q)�Nk���j��i
Z5�^�zuR��c��4�������7�pQ�*�G}r�]�"Q�d+@o�u��7��(��U:�G�u�Z1�MW��U��g�|�JS3���X�v[P�����b����=`Cs9���l0�v�JAQ�|^={Y}T�jT����s<��F�E���
z��i�x��uuy2��4�h�4�h��a�o<A���u�p|d�i�(�yR�Q8vn��:�M���G�ubN9<L9��A���.����rgo�-���C�bsr��O?�>���;��'�'�����,��t^
za�����@�B?������j�i�Z���
��v�~';K�/��8���-�o;�C�5l�?��<��y�kMfRo�ym�c�}�0���^�s��k����ao�Vd�^�'O�5�L��[�-�=
&����U��y�"�����X�s2����������m}���J���m�+��Va���
���Y�;��)�y�l�)��9��v����:<��y=��
�O��o/�4���F��k���T=����?�Ly����"r��S+�$��N��~�n�lf�F�������Kh~�AT�+�<�/�B�u2Bv�=F���*�7f~��q�g�5�.��	sB��Q����w�Z'T�1�6����g>�����I7���P<���UB8h��������!y�\#zLtu�$Nt�� I�`6�l)`�_�B�3�n%�
�V�AWP�IA����F*����:�_�Dj~JB�'�8��p�q�)��1�+�IbY\�����F�kIDF
�����o7t��q�p��H6^}?lr}��p�2�a�IRc���+G�|^����U�xd������{�y0�\��,�8$��e`S�b[t^�j�f1�p�5$n��[��Q�Q=\9�j����]���8�8��K��&�����L�QmB����E�oR�5[���0ZF3��H�4]tlaLT�9�.��Q@�1�`	��d�Fc`F!���D���2�%F����O���\'u��{�{����K�L}g��_jO�&�.HA���dAy�c�YD9������o��4��k���d4��t\�X;��V�����M����"gs��w����pq�QH�	��9�a����s�����uZ2?&� cLlLZ�6v���i#���
[��D�������8q@p���(�x��<�S	g�(	as0X�n�4i�b,��(8N�a���6��M�#�Dj���R�4�����h����d�%P�+b���@���I�Q���V���(������V�
N���v���p�w0���|4X�w��y�T	[��q�h�d������=�nD���`���3��49Xp%�����Gc�8��hYP�����,i���40g��� 3��	��q�f]j���=8#�_\}9�G�����@4�UQR+(����F�z�m�3'������f�tdo)�N��
'�\����x��(����	r,%X��'LY�*�Pa\�N��z>��;y���������+��c
��$���#���F
����}ru�>
�i���7�>H~����!?���!]g���[f���5��r�����6�r�/9�����6C6�=F	��wK1s�ph���.S��xJ�F4��DS����!�h�w�����EDqX��Lb0�Ys6��dMl�$�E.A�Db9ezoo2L�.MCC}iW�3e2e���^�!���h��O�:r��2��T6[;����l��2dk�����A�$�q&C!*Z	BK+D9�������h����F�1�b��01��	�w��`x9��Q$3��<�����'F��.�_�:k=�L �-NG���#�m�-����Xj�o�v}��kuB��G�d��**u0��xde�t��|�)��\3��V����E
�d��F��9�8�=gu5��u�a\��S7B�D;p�8��$,���%8��K�~nu:z_����B8M�
���
3�>����h����������n�����\A�3�Y�z���r�g>���H������z}q�4�r����h����0�{>��z���E�q�8}}�����r���4�q�V��	r����*o<Cb�B2���H\A�Nz�3��(�N�Z8�W�\Q`E�wK��r��Dv78�>��U�S+��`e���*�t�r��QtD�XH@3Nk�Okg?%���}�[��0EU{�Qn ���R�������f:L#�J�=����Ob����?�Eit�}rI�����2���_O@��HM�3��&3�76j3��	OFQu�	A&]�o���YJ.os��R��������T4��1��ht^"x����3���2y9X����m��'���c���
z�YD��E�}$p�����������q
��yC���]������{Q��cH��r\���y��~�(9#�3��O�k$���Hum�������.��onn�[7��������/M"�y)dN�^����G�����	8uXmT����u��-�Mv��.�5���m =�@J��+����}ds;J�1A�7�I��P���Vc�s��Y�m����d�`������g�n��
�"Ux��0�J�T���'d#���2����Q�������9@I���������w���z!E����DkR�?Y�+9^:()�M�Td��V%��#@��GE��6���!����J��.
�V��D�s���a�c��[fk�X�8kh�����"�OgXI����:o1�q���e��g�_Od-�����?x�c~�g�w���a��N>ea�c�n�o4,.��k������F�z�N�S���I�M�
���<77p����$��k�Y��@��}��`C|;dfj��<#.����������8R#u4aq���G7wo�����D�0w��!b�~�
m�/&���s/�8!�(M�����1�����9��"{�9���EwC���V���$��*(���L� ���WD�M���U�sv"�-���@�g�Kn��'5�������������na�r��������m9�B64���r7�z����:H1�u����eL~�UDBs�,W�l�nx�N0>ei;s��{�y-�����!B�zr9=�8I�!����vb9�=c����W�-]3��L9�&I#�
G1 k�c�T@=\_<��	g:!�)EM���)��l)8���E#%g����Pr3�"���$r���������H$w ����b-�W�dm���t.�b��F
5o�n@�.>�-lA=o�_��.��-�j<kq	�!����1)3>���P�����X�a7N�����"v�Ga�_zj�`�
�8�����=�a����:_�jI_��rV���G�{�xqSu-�������e2t����YO��R)'��-F+���r�������Pl&��l��������f���)\O�BRW��X�&��&������i
 Y?h����������~r��7j���#���������P��^�O�����#�Cd�a��������vY�dk]5�	�C���VI8=G*��(���D��W_I���"<*{S�$.�����
�,��	�|p_�}k85�u�J��L���T#6k�L���.\�&G�u��)���'p�?����YZ�A^r���d�|��l"LQv_�QB��*������dy>�W%��w���������sG{{�GK�N������.����/l�lk�F�
a�g�����e�uD{�j�����$d�_"k��=�u}�`iK	el)�*}�9_%r=��t<���m"�7'�Y�Unr,��>�����^v�g�]T�??����h����=��c(�3`��K�@��a��j�>v�1A=8�Zh����r<	�-�������m
��3�*�~��+���~eC?���X��-��f?V]������R�K6���G*82��P����h��z?�����$&��B&F�r6I�Z.�������K�R�����s=#WQ��/���o����3�r	�]����I%)�Ve�Rn��-��Z��.+q>7��Zq�����E����ET����������j�!�3%��������V��Vs��O�x�
9,�u��j�tR�C�wA�^l�j�'v�`�zvQ��y�y��y�`���MTC��8aJL#7Y��V8t��2iI�B\v}f�!��4�������H(���8��B�Pbv���W#8�/���������\g33e��mRN��J���lz�Dx���BY�����]��������9��2:v����O�l��%�\h��������Jr�a�S������	�<�O����|Yo��&
�����	s(�d�$��q{/��%e��I�Z��t���W�*G���G���X������4Z���/	�Bb��~����@^Z�
�C��?�H��i�O��������01��eB���A�� ��wS�J����hc�V�&\E+���F���� Z�=���6�w:7*�b���������vd���a���6��J����tR*0a�c��G''����j�����KZ'���9��3V_	�03I�m���U�7it+�:c�t������w�'�m{����I r�Ao��%f���'#Z��q����-�Z&��<���@{�H��b�t�$�`����A��f
5z �-���|%�/��!K����AA�8"����)z��Py�J�38��g�?���B�u��/�r7������q�e���S��m*�'�g��?qL4���s����7;��~h�O)&������#q~�9�N}�.��aS�����*�����D6�F���`���p��6���[4l�q'm"���gC�ob���,�u���M��D����}�����[�	p-������M��Z��>��*'���l���
�,�SpJ&�vH��uq�����2�L�=�z�=��B�1�����HGi}d��p��P3�*F����i��;/��h�Q�(��Y�'�,����
:~:ln�e5�'�c���i�lN:g���_`��u�(�k��	����+zw���	���Q���)bu�%�����.� �M�k�����}b���9��1B8:���8�M3�#Y��i�	y�e��:����;�
w2��Uc~^����*����x�S$$u.�� l�
�P�7%�I�������(|{c�k��r��P�?�1��k�r! ��-!��b�R����"�c�#�bt�����oL��8ge8.���{^P^��d�2�=�_�yZ�v�d�@�B�� ����5�;��m��=>{�������X������^|���l.77�8�E�u�T�Jf�W�n���W>������G���T��Ok����;>I�"a���b
:��yh�F�9�a�Ls\rAs��f����i�uV>?�	���f�!J�
�3�\��8DCov�f���T+�FS+RP�#���o�zSQ��c-U�|�T��k�K*��w�uq��:�/q&:w�����3>@#��Q�e���������d�%�.,�d�����~9K�e�0�#���~a9�Fq^��Mq��VDt�x+�����9�\nK�[��e5��3yZe��eq�v�H��k�
F���\-��hM���l����]|�/�|�v��k(�+Q�<����,4�]c}�L�r�l
Ab(���j�t�B?��OO��v��)�����hZG��g��&m'��:�������a}q�H�~*\�v��bp��&��%��A�o�!�RT���:���l�������6&w,�h(�x�/��I���)������u>b��0��+T��7|�H7�6��/����;gA[N���BFB��u<��NL�m��b�j����v������X:��v��v���}_3��f��yO��I.�����&���h��HF.��Ga�y�������A����h��O�88�)w|�`dU��d;*�A1���Xhb27���A�#�h��"�>����>����D�r��S�����bj-��m��40'I����h��"3����xF��������k �c��)�|�3�P�����I��[���v]��F��^����$����^����t���5"�Vx,�o�93*K�<�G�/:�������������"��#�;*?�
Jm������j��"L:�qn�I)&�u�bH7��|.d0Y��F$6��Cylx!P�
�;;�\�D��(�&P��"��=�,2h3�W/���_�~�I,D�h�����p)�D��r���G�I
�
��3��zPm�����^�k`��f�����s��k�jk@������k�F������EB�;����zb���I�)�*���`6 O��V��("�_Xl�i�oL������=�`�J�D�HT,v��PI��&�����]�<"�zH���+�8^P-&�D��/���K�#���xnd�5����v��&Mx��Ct��3��W�t!����@���c~�x��JW��Ds��Y9K���~��a��C��0�f��5j����T��LB/��IK��=*��Q�Zs.��a�.^�k��S7k�FM�SFH�:;b1
�jc1��v4��
����5L���?&V��e{�i��I��!�G�F�>�
��/��#9�9���vn�t�HYc
+<����S�N�0��C�YO�"Mj��m@I�������+�"���(rB���>o�6�a�wZj����Vc)�1�N9z��W��h0�X��$V+��%1X.[��pcW����m�K.1Ls/���)�`��[� �U.�!�
�6k���;H?�z��bCk~��(���<�@N�N��Iy��t���B|N�D{
w���^k��������yU�o���r����d���f����Z���4:����x|����h������A�����-��I�6��9�J���9�'�V2��0��$~Mf�z	��g���El�A��i�H�E���M^�
��7�8;B�9
�
��e�:�^#�H�a�co�g�t�w)GDR��nf��SWh�o�$�En
:�d�c{�@jjc��`�����q���"���������?SZ�u���z���L"-
@������I��1�Z!Jh
6�������E*�Q��b6?�~�������P� ��~uT;k����e9�����,
���[�	�#7������9����p�%0��j�:�_�N�������rl�����6�y>@eh��p�ux?dtB\�*	LN���y�������������q'z�<�<��{�V8�;R��@��d�x%���{N��L7n��p+�a�'S�T�Z���c����x������[��0�oy��V�m�����7�kc��������u������(���pv.>{tlDp��\��*������e�������V���R���n�_>�>3f6
5Yn��	�ZA`�z�D�o�!��:���w�#|�W4������i��/Es���:,��B�P���W^��LV�5�8~s��w��h�!������V������J��F�i�	g� ,~��D���3���i���j�Jb�w/J�Jur������o���������*T�7t�
u�&��"@�'/������	��k]�i���7��
����=~�Z�������V��J-)��~�����t~��a�WyqG����%����T�.)L��M>_��'����������5���%��G�C�D�]�`����a�\�+����m�0�]�s����$C(v#[������{=V3�T���X]���b:�G�R':�sBa�&�w�"1��
Y47�<QFe��,s�sc]��������?I���W��Hd����I�8E~��*7� ��(9�G�����tfk�8V�TH�Nl��������hH6��1&U��[����T1J�~����o}�	8o�)�+.��Ma
���2.fE�����LK�p�c|���z����t����G�\�Y��A��������~m��T�I���y��Lzd�c����i��H�;lT$��8���A~3~;hS�����~%�-��t8f�9������k��^���f�������1������`2�Gja8Rc�`MI�E����t�)�l;��"�ok�'����`��3��}I��x2�
�����V����e/���0_����i���\�tvP����B5�����;������a�S"����Q�!�yc�#^#|&%/��V�'�
�����O��F��vl<�8e81R�n���8��H�>l~?kbR���v�F��X&@+�����.���]	���K���\~�&U��DA��}	�����A������q�y|��>�p���{�����f���F��R���^�H������,G��
h�8��&�C���p�?\������|�<��������\��2lk�[��I���TV��s�8%���$g��f(��jm*B]}���Kn��M�_��3K����w�p����N��b����PH���#~��r�2M�%��Z>O���I}B�7��..(7��`F��"]LF�����x����zq������;�	���� u��#��'���D���
C�#���6�l04�'��	���irg�����y���H���^.W��o�K��QI
^;V�:��s��5�����]�����e�����t�n�M�n9H0v
���3 ��3���(�a�����;��@���;�I�y��7��JL������]C��F�������������E�����5c�o���:/�<y��o��d~fV]����C?y�+N,wLY�3��o�27j3��oN�g�F2�����^��4���kA�v�=,a����77ry�
��Ix��_F������V�s��kL�	�&�k��������c�k��LnL:3�������Ud�D��4���<��$(Y���j[��~�C�e��#��Yet�4�����sWV��]}&�x�
��-�Z8�\����}��;k^�o���n�P��v�y�����d6�5�i&R3�~8�l����{[nyC�e�?!�M.*��Z��\W��Y�,-kq�e�;<u���b��^aP�J���9hA4����O���
TpF����b��B]LZ�d��K�KDL�||LJ��������{o�^����k5:g�54�+�WT�+G��dE_r��4
�x��w����+�tF>�u��S7�o$x�����,���d���.Q��}TG��1�&��G,����Q1����g1�G��5�Q�SVS��W1�{~G������}��j�S�a; �M�������_�O��"����$
��\�B�tZ�������{q`{6�_�$�b�@s�c��s���Wu��R�L<EW�!^@Q�j|p������q��O��T������,�9����dz�����n���d{��<�4��=��n;(���E���
\P'G=�QE6�"N�^q���I�:�{���yi�����������:W�$�<
�+�v��A�`�:by�o9b�c�%���t����`Y��=�oa�{V�n�r�������b�S;a��9���_���S	��(��N�U2����ce,��
���3Y���!����^�fL�>E�(�R�M
���I�v�_�&�Q%l����KoR��$�����
���_�%O�_�2�����0��/{��<��I@�=��H8��,�2�Y�����c�%x��^?����|���iW�Ypf���K�&$4�������]���,1���v�����a3���b�0J��[S
C1�6Fr�����S6�R��B;a8���8=y�J��kDDS�W�%5rd����Q�%�a��.��t�M��I����6����F�qF�`?�5t��.��G#���uQu���D��	�)��[)>q�/�C�7|�U���
Y}��6���{tW������t�!�|d��d�;��hus��i��L�q�Z��0�/�s��C��Yt�/y������9���G]v}/�ZaxG�������}�(����p�W�?��1V��W�Q2��i�6 �jH�H9Q���g|YL�ap�I#��kF.!(�<��"�C�`Z"�I����V��rT�i�j�6$���Cg�Vr�x��+�\��!����e\M����	��������Oj��g*��F�b���z�O(��
5#�F��P���v���J]�6#�,�3t�C��Uq��4�z������\��<�W���jc���8>�lM�gC��UA�a'N^l8��'�� Jk�����Z�����H�3��:a2�,��4c$��=�V4�D@l���E�9��PH��������IC�4�S�����f�:+��<���g��j������g%�����),>�����)����#5���*��GOi���u�v��Oy�p��a/"`��1�R<��n�'�v�d�z�}���82��Z�r-�Xiw	6����~L�yl�����x�G^����~�4��#���8�u���;��[~�&iw	��h�&D���O/���gG�j;{S���jK��3I&����F�n�S�+��^��m���O�����p,�cA;%���:��uQ��y������0���7���G:�|$Ke���n��5��X��Sj~*��ii�?F�"�c�6l"�5e9~L�2>'�9�k�R��>�Sg�'�+�k}�����/��,�T*s%�R`
�����:��s�������9���F��$���(��f4��M��������O|�L��7�>��+�N��#�L���S-q�8c�x����h�������ZE0]�brhE�Ed�:>�X�0�8�������kw���`�h3��i�qL��f���jx�������9(���.�(��=uOL�G��9�h3�8�v��Z����-6�N�/DC*(��r���Q���NNUR����"�����t�s������v���5����J�
.`���1"�<���h?��/�`�=O�1�����5�T�f��^�$+���8l�9e�yi4F�z��2���;�}U����9T=�]h{�-��P/��z��l��'p�B���X5~}U;��Tk���<yuj���`|��
z?�tS������D�`/�@���a�y��vhR��I�i�gi4������N��S@����W���2+��i�R���HC�[z��J����^=�k%�K<�>�e�W�# _S�	7 ��~�U*(��������"n1��T��T���&��N����@~�.�@�]9�Un����_�9�M����&��,�����m*���<��T����N~<���:���atYk�Q�zw�:*�'��u������M���t�s��y[��Qm�>��7��d}�����A*y�~k�Q�������~��#����T��jX5�w�3K��
�T���y%�hF�$�I�jd���81�	�X�Fo�� e�p���+��!&�<X�����6��`��x�����Q����ro�2�99�b�le+��^������"�6{�0���L���"�����X,V����4��|�j&\K��������j�����wH������4�8z����
��:��EJ��L���<-��[8�q��"�����xE�%��a{0�O8ps{�En����=ll`�K�&�>������t�cT�Jl4�������~}Q���-J�{�� C��fI��7bv�i�Sj)IFc\ G�^|G@� z���;&�2q���~���~)W�zp,}>�B�x�Y�C�����Gg</�#�=.���x��=��z]bhoK��I��*1hTZ����+<>od�1nQZ��1�y�)(����3��=?�������&��)�:�g">	�#��Q�4|��5��T�x�~xF���{�����a����6��r6	���s\:
v=-��~���5
`���=����C�$a�K�2�N0�V{4�\�}JFJY�(y�7������E��&��`Y;�����mH.$Y� ]GP�M�����_G?�\���*E�OY��ml\����(��a(}�0I���5�t�b[�/,�ew����^gg�X��+����T�T����nll,�Ov}}}����7��]*��u��������/	���L
���e����������t)�������U�����H+�$6�m�	J����8����6�>w
�-�4�Y<6ZSr�	&C8���#��gU�� ���~���*�v���:R��IAKz;�P;���E�����z�A�q�IuO���ky��y
���Z����<{���	�C�1���(���;������G���������c��1&�%����������X��8b~=��
/�����@b�=P9`��#��"�(j�������m��U���W��������v��S*/�WM�v�)�x�S���=�.������������%W�3<��4g}�y!�Fv���J2��'�r
���[����
��Tv�&{��rE�E������)S�#�o��D������z�Qk��������A-�:<8.'���c���W��@���0P~p��E�>�|
�LzC9�U$�f*���>��G'����1�[��N0�Riu���t���G�x�s��D������E<M���;
p�����A����okG�9	���,���bK���N8�~[���z'V���)�GC��CB��-XV�
�[W��^���y���]�.U�-��D��z�hiw��hi�����,��-���<?���
g��5O.Yv.4w���q?�Hd�ZHA����c�h6����`@gw��
v�vi�TZ�Ni�������p���WJ�k���P����:��O�@w~J\t��r���sV\����(i�M����8~�q<~�������[E������v��.�K�����E������n\����&.$}�Bb%Y9�����kDo�-��BT�3(��2��A������V)�����v�[�%���7+��9D7�������
�C�o'&1���>.s?"����cHb�/�Z�uk-#&�������"�_��g�q��*�0����7g�)q�*(�'�
��A�gBJ��h������z2%�@�%��9��0�2��&�����������
)�j ����<>���z�����)8+�@x
-��W�����S�{]^Z!�k���`����V�]0���Z�C��N�����!
��>qRRV�6�x��z�C���8�$�$!�[������
������=7�����&����n7����*>�+nL����^�G��z��A�]���{i rul����l��vG����q��r{5J(y0�v[�Z�X�R�(l��_��t�ul	�!D1|���.����[=+��|���
����8&���h����)�����{;;���}�7���.a]�
���a�
����!��e|�P������t0'��FIT�	mt��
8��$����������\�UO.5��Zf,}V-:�n+RY�o���Sr��Q~����!5z"��'�	���}��70��1DL�ztt���gUO&������.���6�������(��4�e�h���xL���E��*�(��[(�,4���jg��9/����MkB6V:M2��5q���S&?�K P�P�&������#t�]��
�����S";����Y�~�6�����fKn)@�g��!d�;��#��zd�7Z_wJ�(�h�$���<���AL�H)%2D������R���t�e��v�2DZI��w��u��=D;�V8��V�"I����6>���*vH[P(�yGy'0�s�F��as5��!�5����S�=9a�l|����[9I�v&�:�@v��#VF�4����x���k�!k9��-�~#ep*v�A��]o���T������C���`]L�������P�~�
AZ1�����.z�c����nki�
-D2[�l�u�K�-.u���0Q$��XE�]�>���YJu����d9��G�������';��N0��I�{CB�:��Qv�.��fT��0�N_��}����������hOv�n��i��o����� P�jo@T$VD�5���KZel����q��V����_���>c����b<�eL���i\\$��rld�T�x��=;�����`��K7$3%�1�=G��	�r��4�D�d<��d
2K�I�>
+�-�a��Q?���g�.�	��	��Pp�������L��HU�Z�L�$�q���h��h����C�c:��J��%�;�%�1�VK5���2g�(,=?� �������;:�S������h_Zd�*��i:�������&b��
q4'���0��z�Lb
�D�B$!�F�yI2ii2����H����+�����Q�iI��	
>��2���!3/�C&1�C&5�Cf^����e}��X��������2s�?d���,����X;Z��a���I�>���64�M�JQU��Kd�P���d������H���9+rs�eP�y>�z2��%�aPb��!0��x4��#�����i��l6��	9�NS������D���is=a���({��j�M�����XP�WZ8�=Q
��ee2,�,��T������K�Q'�:-+�)Y�8��>_4;��XZn7������k[)K������7����&�'C��m���O��Q .,Utt��aw4����l��z�J�5{!t%�'�c|x�&��ak��@��1��s\UJQu��ThH�F*���@��G�2��m���~�0���� ����+b�����\���A
��.��k��Q�be���y����u���Z�~�9��a�)�c]�[e�L�{*W`��%:�U F�^x�;kL���E�gOU�R��'|`<�>x���5���l�h9�>`��3�����x�i��I�&���/
eZ	��&��5D���;��@��=�N��d����4>��x��
u`��D��C�SG����������)(�m1T�����	l�X����:|��[aj��������gQ�l|C�w���(�.��A�e�����FBe���WHx�+��G�-��������������~�r�d<�P�U��������
Rw�����;pe��������i�N}D����<>�%�zU=m�"iV<J*�U,RHh�f^�"-�Ny{��X���sPIHC&�js��m!�Q��>N9��-Z������=po������=po�d���y��7�MV
�l���[��h�l�����i�nm2�������w������=po���,�n�����h����t�ts��4Dk�[�Jr�	��;]��	%����Gb�(��s����D]�����M�Acm �bg���wv�Jl��
�M��{���}����_����G<���0C���n�Inc�UJ.��M����U��)\�+��0tLm��	�������@���������:���~D�;�?<<=y��v�(��4�i�7Ta�2��y�WW��[�^��c	�?(��*PX�u���_(�qu�-�5]cz��i�s�QB���ak��9_LZ��2Q���D%M�k��[;(nb�Ygt���nM���-��% R��r5M�LNk8����YXR�5d@H��<�50��}U;=� �6_��x���CC��5�X�9���v����c�_�W?�Q��7�G�q�/4JA�(Z�y��o�7I�[JG|9G�{):�J�T�
����|��{�����r���R�{+��+����"��<Q���@���&�V���o0d-��Z�0��{"�Vm�
�N���Vbc��jd�E*)O�����&0�`�����2��P��HaG��h2nP1�j�Ucs���C���h0���8���<����V���"�);�\�>*�*M�\�~�ZF��l��ww���bkokww>��I��X1B���.�~V�~
>����	��9��!)��>S�V�1*�6�X�)D���n'�K�@�l��i�zT����qMP���j����o�&�V8��<C{x�[u�w�3������G�2�:-��>���|[�fG���A��/�c�o�G�?j�������o�[�o�������~�8,�L��b]��DV�'����MC�*X����~�9������e��a@F'��$hr�������:���z!_��~U�2��v>�*1*�q1��� ��b��H����OFm�q�}4��l��|�]n��;���`������;v^)b9J�;��-`9�Ky�����B��$������h4}P���`���a|��-��2pO�2���W!�pJlV�V�/Eq�����J���<���a�V�b�d�c�*�{�6���������b<��^����U|6��i���?��N1�����������oy
nO4�]����&�	m�VhX�9%�ww�6�����~y��=��A��6�<�!9�I��x��|���t� ��r��)`jk��*�k�2S��^��N�i{4~�Y��C�V7^�%Lp>b����MR�qE�c���j�����j��W������	`7����1	I	�U��XX��\GbC"���oY����w^���'�[a ��������U��������K-����$�H#����h�6��i��x$�7�
�
�\x���#������s�Kl;��w����!��i�@TX�d��UzS�w�1�M���������n�U,n���V����&�l7[��fp���9�U?�b��j4��*����X��Y�0xO�G �P��Y��cN1�Rx)���������
�m�'L'o�����4xoV�#M*"5�6��.�M��b�Z�������5�[��@%�r�W/L��f����S�9X�U��u�����{���D�F��#-�!a�C����  ����J~��3~$D6M����j�vK;�9+��~ks�a��D|s&b~�D>}&I����)�|%���2���� 0�_����\�f_��)���_��vd/���I���P�r#20���]�����*5L_Q���Su�QG�O�0~�R;��F
X��6G.�dr�X���}s�J�P�m�$C���x�h\Mvq���jz�:��7t��Tn�C��`z9��lE�A����{��ns8�69�Ux�����u)�F���3�����#V;2W1kCp����>9�x"�)�`����{��b9�����o��=�����Jm�}���`��.�m�8�oLG��t<�n��T�3�F�a�Z����������gy���W�7+*o�T�w������'���?��]`�!���J���]��>������{�Q������Un��wwvZ���v)(ou[�[������nk�s��@����*��R�1��*��Yl���uv1R����eH�'�(�Q���Y8���z>���Dm��@5��[�+e�^�/��g3�����6~R��G��������-���9M���0�N��t��U���xY�fu����Mg�{��5�
�;�I���^Hmkh���~(�y�����&�%�l��Kk������r�Y|�L�w������*�.����/M��0�����q&�~������;{�
�������~�3����fb1���yW�uB��G�g�������E�'��E-�I}���I0E_����������
���b����[��P%�\��Y"e�)�;�;_H���cSF��b]
S���D��W4*�>9�$������Y(WZ��?eIk������>`�@������1>��P���'�7\
���^�����
~Sk�5���_�!���>R������������Z�/�P
��f�����*�Y[��h-���/�)��k�-=�q���'�oS���Iy���2�Ngd(T%>�o�r���
��n�_�Fo��}����y��
����_�����G�e�TA/��vk��]�������^����+m��n��:����[=��o����t�z�9]=�i9�����!>%�U�X�`$��t�+J�h��C�	i�x���|c��#W�`�Q�G8�����4�=�M&P�������AA��.
���(9���<����jH�0�F��X����)� GxhBg���r4�hav�,�1�
��k�%1�)B���74�)���@�1����<�9���i���IAg�$_�Q��2�W�*�����xt#�Ab	r���2��P$�Z�u���H?cl��sNa���P�H�=��xL�pg)����6���� ��b�8��9V�@��B����^j
�������u�'�
�z�kL[�
-PI	�z��k�"`N�a1M������J��Y�cF�i���{��:��\�H����:
��:���p�)\C1H@!w���<g��#Y�$���Ne��Qa�� ����&�����'���K������?g��0���B��fdq���2�}$��t�TM'-Ny���i�Y'm8Vw2]����Fu�m�7��F
���QP*!�����0���>(O�t��wM��4���|��A�8�#D��H�V��q��8����qt�@!�7���mq�����uMxJ���}.05�Q0�����y)��U�:���8\�NE���$�|A���u�%������	����;n�
���'�[���B���jg��H�X���{��s�1�w�����(�C#�N�	��&�1���D�B��.��P�dCA�b��U�Sfst\��g8��ep���Qd'�v���72�)���b�����8�lt4���'�|�G�"���^c�B�sy9ds�f�LFz�*6|�w�+=�����U�#-�u�79K���;&����� ������
�R����{V������|Z�\)U|�~�&�{c���4�������=��-����������`�Im�I����8Mt�����[v��W�M��!�b��`��1���N������akR�N��Oa����/���&�LFs�� ���?�N��]��t�nn������t���7��+n���"��aS��}�*��
���j���p�4(x��L�����l���C�$��e����\��k�3��x�����X�?$���o�!���/�N�����v�F��E��F��M?���� ��W?8���u�c��p7���n�f�T�VH0���g42��y~����:����3��aS��b�c�#�)��d���g�m��\��1���1�`��>�Uy;�K
je�'+_E)9,X�B)��
����D�:S�����3V7���I��$/�����`�����A(��<��1�e?^L/s�k��QiM`��i�ag��	o%&=���['�C7���({?m���d�R�Y�G*����re�%g�s�`V�<�����	�uf�tT����L�����y��^~'9�M^���h�;^�Ui���M3\�!���P�Ge��l�zX�m�[���H��y/�?#$�O�~�W���E�B�_\��q�#������g�a����_r�!�d1�[@�M�),dS���5"����0��a�z;��b!U$���+S�����_�zW��`b����������2������L��s�1=���'�@3-�������}'NP_����_�<�-E�]�;�]�&y�c>)S�5�m��6�u�*�V��B�1��R�
�[�\C�Z��L���L�	�G;�$-`Kzp���>@��%=��&Fl�d"V;���;�x�<Yg�BV�&������S�9��������Se�~����8/d/�����Yy�S�bc�{P4�5G%�V�}��U�b�,��<Z�g��}H����*n�`y�i:�0�����������=*2	'K�'����76:D�n>�^Q�;����fi&�Mx�aKh�e�I��:H��%��.��X���C��$�
�(Q�)'Rb�� �m�N�<F�����'��?")������U���d��<�d����
79����iU�4� �.5@HZ@_>i�6���h�p
��d�����.x�I�����hc8��l�������r�n�uf�x������J��o��/!����M��i1H�hJ^���%k�����	~�P�&��c��z���ed<3��J��L��~�d�tf�d����2Wf���$�.�rIuF�8���o����_����6q	�=gU5�����l��I�aq����+a�:�Zv�HfdBTs���vF��O�7�z����*l"�����VC3RL��l5l��y����=P�� 	 �":�z)�9����N�}o_��������$�/��8N^�>8��q���Ly��w6��>{�s��E+����������N��5�Mb/O�k��K'��7���w��_�N�vjx+�Z��������@�ylj���{�<k�&�{=9����f��~�[==��`���f'�c��������a�����7�]V����������Iz����Y���������aU���Hz������W�
1y� ���A{�C�%v��E��������L����)�.S�y|��yS�y���L�o�N�x��)�<aQ�M2�H�dLq_&7��)X5	S�y���L�q$�4
Shh	��
�dL�l�JM��	��Y��6q��|�����RK�U2���n���+����J������mb���-�\��������+	2�����&&_iR0���g?�����)���
Zo���d_���y����������A���X��;�5��6�k�L�a:^��t������10�������a+�1�}�h\����1�R����v�
/�m���������	���=_`�1B��	����d2����:d�������_~�`�W������y�L���4$��
HMKR����x�P� '�pgP��?��3:?�?���W�I;}�N��&�6�P��rh��l\��h��W(�$�6����lj��N��M���Q����*l]���Eb)��dP����K���?5�?����&�0A���W�B�������m�R�h��6�)�4���������t3�_�^���_
N�eU;>����i�����7�q���a��QmB�����Z�S�5[���0Zl���bD�������"�����'��Jt��,;��<����*�4:���s2@�1Npl��p���>�y�R���!���q`e�Z��#�;�;�k��BS�L���z�~�5�%�����ib����:�~��mZ4[
*���/���k�s'�J�J��.���:��w���t������d�Q/J�F�A!�7��>4S����L����F�GI)�,��JM.���{��F;���?�u	�F�y��w����\f/CI���T�%!���2�-���Qii�����
���K�� 3&G)�����7�q�1������O����E9���;v^���k��i������������m�P��r��y��
��CD��3�[.!�d�*��y�L�7h��b��^�v���#�/��:�������8���4�2�\nV��� ������t���a��1mX��^�pSZ����N��1��#�]6��e�������k	Qa����t���y6��YZ%���-4���lS(.D6G�>�N)���e��f�}g&�cs^GX�6��uq����$ n�g����u�`���Iv��m�9@|y��1�bDW�����(�����,����� �;����ae�c��W"����+�!,������u
n/��F~D��k%��,HS�87+���1��~�:c��&��M_s	�!��S6������L���hC��J���]'uty.�Jn�vX��)|W������ $��E�����u��H�_��~@(��v�+�o���G(g��+	T�\\n,��F��Z�c�>�2�q��FQ�Y�����4��e�b�p�����17�X0]����A�g<�+�Q��^��-�J:��?�1��z8g���Y0�0�PvMX}��2��2�%t�.��Gf�~�n|�H�]�A6K�5�,UF����]Jg�:�W2�-q��@Sm@����,@�Xwz�o�<���f���b�	\�oc+���k���*^K\�>�*��.m J�c�z�lN��������H�k����4�n�F�=�7
�7
�7
�c��w��lo����g&�5D�<c��6��Xxv��I�q���1�,���$1��}��E���S�)���%W����wU�*��wM�"l��\�:W �Bv�=�q��������x���>�nD���z�C��u3tm^e���	����"�VBO��%�a1���#�X�7V�����z�3t�����U!�Is4���~1���j�o��,�K�%�e�{�����M�7�P����DR+�E��]�����1�$�������F=11IiFm��1��(�$�X]=x[���q��d�2���MY+��~a���!��4�,~#�<�$�U�r��`F�� /Pe�����T�^��I+d`7fNKZ4�vZ�p��$����$e���>�EG�
S����C��Tg���[9:HKG*������3<t��i�����
��g��"v���2�����@$��0L�)Z_���dg�A/$��n,�b4��Sm�ZS���,K��@$9������N����w�m.M]�����Qo7;`r�����h�w�7�����X��y#!�f�H��{��x���.�4�=D3��7�8�����CI��~�]%�j}X�u4��4�')�Y.~�%�5��\�kU��ki��>����N��n-�g�&\�M�Gp3��j"�]X����mxqfC�����zLI��c�1���u�v��
l|�����'z@4���KH~1M���R�$�{H:F���pq�������!~�sKp�0��9��8���X>�6*Ms�7����]Ow���H:��c/#�P�c�)	�&~��|���Q)or~�[c>�^��������)%������K��H<L�u@�������or���#R�8b��������y����]�f��=E�bI�pt�jT���FP�-N+�;��^,�1�C�a��J�=1�%���7.]��3'���gZm�z�����*���������g������$���,5e�4?������y>�&���+t7�[��o�Bm�e�4^�XHW��������T�����vNYC�BZ���)������k�<�-#�W����*<��E�����C��Rqi����?WE�cCM�wFw��#>y�
�=�;��5��lD��@�0�g���H�}^r�d6��6e�c�Z�d�Z�F4� 7g7����h��
=?������c��%������0y�s���C�����3��B/�A)2���=o����gD�*�Dd���k�i/a4~��le��m;F������Z(���*�W��+z��l�U�2�q��L��d�I��u��`)�,EZm5eF�3�����������.Cm�Eq�-?�z,O�[W��m
5�u��/�&�Ru��@�)�����C�
i��?~,cW��]���W$���U���89�a�6N�R|���D@�v��U�Y�}����&���=P�������)�=�����3����]Q\���Kn��O��gU��$K��U�����+��x�,k���z�$w�������y����Q�~2�����tn�K�,���k��:��������t ���&L��I1N�@�����R�������{�>��N��5��i0y?�tBW[���zP=������~� kG�����r�xb��7���/��k9z�?�qMFC���>�j��2�u�$�M�e��<U/Nk�FM��5k}�/B���3�r@�����Mt:���5!���j�@��c�t��A�X��x\�y�MJ������16���da.5}�^
FD>v��_��<������#=�:��(
�7g)�h��H���z�^f4nR}�Fc4�
��������D�A1��=�����:�I�qN@����N��< �|g�VC���uF��5���-d��L��}�����$�^hP6��'�d���K�o�d�7T����&���E�2k�'1Y4����p�"C����dff�`�{!�qg�hnN��H>)p�1�j�������7�r���n\�o�g��!��}O��u�4/�e&��-��������FH�X��mMt���A!-W�!x���V�zu3LK�5lK�^�E��D�����$q<��0�U*M�� !�4���D��K�H�v46���
�9�a�X���Z
Z=��������� ��3X��g0���U�(��L(�
L�P�/1/��c��6���;����f��6�����������T�n�q��Jd��e'e�Gz�c�����;tl�W
rx��08I���!�)���T�l6�8!]�\X[�b�S�=����l�
t��f�'�$Z�1������l	e�$gRz\���H��?lM��-���k�������f�Hu�C&wzJ��5GdY;q[LY�_���5R�G���ES=Rm-l:�a��@�E�3+�Qu��L&i=��1U��
iMt�BouL�M��Sr�BY^3<w���;E1c����@�L\��Wn��S�.Tvngc��#H��#�XkG����4%�����iTdR��g�f����SE�i���Mr�[��0'���l�k�OO^�Qm�
������x���H���W/������|t���d���-�=��L����]����0 �#�fI�&�$�l#��E��Y��V�n(��������
�����[<��Oz�l�kvh%J4p��0���%��@R6H$S�x8	P��[�>�aLcK��te�kf�t�g�de+X��������i�3n�G���PE�0���-��7���Mo<�3uM\i����Q����Xg����2b��G���O��F!�b4���p�2
�B������;{�����=t�8\@�}~�7~��F:�>�B��R����z^S�8�P���tT�@H3k�^!kU�����d(���%��V��u�����);��Z4���_�g
Xa�T�Jz�a�4��O��~�q�$Q��F,oE�2�����=��.�����x�/���O��������#F��-`y�����1Q�6�!!��	��]���5J`��]<���83�?���<%��iT����@���)*N3y'v�n+gO���~�N9a�xX�/7���x.UXf�S��r�71�h�O�*��a��g������YA�|�������4����[N�E�� ��G#�9���QX$�8�2g��D������7��tSICO��&R97��+P���}�M�@����������5FU&WnzY���%�6�?u!r��>��6Gc0���������_���+����{�����$y`��N.'���>U�E�\�Q�I����S(m��I��m&Y0FZ��D����3&�����������"��p�e�8�Q7T��������2�z��o��"�h�))��1�@ WJ�7��n����.�:����H����,��[2i-qL>&�W�?�A�S���F�i���Q��I�xE�P&����}5{�/��Y�1�����
���5�c�^��N��u��e$�sQ�A����x�M����-�y��;��G�����g�/��������cS�f5���7�I	DQ��&�v(�����q��4,�=���N����R�4]�M�-z��Jr���p�M
��_R��;%��t���=3�����f�E$�Mg�A�/��)�������T5�"���p�[��c�8m�7n���s�X�+E�r�S?�'������]3i}u>����#�:�[�	�9	�
�������$���4B:�� �_Z��E��V�}���
m0���|l�P���[�����@Bd�n�� �7�(�@n��9~�z�/����n��P#���+�
,�N	l@�W�����yF����8$Am(R_��#��[���z�-�{8w%*\�]�x������W6�l�I$A�I��3�-8kX`��p6�;�]?K$���=���$�V�^p�,�*
���"����df�:n�$��$���-����Ko5.qA����1
�9�a��\���P�
*�koaI@����d��tQ�'B�����HIf%ZDI��:���
�u�)Q(��{+G�4AY�GC�L�t�1��3YfZ���i�Z�J��41m����LQ�
~���'����
�B���	d�	$r�n#��f��m�h��D}������L�Gd��������{�
!��?�2��p�
y�2	t���1>��,����r���������wa�#�H&�r2���`����_-��f4T���L�.R������a����r���p68_���������@Y��B�~xR?���z!9��76~�k#�DA��
�p��&���v�O����n��-��*Ko�O�Y�V)DH��,�9���

�m�G�����������	��V�	j�J����F�ce�G'�M�����L�0���e�R���&�i^����)�w�����^0O?��5��M�W�=���Q1������?����&5���U������7(�����X�XB�two~�Z��`[�d�%f�1M�I9�����MJdo<b"N'����hI���61��i3�f��Pdy:j�Eg���G%���W#Ls3�	ws�xM�K�8�t\F�&.�4
kG!������,��IG:�Q��������
�Ao�=����#�'����o���o�C��|M���O�����{����v������5��YP�b�UU9�3w	o��F7#-�o���T
\������Y~?1�3��0����*4�	��D�����1��
������s���nZ|��m����zl��MC��jr����vT�A��!(�y����s������43������/Ywj�����&]\��#��H	cJx,V&������3L���wx=�L����3��f���q�����9�"^��g^���>'�y+�1���`�m�Q�fv=@��\D��j���:P�&$�"��p�/����$)�[�'@+��-#�|�"mS,�aS��K�z�C�����`l�5NR8��3�k��;�/KP���������:����4����<�+�uQ;���'R2~w���Z��i-���$���T��|�go����n!�a����{����r
o��V���7����%9��LU2O�4O�G���AY}�����2��R�0h�sY�[��gx����)/6�S: ����LD��[n���q��yz�s��u�6�L���[��N������e� ��R���r2�^�9�W:��$X���6�F��� EG�����E��<G*L�z����
���l6���8���l�W��-�f��-��S��fU����O�#`��}v> ���AA�
: ��=_��1���S�����e�L.f2�����5�1i��s�M'qn���k�������q��������8������<	|��������xt�����������P�5g��H
X*���&�'�T^�}�ZA/�>/�f���?w9][��(��^.5�G���9���t��,��o�]�s��o�����?����mA���h4z7�3Fo������0�</	;Y������X���������+������2��q�38����$���N��RvX�r*���_�>��[����bgm���1����fC&M���'
�`��Rx9�cb�vn���a{�"��F�S��,��Z���Y��i��������0 9Z>��=g�I@|+T���Qh44sg�0h�Y1aq[�`��j<
��9�+!����vQv)�V_c"���Z���/���Y8}?�L/?J2BU�e���f�#J9B��1��/v}��`�T����t:�:r���Is��E�������r@��)�=���g����+�p���������l=}gM� 1 7E�r6	��$����x*����(��S��;O�9)u\Y-�����:h>0"L�^a�2��+Dd�M�����ht����!vf����B���k�q��b�Q�%f0&��!�$�KW��,�d�DnI�I�	�E�����5`�A������	�m�0��%�>�&h
�R�U���d�e��L	H���f�p���7/�p��3��H�Y���
�����q�n���}8Rc�d{���LQ�����X�R���
c&��x����r� 7����k~<���p��J�����i�Ho�e���r��,G`��-��&Nw�tKt��4������^q�X�f�����)��J���l��pv����mH����p1��^�h���[�O����������)�l����S������*��T)�Tv��J7=��3�,���d4��+wu>�v�bDw��92S���y+(�Uv��A������Ay�T��;/��������J[�F�,���*������f�_g#u��@m�_����p�������ea�<V�'��S�A>���\z\�{\.���~��=����c��W���O����\��"=��'eT���=M&�A�R,��
��N�l�:�o�%�F%��y{h\�g�4����
��z�u��e������CJ�v	O��(�����$��,����zp r��^�C����y
���%g2i��l�eI�;^����kD.�|�����c����q!t���tm3���C���0��P��ht;t��Q��Z3������S` pW���O�-&���$����Fb6x����6_�tt�R�2������Q$V������Nm��	f��YN���[��; K�y�ru6��rE����k���fek;Z3��i�7l�g� ��%����Z�������C0��z��oC-���<<*��an#�������H|�G�f}��`S'��vZ�[���b��*��]�	���-��^�0�%zBU�^	��L[���+~fS�*�����N8�4I�=
.�����	t�$Qz�U���)�H�xB���H���CS�'�C�hdBx/Sva��e�'�`X�@�F��E~�&�@`{�N����������	&L�=������g����u��N�sD����#TbJ[�~ZC��e������W����r6���s�����P��6]g0^�o�1��_�/��W���E4BW,����@��I�,�{vB�p[L^����;�E�i�R��'�l��|$Q��4>�]L/�}3W6�)4/��h�NNT����o�D�ww0
2<�}�8���a�����y�3�{%��\n�fs��n%88��(�C��Y�95��>$��
~���ct��}�$
�����t��!js	o���olV��|���W��#"t���1\
�g�H��z�x ��[����{��������@�P�� H�@���� ����\0���������K��uI��u���Qm���(��\����L��D��P@�4~����VF�����h�#�Iy+pI�
C�P����M~k{��E���f�����h)-��x��G&v9=A�a��������z��^���i����C��n6��/j�'�h\cB�&��<���,ZZ�l����Q�P�E?^������7|��{�e�)2����H�
�����M�#2�6�sz��{�C��lj6P�
����B��v�����~�+'/�1��{V�fu�=�]����(��cv�b�(�N���q��=�p�Z��&�	����$�0E@�g"����@���X&-m�m���O�c<�)�����Y�����M�N�57A
��"��=��3T�)��32:Bz?�j�Mk�c�)p�FkV?n���j���)��d-��;���s��2N9�=9�hs9d������soz:9i������tfc;"��x*�U�t�k���=�54N��Y��-����)k����7E�lj��nLR�#/0�m`��@�l���l�
O�?��(�\M*�P�*��2��!��f8���Su>��i��9�~y;uJI|�r���y�U%��2����Su��a0L�lu6�������mPQ����;���B������'�P��N��7���j�%s���n�i����>Vb)�Ip\@�yG���VaO���T�P�	�y
~�4���o�BY4z�=��h?�XabD�=�#�\�������[��1�&Yg���M^N����P�Tpn����� ���;w����$_yH`�!�J_�=egw`�T�F-
�������Nm�q����-�o^
c�z��oz���A�����H���3�9C9���7�<�$wY0c�J��T����I���Z�:MR>��G����(2
9���[����KbPz]G���^�YQ�U���sA�����&&����P�a!$��,�	K6w��5�����o�g���S������i�^VXT7�
y=������G���2<�Y�L���z�8���|L���XJ����"*��'.���'��'g����K�}\���{��=3����U�A��$��q�
��������e�6�9��z�|�wc�Ck�P~2�^�^t��2I�i�M�������h����D�c��4�e]���.`}��*^��Q2�\��~�$���F���5�	��Zu��������u�vh	���.x�${���[���	����dB}
d����h����>�������3��w��*�.fP�z��}�J��\%"��_J�N�*VvP�Y.�K����]���h?�������w���*�t����)���"���ri�s�0���C@P�-l^c?n��E,���>�+gC��7C�9h�����V�R���g��+F�+m��RN�$�����@�I��m35Ft1���%�Zs��� ��lS�Xk3G�K�0uFi`�$L�i����GH��k^�s�J�nK7E���Fh�zh�&r���1+����9�k3i.�Pk�a-���.F �B��c��@A�{C!��~��E�a����M�?�dR �,���Q����k���cZ�N�sW���UL]��rW���r�1�����>��$t?�oq`�n�o���B�U�) w����J��U,{[��fe�;���+[�������'+�0�No0:=�����`����������2�Y=}q��J=���D��R���J>�,&�Tjh���{�wL��qV�SQ:��,J�_]7B��
	�h-���{�:\�IUl�m����(��O��P)����Wm��C���r������9�P���%�Bs��^�9�����
����S��"�)�4�f�<I����H�H��CY#�G�Z����5����W����l�tT0?��_<�6=)�3O(6��v�S������e�
<�i=��MG�����W��I+�
���
��1��w���P�o�A�b����z��l����`&�������}��������S������G���d�����,�-_����&���0x��s��ZDqv?��PR�F��iaj�+��)o��0��|��U�l;7�s(L����1����0��Q�!���J�D�i/�~'S'��U#��M����".@�%�����i+"�H=�6U�K2r���}���Xu`�udC�o����}��A�B�a:�b_�{9A��6�������{5j��
��ADY�whV+��{S���ZxQ�_���3��!�����L��p���z&e��G"�������.��E���o������;<�\�������y��������,�ni�������a�zj~C�����2qk�:}r���b���������b����5���6)N�$�M����|n�=�T�Q���.�Y�������Q�ag���:�^����qc�<,����/���'��C	T�n��3ka��e����;��e��[$iB2=�7���;�I�N6���WqS�I���m������f>�mQ�y2���=J�R�������>��=�r�I����o�j?�*�l�yFJd����L{��I�L�\��D����f�6L�I1�!?	��-U�����V����a�����'�+1J$o}>_�TB���� ��~$O-�@3���a��])��o�b��J�N2�D�H�vJ����2ed�+������@�\k��l�h3o���&g�.����y������<��_F�p�qM�g�	��|m����6bO�L�b�f�n�9��iE=��b���h���M}������Q��#�����V~#���.�IP~�j%Fc�N/�8��LU�^8#��6�����?����Rz�[)l$p�8F����\yA���:�Rj��@�
A�^
f�;����%vVY�]�L��l��''G����o�L��3�A���M�`�e��IqYL^��So����I�_5:Wq��b�d�vO��t�3���l��a�cO������2��y���<o3��	g/� m�x6�����vXmT�j��z����gRkT�:vy?(�E����8
��x"��8>���)��>s4�w��L���4}_
����3����u�����s���s4�H���I��qj�E[~NP�����h_&���>G��^h��,~>~��:m� �F���6�����7g^��"�9n��O�
H��-�!2&2��Ubj��W����Nr��=z]I^�<�@�f{�g��#R�%��w�M�	z��4i�����p��FY���h�nN^7b���r����|�+-$����)g#���d�RTN���6��r<Kj��(u����}-%�H�����u�p�{�yQ<J�s2b��ub��>�L��B��M����u���<���P:Bg-�z�u�TnH� hQ"����)L�<d�[un 0�� ��A���\����8X���hr�oS��1���M\����(��v��t�&'�J���	FP�	�s�Dw�9dF	��1�T��POr2%����J��E�b�����������5p~�o����m���7�*-�����4�<{����I����c�7J�@�_
AA��h��G�����O��\>��=R�I��3�����*Q��}�����r�0����yZ
�	Y���7q�eSNA�����Y1ly)��;]�m��f�{`�b��u�U<O]H0����^��
 ��$�pv4k9�t��[-�<��6/�������L��*��\m�h%PM�O���x�36>��T��#]�����
Qs8�
�H����I��]�5������6���Q��h3b��v�����pi�tR
�}u���{�������~	]E�l�T5gc�]��gdV�TB���GQ�T�&&��`�'��Z^x{zQ�.I������E:�6�Y�p�+W������KYU7�fT13KW��v��w)b�
iM�p�Z�C��Cqg9�T������N���?���������`��}^2HK��
}����%��X/�
�%�V�[.m���Rp��I��2�	k�5��,9+�_r�>`�K��5}����$@���i�{�a�����%+�L8b��"�6AO���kN�[�[B�S;�5��p"��)m�{��km&;��7�o-;l��X����S��%�/��z]9������Ot-�5����������,�2�w��\�.��D��moV�����?����Y��t�����f�<8�������v��.Uv:�W9��f��
����o�������W��rL����s ����p^�?v�n�0�9�7�*Yj�}@!H����!�=u��RJ.I��|/,d1�Q�!��{Q����m3��vFF����b6�1D1f��T����4��gBS��#8�.8�#���.G�lVh7��Y�WQ��Q��uN<���X)K��G�p
g���b�_�aA3�����I�`5|�4��AVbRc��nVX��em���Q�ib@l��c��5��!�A��w(����)�9�6�Eu6k_f�-��I���~k��b#,�����:�p���j�.���#��x#{�1cc� ��f��_d�dLF��#�J����h��c��j-m�#s�8"���ZsZ�N;����W�T�bK�S����
�{B�]g������H_����8|��Q/@��>�m�G��G�Q��`��*�T[���l2������;��G�q&n�����Z?�%��(��\��i���J��k�E���"���#8Xi46H���q� ����6�qJ��������c MEp���x�^0��;�Nw`��]2"�����\*����a0��J4���~%�r�.b[JH�b��y��W�������*���m��=A1������Ca6!^�����(~��$��'sAKu������qSJ��"�<������p��A"�559h:�;�{������w1@����������G�F5�q\?�$�_,�".���w���3�U	�
�W�Q`�x�O�5�����6"$"v���R.lm^�����'%
��������?�s���K�2��=�_��L7 �%4�H�Xo�x�����5t���%��V�IT�}R?�mW?k��n�N��$���</���;���{m)�g��������-�h�p���&��:f;%O���D5����Xn�t
�|�N�
��I�2�|0rT�u�!�&�si"���w���!�A��t����a6=�G�>�-p������L��5��wY������������[��t���=���f�N��(F|�p����)�����6P�nq8��q�� �:��X�|�'������J
���K#�� Y��[l����Ggp�r�nZm�$�H���H�.nL����p��e���y�+}C����fr��1�7�n�J#�g0�%S��7�!7&s��+�����M����(=/$e�=�4��S��E��H��,�G��X�zITi�V��9�2�&��5K���n����i`��0zM�����t)m�H��v�@���1��#��7��Y�/-����yA�a��ks�B~�]�tsq��%��4�������V��-3�/W�Q{��#f,:�����)��u�c�)�
��}	�w��6������
����
Kk']C��g�I�]�*�'�V�/������)w�� ��f"v��@�~��m�_���e�}M���-��K J	���:$�"��Kr);�
}G%�����)�(����0�p�
����;���:��ppt$���m�p�su�*��\b�?i� %�������Y2��9	}������7fy���C96��%�;o]\,�w��vq��rd8��D�Z���)�Ew����Z@Kb���)�lm�n�	�u���	55���CM��V���"4\�K�J��W�����E��#����D�
��j���F�?'�I�>�HzI�I�s
E�)��P����'�N���`�}�����)m �������e�9	��\�>�=����������h/�R��a�D����%��s���c�q�06�$�9��&vF�$',��k�I�0g���a	;e,J��3}��_9���RbA�	�t`q����`���l )�Kn�`QFHE�#1�fX�p�m����K)����
��w����#�V�)����h��6��~/�8�	��n�����`N'�u#c�q��}X�\7��E�
���F�V����[�'Q�����^�}k��v*D��$�K��)�����u\��yX;jT���S��Tp��g��5[���0Zl���b&�q#�jd�`���CT���]�����=��h�"f�B�	�Z�Is.�����5,��M��&��O�R���9|�a�8����<��}v�O����NXf�y?���8q$t�a:�X)��y����9TKC��g��
S���
�
��� p��kk<��^Qk�|�3�Hd8e��t����U��H;)1=�d$bj�H��w�#����R�H�V�3����l�Q&I������{8
)�#(B�8zQE��FU����YqNU�5�z�.6���a��� � 0��id����C�_|�9b��m�-�k�m��4F&���`����g���{Y�p����WS�d
NF3r�{�X�p��'�z�-�)���	|#�r����;�������]F�w`�E)���7��Ik�I��D#��8���o����T�d�FYq���K��'�[����K�[����9��0�`[,�6�/@��}�8�&0u��@V��hH����V�O�I���)$_J�	�����z��H��x%�G���vl x��jX��$�C�lU���Y��?6	)�
�������>$v�$T�}��%�^1�������,F8���4RH:��"��k���h2f��	29������������-eG&<�0�����Q8�Pr���
M��+[��U��z����?�ND5���i�I���kl �vO���}h�+�,�[0�&���T9�C�B;�����#����Y,�t��-�_P�#ZB��g]BT.��y�+lR����������nR��
��.E��&����C������:��F������4m��-����5T�����k�i�\-��[8xvX���?��JL���.��G�a0�U�g�P�)��;��'f6�����\��c-#&�}	����KJ�	G���7��8e�����Etw����U�I4f C�n(��~��g��H���f�B	@6�%��jq�YD������I�L�&1%u�7�vd:�U��'��IOm�MR����w�I�7�Ma��x�w�����/�v42��2z�2v���o�Y���[a}�J ���p�xo� d2M����<�����n�����>6��,z��pF5�l�����B�t����B8�|���wuV;x}Zo��<��5N���a��U���
�V�*�*���pz������]�M���x^����q.�����mn��Q��v�2W���m�2I���q��s��q���������u*b���W�����c��X[L8ZN��u��������s����`;��B�r���}���B���t�'
���d���X�]� p�bi�����MLC��2�Nl��{a��
w�X{R�B
V� Ab�
+Q9a�E����w�]9~��~Y���7P}AK��.l���������TB�����!Q!��hd�Z����#Veg�|�sFO��������!1�f����$��k�����\����}I��|���7c�lu�mjn�?Z��m�XO}s�:@�[.Z����&l����'d=Ie�"T;9a�������5�|k�rR�?
�!�)�F�w���\�0�uF�=��Yy��T,�'��xevL8U:�W�~�(
*��a�
��k��������{@�5���@]?~L��S��&���E��?z��B/� ���k<~���a�"ur*��,l��g��i�o]��_�U~� ;O$r
r�#��m��K�=���}�����}�4��^�T��#����\rM	B%B[�u��X.O�Zs������������ �3��g�
?hJ�a�wD$��|�1K����QE��Bk�M: q��������p@�U�h�xJ����~�W�W����ml���>���� \�� �����a�{�����d�&�\�������2�;��^�f�Y������~����r�Le�Ocv'N�`�]Ho�wJh�
����aX�n�CAG��Y��eQQiy�#�b
qa,�[�����h��40#_���*���Y�.m%G_�L�L�-2���l���5��O���G	&��{�������Q��o��g��J�2hb�'�&���5�9�*6�Y�������b�����y�l�B5+����5EN�^��+�~f�����K�%���%mO!���5���U.y���5K�,��[�*���X+b�;T�������'h��WO�~�=�u[���~�i���cm�^NQ��fy������1e-}��U�y5��F���$L(��}���;�f��u����ZO����!0=m�a�#;IBW�-�c��q��d~[��V�lXc�pL����@�j?<�&IW�I�����&�8���o����a %��M�����!��.�7X(*�a�yZ�E���!
'P�6�R�'#��0��iT����gl=�c'�c��4q��>����A�Q��,	�8�
���=��8�vT#�����M�Y�h;�"�L+�6b�%�1�VT��[n��p=Xm4�r��B���]-l�*:��o��P�,�3�5��"���kg�V�o,���A��^O����(D�n2�\X=���g�AD|���`%M$!�3��32����*1���?�M�����h������]��-���@�e�h%9�xk����Y��������=��/����X��^�����b_��~�E��(���B�F���V���]	`��.D�|��^���k�a��I
U+����kN���`0���f4B5�yY�v�����(��Lt�D�:��	������xO��i%�������Jd�g�����f�D9������6��9�P��8���0i��J����c/�OQ�G�Gi���C���tX ><�����[�x�d�&���ld�8�������S9�|/9�8��T��|�=���9�h��G	*��*��Y	F���("P��92�r��v6��;��wi/;}�Bq�
]�{A�v6|�H��y�C_�0{(�+_���0��|�K�_M\x�[���{����Y��������$>-�H�L�d/�����`D1aK?9|{oe�Zo�;�Y��pG<�`�����/b?�2�	���U�!.r��r����H�~FgS����0y%]�(�O�A[���%]
\ATG�.��>������^��Ee��Ip�J���Y����n����L��`���Dz��T����vD�^�S_��h|rh�D|(����� 0�q<��h�cp��h�c{h�s~.��O�g�-��%�F����1��@*��:}�]s������P�a7��L��g�����Mf�dI���F�gltc�K!J�J����l�+����?��Wl��p�]<1w����(�/��cI;�i�EL7��(p�d���3>�����ca��F���%r�����@��l|Ra��F�T����XK�o��Q�M!����M���4��"K(�#!���}l8}����=Y��.�|�����C7�+�s�V�R���I�=�tuos
�c�T������$:��u�$���F`u~�������hi���������z��t�7�������0C�[vo`U�����B\���3o���H���=�K@'�m��3L��Y
1����W���aH�1�����M�%��(�Z�B�x�u�$xE��n��+KU)�u�#��b;qd�p(���n(�B7����R�����c�Fl��V�9�o������d��&���g���N:
P<�2����>������O��:��b<��Ct�k����%yip@Z�QV�z9)�'����p4r�i��0�j-],��K���7N��@n���|p0�K���W���<��F�h�`x�v��6<�����u�CB����_��/xw��kO+�]��wg
#���)n�:��j$v��b/a�8�"q��#��bt�f)�����W]�1y��D������p, ���&��yV����J1}���D��F[�F��V���R7e�9����� eea�l��R����c����7B�"�F�a���Yf��UUfIRT5��4c�����dX<����}�n����)��[Q���������uQB�J+�(QIh��N|0�G��[`�YNJ\7��=9�0'W8�\a�f�G�F"X	�	���=Je���a��`L�H����i�P��ih�])B����{�0���J�$��C�e�t�����%��o�rvM��[��F�+�1���l"���G��������H��@��Fif��I{�����AH��3�4�Ap��uv2�E�
o����6���i�u�k��_�k����00P^.�_�7��	����bL 
-)F��s4��$r~���L{o���T��+r���%�����=&>%�-G�Bt,�������`}hr���@A�U���$9�s|q�Wg��q��O������V���i��7�K�������"������w�<t��#���s6kVAQn(��	7���
�������������1H���Zlhb>���@%�m|��>��Es��
9:���*�.�A8q,�1��9��$�=�9�����bI?����)�TVpK��-(���/$�$��C2�/���_e�NC<#1�����Q����3���������M}d��h@��El/nC����I��3}Y��H��B<�������9��.���E�6#���X_C�9���t����dn|07]BX�VRs6���ge�����s����U��s������9�]��C����i�$�����=��n}$�vU�u�
CA5����`K���EY��U[�0�#��.-X|8��6�2�bN�����)���7	��3��=�|<����.�]n��p:A�>}7�v��\��i��D�����Q��a�|X{^}}�h��6�U�8��1M?��=��t�l�����,�s���9�yn��&�����Tv�����s���Ai(P����5�;/��-n���DqoN��j.T<A���*/*�J�I_�w�Vv�G�)s�c^��Q;T/��$�O���aR�3�za��KY?�t�Ea�sNC0�cOx�e�
���<����&���!+B�'�6}i�=
&���	����'�
��G�6��I�����G��zC����A��a�i�a�b��l{�����[�+����g	��3g�1�IN}ir,�R��u��i�t���+,b�x��E'�E�J�� �h�T6��h�u�6�n�Dz~�F�4F������	G���-Jx����0I��$P�qug����!�(��#����	��LV�;
{:��q$��7��0���Z�j����4�
s��/w��m@����o��]��3�0��	�=v��fG0�b �^���o����B9z����	~Jm��V�>7���E�5k�Jq�i�|��;���q�<q��-���{�<��^C?E���(]2/�*��j1;qE,o�>��mN�f���@�O|���n�������w%�t�Q�M�E�Qjo�BD��n���CBa3���0���
/
E�����bAL���P�;j��1u��o��{�1q�����#0�w�E��_�a����I_'TE2���u�
��o����\9��4^(��N����?������;����E�-s<	�O��C��D�<g,��Ic�k����Ll��
q����&��n�*;�V~��������N��U�)�6����R#w5u���_������ly�U��h��G��Q��J���N��~�A�����Dy�R����/���S-�j��}�o{x�w���������F�
�d�N1L�$A\B,��CJ����N���iC2=���gJ�t�O-&�pV�~n��X���d�k��l�wi��k�P|�Z��:1��	v�Y��Tj�{�;����qt��Hq^X�D�62Z�6rp�����(x�{-o���g6����pl��!��:^1��/���wp_,�KE�'�z,��t-�LKx����l4ec%�:��N0���I�LU�5a�j�\���8�o�����f����$b��.$��9Vkm2[��-��������9E�k�''������&:I/������W2��mm�T��\?bcPe���K�4K���DFq@ 6U���]0$������vsZ��I�D���t�N�jIo'Szp���l����ZA��E�@����`�X;�D
�aK���qN)2���t��R�����r����cQ�����2�G�<���~��^wSd�JN7,�����2S[mbe:kQrY���h��-��`�
���n��qT;F�4cJ�	��YSt
�\����%�B��V����
W����V�c����5X�
Gi�b!��v���]�y6��H)���}HlR�(fg4Y,��G|�CkdS��:6�����S��?qY"��\�%��&��(�O��}=����Y|��@4����n��"�b�
��JY�SP.�0��	S=>\K�g���f���8Jq�/[$�'�
���%+U���oP{A���G�G���4�V����`2��I@����{���e�R��E\�0*�t6	��R����=�^�v:����qvR�'���������!�
����sT��������m�xok�l�����\�h�	;z9`���y6��P#D$0�3���j����7=c�`�z	6	.��E�A@�J42�*�^=m<��I�����5 a��x�Y��0G�=9�9�aS�����P��U+�S��� #������~yK<g;d��cMNW��j�=C�q�cL2���q8�,��R,
G�u�y�����K����k3����wv8(:wk����!�"o0K��_��q�B�����k~�tJ���:>i�=��f�L�5����(��o
���P�f�E9r-��|l&&9u���U?n���r���y�i����J<�R?�/X��P��3f�tqvD����af
&�)'
�M�+�k�E{��Z�a�uT@�!1����\�]J��#��"X���X���R:K�C�/��r��6��w���U5W��*FO�E�*_,z?�UXy��:�S��j��;�Bi��w�G�B
�R�|����&���z��)L�2}��gcU�����`�	�a"n���Co��w��w����������7v����k�����_e�)�s�~��s����W�}��7�}���O��&q�k����M5\��y�9�q��&u?M���������1���y�7�*���*�B� ��<'E�NW-�a8�g����N�@I\	��zcCe����f�������W�o���/��Q�T.mT;����=	0�^����~���7D��1m����E�������-�'����neG~�������;���������J�����?����O3L������e^������{#��hA�v�J����V'h�+�Jp�}�n��[���fP��.�w���m�X�3 ��]U*=��T�0��<V��.F��u�,�_����p��a�=*�?��<V�'�����
�}U.=��oo���~��=����c��W�^?*W�E�Mz*DO���U���g6�5@�g 6�J:��6$;6
��{�Q�d�YEi�{�G��#�;�������rE�;�"��?l|��T��[����_�(���l�pW�V~s����6v
an#�M�7�0���7�a����
������~V6!�xB]'�x�[N�GF!�I�������{�������XU�QTzI
r&��=�^L�q�dc��o^���-�{W�t�H���3��2���}�����K9��8�Li_����=!V$�Q����q^��i��E��Br�������2A�a�v�g5�M����+��%������p^z��Ip{F9j��~G���2.��j
�����Z�2+?��-{���_��6�������{KO���6���Nu��i�`��������mC-T��t��;{���;{:��u�?����Y?�q����\S�����Z�d��+�t��5����$%a���
	��������)���&-�x����6������"�-��[1�>^����>��$��X�1A���oK���p�������g Nr?P��fn��F�K���#�o������mP�����p�c;(A��+_cn��}�C�wL�`	��wm ���0_Vo����rYGL�3R�r����{�>}�X�!5�9B���xa�GJ�;��V��N���=��{�V�|�3�h��O8^"%hK��;�%��M���3��z��%�|����6)�T�M����/�P�5������Z���jT<��iO<��S��I��H��|��u�&x�T|qz���ZR���'���V?>8���7�G
p�vZ����v�K"}�oJdR�{��~�c)���v���A�2&���/�[���D���/^W_����e��p
������$��:��y�|����?I2��=J���I B40��K/��b�/���k0�������W(7�7~�������v|��Y�K�F���F�qp��_��xQ;\��n���pz/��qii9{��TNZ�.L#�<Z�GFeF���������*6�W��Fq\��+O����r8���Y����yS5��~����4�E�a��S��'��/����By�d6����]|��p�&� ������y&,�[����/�~����J(�,]R7�)�%���4ub�S��2����S����:�H3�N�|t~N���b�$�p���������g8/�����/<�q��^��������a�7��j����X���_�?G�W&���b��1
�i���<��:������������o�R)���������~���������������v�S���w�re�����6[�[��m>.���e�����=Yq7G)��=��Hx���<�����.b�����f���ET~^{������&���Y?P���h/i0!���V����� /%�������������qgoo/�},�#*�PO"�nu�.����$�@W[����d�����Z$^!g�b;���������\=B�G�]�Gd�K�&_ ������p�D�g�����)�y+��F`BN���"+����7���Y�C�d���f�fp�D��f3��P��?Pq
�l�7�������R�r�\B�#�Z)T�������U��_���l�������������M��R�t_.m����-����v���ed����Wx[x���-E�X�h�v�^�������H��DVU�]gy5
q7'���
�b�B��Y��6��J0�[����z�-��������]��u_K�������[�i�MH1��^Fw���U(�["]��B����Y��D����*��?7��&����[�m�ne�s[�W7����7$f&��qTb��k/��p�\��%S�x���m������W�����j
����l����7�@����j��������<����\s2.����X=��W�/��0	��h���L���]'M�<wZC�pupr����o��`�|����F�i�W����0f��o3�x������x����^����|�q�_�U:%N����5)7�8�E�E:��1(�M�J��m�����Z���'�5�{��q����D {�--�\������m��\�I���h����<%��,I���c����[���OK�;��M�����On�����T�� �������-����M5}$�9K��^k����/sy�k������[}��]s}!�n�:�z8-y����t���G�rt����K�;6��-}<���� ����uua��sC��O��>��Da��b)�ON>jt�Jb9<����6������.��X��t���r.B���������	���;���Z���uVJ�8���eX~�b�JT��('�/��k��b,^�������*�|,����.�!��M?I�������/Z|y��-�����C�obn��<9��E'���KE�\��2�~=����fc��bt"t����!\�V�%����]��s��%�����A���Q�p<V?cu�Q4�E����qr�N���(N�����o�&�
uM�Hh�|6���o�Zz�R]fkK����=UO��K�����~�X�6}����![����k�����w��_��,����<Y�;�*����O���c��_VI�����Ao��>�D;{+�]KJ��&�����>�����#KW�,�fd)�" �*D��{+����b���|���Mz�{'G���b���|VR��M�T����DU���*�@�O���0"���\�K����\���n���o��;p��p:��Y��$t.h�w
;��[�M?�]$�#�$��hh�9��<~X#E��6�0�pn�@,At��o�$?G#��~j.���.��z$EJo2U)�.S��U��q��+h����I����UAM+�+����<|��u��Q(�H�	 f?_��q����"���j:�IiM�/i��|�o&�h�y�p'����h���]���W��Z�ie�������:���Z<�Rd&h���m6�d��K�q��Z���q���b��V�S�vb��l�i�EO.�$����t�S��^�q[���_��S<��_�Z��nl����%�;�Y�s�q�k������z��n����0@��
����x�N��{����e6�[!�+���*� �L���
L����&a��mzw�;������O�/���p-q�L���V�d3�Z�ea�\_%y�M�]6��2����������:�������g���,��^��\rVR�@&l�f�87����7�~g�V/A|���+��R�N�/������~���I��P����������&KvP=;����,�<I��0���B���f�P%���g������Ewh�6�1��������
-�u����p1{�Zxl���5�xt���T�<4�H�{
��c�����������*sF�Gb9�\e!q�)E���C�p��\�z���!4�Z�����*F�)��]�_?��k���Z�n���,x�.:����OK����Fja\Po=s*�������W�_��o���+G~�Y�nk���k���k�.e���g�X�$�����]��5t��/ie��Q�W�-����n�^�Y������
������n�.���u���[{/*�Y�v�Cqy�zUV���M��^�f\V;,����{,gH����z�Sz+xn���E���rZa~]�x�+���~M�]^y���w���K^�������"�=��>z|���(���SYA��a0�M�z��3F�y�2��b}+�}w�<S�K	tw!l9b�jK|^�����d����@|���
J�7(~� �|����RYB�����U�
m�����nAo�u�����r��S��{J�<�I���E���V��������A���(%�
(��f�{CF�v�Z��D�i�2;�o*�TL��4+���@�^�
�vX�|�n,�-&�������~�UMe�aMt8�}/8s���z������V����M�^��4���	���X���RJ9��c%�Zg.doT4f\i��
����[�U�u���"�$�<�ZaG�>(������X8����`d�ah.����
l7`��_.}No�%���5���K�_7��-�u��+2�
aE0����X�����6|�m���i��������B��_�����vk	�D'����96Ko��'�/��TO��7�O�S�����~I���#ve7Q�u�0�0���ik�3E��as����TG��Zv�=�[�\��v��o�Y3'F<r��)��?�������~|�����g�e������A�UCU��"���+82��q6Ud&�����5���e��G��e��w4������O��}
��H������A)����}�������f8"��x[P���>�?����p^,��c�w1Cp@��!Z���S|5�BE�
�������� �'x��!��L��fX:���i�cO�a2�h�L�	����D����7���������
��P��<�`m�n\�0�������TXzG�������=���.���8Q���-�}�[���y�%���%��=���m4�8���XY�m3���(��������|�0�O������s�t�;�_�D-/�t2����p-'�����|�ho�<un��)�_�d����v�l��%�|i�z���q�7D��8_Wy=.
�J��8�~���X����������V0��zO���*.
�O�	n}�4�����o�r�9�ojO/+���<JZ�������Jb��$��~�����~��~9]��,z����^#��/��7�6Iq(��������u��k�`s�����~����^��\�^�����n�c�W��\�34�	y-���w�oJS���^y�����0�.p�b�a��/�C��7�����}
:�I���*�N�/~�Z��noY�� �w����zG
�e$�������;��-������-)��E�:��b��*��[������+�d,�OoJ_w�j�o��l����Kh�n�S�64K�tD_��oWk�UL����7����;m�W8�����S��i~A-�2���s��t)�����mFo8&_N�Q?>���6#��/��p1�[�T�����Fg�5��k!����6�����7��[��
������;�/$�����&9<�<�[�ou� =���uWr�M��?^�Ew����da�2����~�^9�C�^9��{��[�}d���s�����Xmq�^9�&=��nl�oS���Aq����t�F=n ���f���22�c��Wy��
����[�����i�.p}��5_\\���G�o�+���$oDwGQuR4i�Ug�^�U���c�����BX�{�[�>��J�^���U������e�f���i��=��e&��Y��}�7�U�
|�&�C�;��X�sI����������U���mG@Y��/�3������E�`��^���-����&��b���V�&�	"|�~Ews���"���O�nN��Uh�y�~���]<��4��_� ��c���k��QA���|�Sw$E���� +w{��B��K�7�����?8����}/t�r�|���e��N�O�� |�nU��
g����;��J��;��w��H��.c�.��sz�Q.V���$�������_T`��7�o4����2��w	�oI��u�q�|v�@cp��$��5I8��D"�"�>��%����oB��<p*�O�X���J����"���>�����R�����9'��T�E��M	�jI��9�]G�;��yk�����bq���n9����q��.�����m�������f,~���W��j�+��	m�7�
��U�8�����n8��
�N���	����d�I��+��-�N�r�|Z	�nF�tW�G��@�yd�~�|��U�
�N�k����V/�������I���S#�OnM��W���L}��^��g3uc�������N�j&��:����t�����m�Y���yo%tf�[v���8+i�����z�_^��"������8_�"�?8����^��UH���_�n��v�|!�����r����D�[Wt�R������g�/��x{w�i��.c������4�@!��4�>�N#��4���C����)\����$��-����}b��a���-1��27���b����a�}������k@���0��4�(N#w|N#�g�u3��;�F�����h$+w�m(��o�/���
��^!tE�^�s��������n�������XfR��Q���j�K-�}:�?����&0�k
nXSpW�#�d���z�_��by����{�`�`���zc�����$�k��}]��}�{��{��;�����;���.v�M���{o���O�>��7��YE�w������L��o��`5?Y�v�j���I��%~{�J�2��5���nV�����������e?o��c�-a�H��������p�������'�r�� �-H3+
���g�y}m��7N����<��AW"�w6���n�$���_[����������� �p$��A~,X����U.�I%�9�T����j��Z�HX0�q�T��s[�#-��P�r��#�L�+d���}�9�k�1��fg0��r�[�[����&�(���"s��m�U�>�z��t��KN�E�Yr
_2:��)|%�l<M�*�
f�ik�f������j_��A�F���X�!���f�ft���5��������LD��P���x}z\?~�J����|���gI���ZB�"�nU=�������R�i���K��u
�������UhR���P5���e��� �qO2n�d��G3&�������+��4���m�n���9�K�gD�lZ�s�.�ktU�@��/���h8m��������U:A�7:j�qd����[�n������}����A�X�/��a�N'�"W��L^��>�
�[��xyT=~����$��6�Y������nT<�A7�'��A��R�`:�id�����������O��u�$�4��R���5��
��t�q9T9�����z��sD�����
�m �O^7��0��z>��o2���O�|4����-=�����e��/�������������;�b�P~�.O^�N���S6��������{���Q;��&�]���|B���V���V��gg9�!�=�=��>j����E��gH(��� �#�4"�����B��M�|���^�L�����HV��j5mj���bE��MCD�i*$]��1����A��������`[Q���H~c��W����Z&�������!���5	K�G�������F�pi v��[�N}�
�f���,�O��92P��91����S [�SuGU��om��X��/�-�[v���^����h���pd�B[xr���;�?g	�����g
��k�`{m��7��K�����tP=�!�v��=+�~)��<,����W1?�E!'w��l�6��&=�
����6�g��OF�<����]Cv�]�m����oW_0U�e~�����#q���#I�0�����m�|�xtz��4X~���r8x��T�L����E����od�ot^D�����/�[��A��b\@1�l�b�ga�U2���A��5��zqz�����-$���s����]�T�������7�U1"���4��`�����P��K��qf&[�LV�W�`��N�����RS>��x��v	?��`?������!����d�>�A�x����i�1T�1D���G{���K�3"����&�����z9
de}V���2b=�.�} �X�M��3C�R2�T����D0��V�����������d�������g����Fm�����P�	#��]��J�����7vt�� �i�0 ���VD./��v#�� I9�I����R}�I�/d�,7���o���f����0�,��Q���/��3���K�\iZ�h"%�Y=������������7��;�(zY5G���
���������<�y�L��=������H_�P���Cc�����F�a>Jk,���N�2�V�E�9qLZ[��FfW.-1;�'Jg���H-����h
���I�����������I���D���'Y��I.(��b��6��=�e%q�Q��~sU���R�vK1�����Sg�.�5������w0h�[�q�_��N�??�5b\q��.��Ti���l�0�:�Q�K�Ds��{����
��l��~���gpH��k��q6[`f�����f^���(���������7���&^�%7�o��( �6��E�K������T_7~:9��O��b*�b����~���������-5
>���{��1l
H���s%����s�\x����<��`�xp�����(�Q�.���7�MC�v+�"��������H���_������!�&n��N�����4��~���n�e�x#Xx��G(/\�(�l2	�S����+��ez�m�x~��=�6�7���i�#�i���1�<=�D�7�#����z�W(���z�P��#���L���n���%�M�
0�$�&BfoxzFI��TJW7o�P)l�U|G��(������3��(�-�N�Y�[���~����p����(��I�U#�62W��~�������������Ok�H$FW�n�P�{������������f��z,E��r�e	-�G�	=lI�S������(���]3tA��#��.�z�B:K_��9����o��5#y�h���A���z��Dt&��j��v�'�t�*jD<����!Z�6�����1IK�'��|��7R�pv���uh��P�rN;��[f���3g�v���I��d!t��DrJ�I�����v���Eo�Z��I��8�G |�	�[�V���a�2����8_�P�7�T�r^��t����~��]�������@m�2����/�����6v�
�j���O��X�O`W^���O
��1���z���{�����C��5��s����~�/�+AQ��r�$��y�/&�������0Ra�����h�X�f��.P����kon�V�����Lo-�2I�r����:J��_��������(�6E*|(v������HYr�����$�],��KB�~NU���~�s��\}�l�f?
�l�ZzeL��G���	q�1��eok��v���Z�Gr%cL�����N��]g�
"��{�l��?z3r�x���$;�T���D�T�����4��]��%�m���k��"��ee$w������{;Q&K�:N$������EQj�H?BFm�]��.��:����or*)����M����9hTjc(�������%z����0k��T=d����df�U�v�S���V���*y��3V;�����f�BK����^��������\/��%�6j��7,pA#��i���-
uP��H3�h0w�u�k��#��"Q�(�!�$��LG���]�u��+$"��8��<��8s��.
����9Q���*�p<��h��pL'��fv��D0�MbFv5���O9�� V��f�X�5�0��h��J���2t�(�>����Md=�T�,���O�����)u�u��L��b�R��V������`�W��m����gr|qz{vN���b�21r}xx�\C��"�����6��/�����
����9�z�ph�eV��(_�4��h��]�A�Pg�k���$~�*yr}��_�`:v���(�y��m@&�����$an�	Q���B:�_�{� �,.�=����\�H���"#���i�2��U��;�.k�����������>��I6�[���R�gS�w>S��B����������"��?hu�u6�Q��3(fzOI�=���6R�rV���S��i��:zx����"�����=���%?��k�YXEu^(��\����������bq����C�mW�'�U
:�<��t&Yw1��)�bn[�s+�G^z_1!�SV�y1��\W�����h�_��R��&�|�|��y8�Z�k!�����U��9g'�+g�?2��^��g��C�������p����q���h�^�� ���K�)���R��K�����|Q�w2����0���T�����F��4�|KX��"�-���z���k�e��l��a�)�T������O��V ��K�����{z�l���"-����E�]I��t��
K{��/��"�6 Gl/Z�����\��"8t7J`�����F���"���tI$�_N,<f3A��pq�/��S_)���=U���I>XHDWfB:{�*�Z������D5w>:�]�P#0\��l��XwX�t���v[��hM_	�i�"*�4����d��4��};���"F��
T�_#�Rf �����G���z8.�1���Yy�#������?�H'�����DH)�L���m�L�KVR����o��:�R�Z�N'��jS���R
E����d-e��,U�-����������K�`�
�b�����cP%�y�����m���v%��A��W�EI����xK�k`�Z��5���M���(���7`�c�@1��r�H�f��sf�����ZA�\��.��?�{(�������!#�l�6��9\0v8��-���5�Y?l��P&�wU#������rr�,���l.�v&�������mrP?�������TJ�6����+:��/'�=!Yp�����
{�)RZ�l�
F���pxF�����5x��{���>����x���M:��
�����z��l)���h�^�yv<�z����������/+�i%��	�6���3�$c��=~R�RUtW.�*�e��6�>�R'�IO�o���%��
Qc���,:>��i��1N�p���E:!��
��px�4V'7����$!�����H��nH�n\��j��D�-B�5����6��x���+i��`������Kzz%E��+���	�v���+�i��'��V�W�B�"#9�X�������-�K!�<��l�*g��.�>t{jX�}��N������d!
������H/�I��'_����Q>��������������|���1%��0�����d��>x�E_�I��%�f���������mm����C�Z��h�%�Z����C+�{`����
^H.�Z3$�/���-O����C������?/������M���< y��:����3��q�Qpy#�#���=Xc�f�����C��7��s]G��N�t]�.k���������!O9F�`S�p�h�5�Y�l���V����S.k���A'[�������������EP�����oM�q�)�9.�k�������
�*�����<��/�qwu�����l��UWa\�q����C�~���	~N��f[a\�q�UWa��8\�|���`m�9w��r���
�*�����3��e@��w��r���
�*�����s��e`��w�J��0���
�*��0��`�s��e���w�J��0���
�*��0�`
��o+.9=�^\i��UWa\�q�� �{���,���v\i��UWa\�q�� �{�
�,����\y��U0W�\s�� �{�=�,����\y��U0W�\s������|�r!Ty�]��l+��0���
�*�{A�L�rY`��]��l+��0���
�*�{A�\�rYh��]��|+��`���
�*��?s!���9_�h�.�x_�K�����X��#"����H����&��Yi�@�y��fo�f/�y��f��fo�1'f��$��J�-�E�����8v)��XD��6�2X0���E�������8����<���y<Vr�	.Qe�Pr����#�����G�J���1Umb���'@�Z�|%������4<|��lt�S	O�?���zv�^H#5�Dk��\�1�#ch$�N��e.i�h���7TbN��U%�<S����\q5��4)������r�H�1����|���A{rv��(������cC c��:�uL��y�d��h�/�2{��A����z7��QWl��
���kvu��NW3����)�|pu�y}su
�n�9��l�����0�����2
5�%�'�
�!�����?9�����~k����M�*��5a���W�1�[$���i�~�oOU������V��2��0���������^\��7W�p�!�M��������������c�R�~R�5�%�<���4R�=>�__�&2���������
����w{�Nc�X���R����A+�f�}��������~Jv���8����Q�@�B1e������nl�����wK�����f@%��B�5M���3<k�:�f;���|d![6�%RZn�5G3�H��g��������:�����WGd���Ga-��u��/��T]k��"����i�U�!�P����?�{rDDp����n��g��'Bp^��;&\�N���K��,�50��1��������M�������r"�R�l�������������A���w�^TM�A���mK�S����:��Q��zTI[�	{ON��9�e��v�2��D��~���<�Z���H��d��;���J��f�H7
����W������%N~9� T}sU�q����}=<^#+z���������k_N1&'I����g;j��&�G��h;�~����6����	b�-I3 mKQ�p�R�d\3^k(�C����w�5!3��7A�{�����,��|�"�ya /��>;����K�S���>��~�k����}����AIaz(F�������Z�����\���QIy�\|R����T�2�
�v����p���
���J����E���W���\����#���8Jf��&����V�bt9g4����;50�d�F�Y����m/�;�-��;��J1{S��l=���_��������F|���x�T0M_.U���(��`�"�}���b�kkI1�Q��>��3�[�-&�6>�n���������p)NO�Nnv.>|��$�L%%Yq���<n���l1�+n�����.T����Z���V&o!�''8 ��<D�}W(#�����Yq��zp}�kT���OW'���+V����K���H�kc\�j�����Q����
y�&���Jl���bkkb�[�-.�]<�D���l���i;0M��BSO��Ed��T�8[V.��4�/���u��WK��vQD�
���8=9���3����<�Uz�B\�b�\��7�gP����kY���F/#-�i�����������z���6K��_��S��hB0O^]����S���������x�?�!��S�9�|��\����Ya���U@���ca.�70���[$-����%�:������w���u|s�v��� ej����`����������h����^�G�8]�J������?dk(m��_UBh���U�c��I�0=��f��
�[��s�Q�>�_���Enrab7�~1z1��!J��2
���
P�o���X��������j��(m�������3�2E�my�����X*��o�	�����l(mJ!���W���	���S��z�woO��Wb�'��v����J�v���+"�R]��kC�J�+�4��r���`O�C�g��`�3��'E�:��{�Q�nO{��'�������������3������{D��D���!�}�����\���=0����%���V��������}����$����[���K���.i
o�v��piq��y��9�DDiG��"`�$j���'	�'A�$�y�5
�5��c[��t��( �nN�6�"i�b5����:�V��a8\�!�G�"�
P1��n�����r��v�D�5�!
���p�{�N
�	UK��h��y��*��o7j��6������y'G���5��D�Q���n�(�L���^�����]�����+��v��z�I?a������g������#~>���.+*Nm�~��w���������yw�.��Rc;v;0)>����j�`M�����X��L4UY�����v�Wv�����%v�{�;�kV���7�H5I�
0���2zzc}�(kh������PK�1���T��TS�[+������qO���f&�=�n+�l���08�?�O��*��^<���Gm��'���uD�-ZQG{-����;�-��7*\�b3v�`g![x�w����^Y�a���L����rp�~p~�e�9"��:�����o���6'��-���"7_U�s�������)zn���P���&z	U���BV�������c>fa��E t�����#���`���8s��i��e9�s�@;�t_c���S[�|�r
<v��b���v��
wk3�[�k�����4f�BB�6���i��ub#��:V��t�:���s��r�������y��S�m��D�����d��_^	��$��H��?/�X����Q�����8�
snk�X[������v�J����q~q�4�"�qMWL��e������N��5�?at��z;A)���-�n��"
���%����<_�-�TP��#��j�c�|42�/�a;�%O��).��2
Ywc-��5�xmBs��E'�J0!�k�U:Db�6�=3��n�� r�	u�'b�UEp�)�mb���M�*�U�uL*�B�#w��	.Xv� �95����
Ii��:��r�cJ�a#�Jh3
�^:!V�
>�LU���Z��AH��Ld7��r@����
=����Ck�p]Z)$!�5R����}�}�t��f,�{��wL]�kT��i���,�<Il�:�[�'
Z�?5�0�QL{M������t�
]w@�Y���C���U����hP���i��Z��s�>�XF3�����Ij�l�_����#6�3������Bb�K�(��~/Wtw���8�6���#�����{�'��c�U��~�n��$���3�m����5�9FP����T�(�X^[;z�`n�Sp�T:czTx��H�[���15�����'+��yF��,�i�N�u��q����{�@n�f:(3���\%�-��
4�X���n�M�[W�L�5%����H����_gv�Zsk�����'���\@���(� ��?��22!y�6���\�q��a)���h��lv&�/�.�N#,�MZ������[��=���g�v����-�E�`�k(�$�{bIno~G�0_�<���L��]�����&�H�>�CW3b��:�(f���A��R!{eD!i]O��{�6�s-7j�7~�����SeR�q��.���N���uN�':E't��9�d�$���9��Q�9D�0���
+fS���L������f����h0��G�7H�c��)t�>@�pR�{M	]�'����LT�����T^��aRo�|�
m�&��x���k�m�H���� z���"����N_��9$N�q.=���"���D�|�V����>f�]��-_?h�6�D.wgfg����d�A��&zw��a��0v91bI����R%[r��_�</���S����'������Q'����O����� ����_"��
7`�����9�ql7��������#)b����� #+b�^{j����U���M<���� ��������n�r����eN:;����J����C�cN����Ua"��R�������Cmy�������B�����r>V �$m�����b��IZV��"-q�������R8=���e��E*�O�
��������_����Q�hR5���3f!��>��Fe����<����a���@
pdS�X�$i1UZ��?�bj#��Y���M?nfF$�"9&+:���|�u�bM��Q�C��?�1��������
R��+�4i[�����<�.���d����X7�" ��+�"�J`���� �59ww�4?X�����1$����R�!�
����v�L��'����p�>?��0�W����>�����������Fedq��m�����F3v�x��j�����)��/�O�,iv��!�����h; ���f���*P���A~=����e�u:�����/_�����0�k:\������F8Y��n��������#�����)�Q�n�>��&�5����0N��X�_��?|�?PD/�������t��3Q��i�B�����<�r2W�\��V�������>^��*��C��
�=�.�����&X�P'�������v��~p	�@��<s���e�I��0$��3b��	��s�,i�+N��!��Ah��P�d�5W%��*c,��������|N��'r�;0x1����
1U�����j�#� `�hD�"�8��F\�I��H��q�,�d�Q�c�y��!�p}"C��A�f��������H�b�$Y]��1�8���3�����:�/��<Y�Q���-@,�T��r��%_+pxS�@�e��.2TR
q��W2��J������h���P����I/���uX(p�p������;����[��aQ5��g�A�/���e�U���F�NpHJ�O8���x���q���|��-aZ�tJn	d�9����D�c%s�W��,Z
F�/�%��#Lqqm
�wTCJ��e~�]�=4�Z�Xw�@1�'�SQ�����>��*���n������=���1��cM����=�����{�}�KC���7�����v�}`c����Vq*0�hF��G�}�h_f�4O��0n.��$c�����!]�,�T��s����*�B�od��&���R����H��P�x���T�1M(.���W�F�dj���U�y��L�Q�����=,3x-+�U;nM�X��
9�D
��s��&``����F�	����{���;��������y��;Jwx����?f�l���������{�j�Z
%�����%v\������f���v���KF!mHo)�
�0���
�:�l{&�j��4����I4��`�@����(���5�<@���?����Q��CEMb�!���q<ckc����H9-���"�~���!�����1nd��qc}���������P��?�'�k<��8�	e	�M5-���
r���E���J��Oc��4���K�
��Dg)�g�������U������Kt�t&F_��������s����G������.�0<n;t��zo������������h�|z�����x!|`r�������$������F��:�?$�����PB�8[��tu�Zi=����>'w�����J/}:��`�T4����_��p��p��L	l�������Q�y�^^0��xZ���B�>n�X�����aR=j�����(�0�U]
�S��rL��TG)�#���	���8>������	-�@�>�&W�h"�5:�EL �g"�
=��n,��l�(�0$m��[�vC`�E�A��1@�Z{�h?��op9�C�n;Z���R������c� m�,m������s�G���Iw���ST�y��+��1V�o.�v�g;�FV-%yrrd7�������S�V(�=N��[�M�-�����@�G,��CX��$<�[�3i��5q�������,��1�Wy�Bb�j����'3�k�rn=�#��=�h��33!�u�v��#�.�D�)�9�]������m�,�J�*��C��Q\�rw�����d���6zX���6H�s�n�*��^��W�7m��ML�`�;kh��9/mS�[����i���{n�j�1�����
R��6(������K?p���O%�L���Z9��"�����dEA�}��5�������}����y7QK�Nj
0U�����������@�V�s����"���q�{����B���Z�:�Y<������M>0����������Q$%�����{,*�}h 
�����@,�+I;#��M1��f������Na#�2��x"D�JC<�UD�e��:���M���p���.H�k�����6ee0��Z���?I��1���ym��s)��D."��st )(yM
^��^z��zyR���i��=���d|���m�g��:s
�Cn����Zs��.4X���C��� u�T��������W_>�WeQ��9-[�w�3�����e:W`;x�v���G����I�ai��u�����X�2g��``k>�#�b,����������rNp����1^
��6�y��e�
��AGs�LS�+�9�I���"K�dq'�G���k�S��
�������2�\S@C�{��c�0��&$��}�sEgdu�[	�����\����E����x0
������|��+�YF���u-G��~l&[������!*��������8�B���[�fn�hz�,n���4�oZ>6=��0B�d�(�=���7n��B������Q&��:���a�a���N��=�d.�pf*�������qX��n�(������}����������W������t�6	m+�}��}��Cx�����=6�[s(�l��.��s����`X�����E��p�����"������O\��?-��Lp��]d  �!��ZUS=y@���S�]$���MP$P���`K7����5�(����o�z���0P��������P����1b���F]R�i'>\@1>w\'��d���5�O��,�(�f��g���-*�<3�V��������f��Z�M�6�"-l�p[�����?���_�-KRJ
�
m��- �2��[��X���v��$tfTo���������V�Y4#M�o�����E�i���Aa��D�N0�&��T)������K�XX��Wu�gV�0��e���N�����I@���T�����\�o��r����)V�Y]<j":���7���o�e�h�0���E���ap��m>�0F�|�b0���l�;vc��!��0��J�/_���
a�RP�g��I(�ew�����IC�+�|��J�cq���dq��.[�M��V���{5���u>��*`��ia�)�9�{$cE�����u8v_�eKA6�:���wG�/��
�:U�g`Dy�9q���B�w��pO������D��)a���������,�Q7/G��h8<��q�rQCWy�L_>{B#�y>j�G�	���]<���_�������h&�:\����pg�v,���{�I�����.�N9�u@�r���WE��NGu���q���A:_����A�O��D�R��?�b\de[��7����2����4���h�Wu�B���^&d������EAei�*�S��b���B�bjx%\\V)�K��6:<)a�I�uD�yq���a��"p�A�8tv�5�F����`%���r�xEuY���u2k��2{W&�@�0�f�*��,�����o���<�g��G�?����D����LV�re��FN���JCqC@R!D4J�Ft��%s\�'�����uG�%���>*�.^������UUVl�k��L�2Gf�F��3��8��M���@�cf��o�U�J�����J���i��,,!�5��9l]���*�u���^��Q��������=����Y�7}F�v�iVv��w�N	K���G�z� ��]vPX������)��D�����
��*����6���
t]I��l����&�+���RKW�e����%Y�<������!���(nw�Q��1�C��r���B��,[���l����������oQ�^���;|��v������1Yi~b����9����������m��m��m��m��m��m��m��m��m��m��m��m��m���������@
#115legrand legrand
legrand_legrand@hotmail.com
In reply to: legrand legrand (#90)
Re: Implementing Incremental View Maintenance

I have tried to use an other patch with yours:
"Planning counters in pg_stat_statements (using pgss_store)"

setting
shared_preload_libraries='pg_stat_statements'
pg_stat_statements.track=all
restarting the cluster and creating the extension

When trying following syntax:

create table b1 (id integer, x numeric(10,3));
create incremental materialized view mv1 as select id, count(*),sum(x)
from b1 group by id;
insert into b1 values (1,1)

I got an ASSERT FAILURE in pg_stat_statements.c
on
Assert(query != NULL);

comming from matview.c
refresh_matview_datafill(dest_old, query, queryEnv, NULL);
or
refresh_matview_datafill(dest_new, query, queryEnv, NULL);

If this (last) NULL field was replaced by the query text,
a comment or just "n/a",
it would fix the problem.

Could this be investigated ?

Hello,

thank you for patch v14, that fix problems inherited from temporary tables.
it seems that this ASSERT problem with pgss patch is still present ;o(

Could we have a look ?

Thanks in advance
Regards
PAscal

--
Sent from:
https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

--
Sent from: https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

#116Takuma Hoshiai
hoshiai@sraoss.co.jp
In reply to: legrand legrand (#115)
Re: Implementing Incremental View Maintenance

On Thu, 27 Feb 2020 14:35:55 -0700 (MST)
legrand legrand <legrand_legrand@hotmail.com> wrote:

I have tried to use an other patch with yours:
"Planning counters in pg_stat_statements (using pgss_store)"

setting
shared_preload_libraries='pg_stat_statements'
pg_stat_statements.track=all
restarting the cluster and creating the extension

When trying following syntax:

create table b1 (id integer, x numeric(10,3));
create incremental materialized view mv1 as select id, count(*),sum(x)
from b1 group by id;
insert into b1 values (1,1)

I got an ASSERT FAILURE in pg_stat_statements.c
on
Assert(query != NULL);

comming from matview.c
refresh_matview_datafill(dest_old, query, queryEnv, NULL);
or
refresh_matview_datafill(dest_new, query, queryEnv, NULL);

If this (last) NULL field was replaced by the query text,
a comment or just "n/a",
it would fix the problem.

Could this be investigated ?

Hello,

thank you for patch v14, that fix problems inherited from temporary tables.
it seems that this ASSERT problem with pgss patch is still present ;o(

Could we have a look ?

Sorry but we are busy on fixing and improving IVM patches. I think fixing
the assertion failure needs non trivial changes to other part of PosthreSQL.
So we would like to work on the issue you reported after the pgss patch
gets committed.

Best Regards,

Takuma Hoshiai

Thanks in advance
Regards
PAscal

--
Sent from:
https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

--
Sent from: https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

--
Takuma Hoshiai <hoshiai@sraoss.co.jp>

#117legrand legrand
legrand_legrand@hotmail.com
In reply to: Takuma Hoshiai (#116)
Re: Implementing Incremental View Maintenance

thank you for patch v14, that fix problems inherited from temporary

tables.

it seems that this ASSERT problem with pgss patch is still present ;o(

Sorry but we are busy on fixing and improving IVM patches. I think fixing
the assertion failure needs non trivial changes to other part of
PosthreSQL.
So we would like to work on the issue you reported after the pgss patch
gets committed.

Imagine it will happen tomorrow !
You may say I'm a dreamer
But I'm not the only one
...
...

--
Sent from: https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

#118Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Takuma Hoshiai (#114)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

Attached is the latest patch (v15) to add support for Incremental Materialized
View Maintenance (IVM). It is possible to apply to current latest master branch.

Differences from the previous patch (v14) include:

* Fix to not use generate_series when views are queried

In the previous implementation, multiplicity of each tuple was stored
in ivm_count column in views. When SELECT was issued for views with
duplicate, the view was replaced with a subquery in which each tuple
was joined with generate_series function in order to output tuples
of the number of ivm_count.

This was problematic for following reasons:

- The overhead was huge. When almost of tuples in a view were selected,
it took much longer time than the original query. This lost the meaning
of materialized views.

- Optimizer could not estimate row numbers correctly because this had to
know ivm_count values stored in tuples.

- System columns of materialized views like cmin, xmin, xmax could not
be used because a view was replaced with a subquery.

To resolve this, the new implementation doen't store multiplicities
for views with tuple duplicates, and doesn't use generate_series
when SELECT query is issued for such views.

Note that we still have to use ivm_count for supporting DISTINCT and
aggregates.

* Add query checks for IVM restrictions

Query checks for following restrictions are added:

- DISTINCT ON
- TABLESAMPLE parameter
- inheritance parent table
- window function
- some aggregate options(such as FILTER, DISTINCT, ORDER and GROUPING SETS)
- targetlist containing IVM column
- simple subquery is only supported
- FOR UPDATE/SHARE
- empty target list
- UNION/INTERSECT/EXCEPT
- GROUPING SETS clauses

* Improve error messages

Add error code ERRCODE_FEATURE_NOT_SUPPORTED to each IVM error message.
Also, the message format was unified.

* Support subqueries containig joins in FROM clause

Previously, when multi tables are updated simultaneously, incremental
view maintenance with subqueries including JOIN didn't work correctly
due to a bug.

Best Regards,
Takuma Hoshiai

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v15.tar.gzapplication/gzip; name=IVM_patches_v15.tar.gzDownload
���^�\{w�H�������&�,���3!6I�k���33w���[��I�x3���Vu����Iv�II�����Uw�����T��K��rs��'xI�j7��)��R�3x� �[�����e�I��
�h>3����T�����]���-{���z)��.Z��K��`�w��'���jS���U{�Mg^U��p�����xfi�a[U�����A
F��_i�����mT����h. ����[��S��Z�Z��n�X�����6�������yG��pl[p�� �A�^���6KD�%��2��������|�:���5��]M_��h^�[��`?8d~�� K/��KE�-iW�Jg����������{�Z���Of	I��a��%f��-�I���F��6t�vY+����o�4u�2���T��vt��;0�sl;���]�cP�h�R0hx{�����9��4�M���&|4�'8V
�c�ji����{�T�VK�:��H����o�<�t�SS�va+x�@��a2����W��a��!�nyk�T�����e����hi��a��3������S�Z���i�v�L���i*+�V��2���Pm�VZ��������j�0��g��	����3
M��/�:~~�^������F	�������W�ltm��9c�l!�V���:.�4�D�B~s��]l��[���9M�v��R',w�	�������9��G�+�������J��HV�n�"+$����%�_]�,���q��Pvm���Z}���~�W���������������,���`]]��g�|-x����6l�������=���������v���C
f�,��z�AD�����Z�4�m	�b�b�=��gb�_���(.���G�2=��3�
��������vM.�����������i��(��$M������U��jU�q�sT�5x�G'5�[�Y�q��/�m9��[������x��F��X���-i�������������Ho)Lfm�)������,��
���YR����,���1Df	�,���,�����gJ���0,�4o*��0��D��t�������`�7��'�`�w&z�<��6c�
�l�]��-�����q*�W8��g���_�Rz&4�K�`)�O.�9�Gl��9H���>]����&V�K���T3R	��'���GX��
��E#�!vrsP�fO&����~����������O��4s��,�K�
`�V8�q#T���-Em2q]]sr�-i�C�7��:j��Z��kuYkj#������F����F��6�lS���I\��it�s����b�J��;)��&��!�4�zom{T�Qo��-�K�aUr��m���nmmM��&��i3�A�!�ud��	r�~�3��D������;f������z�[`��~�c6���#�)s��ls�����y/	1���t1=�}=��By���J������6��=�=�[/q��
|r����V�u�������Eu�b�[����U��~C~�D��.����w���l�B-������� 6��.�kW���D
�Mm*q�� ��GE��m��I����L���B�����>e����tH�
��$
�g�Ro����|{���f|�n9_�b���z�9��pl8\z����K[g���p����m�|��q�i`p;��tv��'�h����VM_��
�����j�3�D`��.�D8`�I�gjx�����.�
�K	{?C������P��[�E�$*���.������
�/�V�����%�k�<r��������y#r��G!/���j�lj�
a���a��/q�r��7�D����	�����:��}�Lx���6�Q���s��4�!���K�~���E>b����-��x'~��[�%��q�a��T$ ���@=����:��~R�9��W�LG$�����c^A<���xm4�~�7~�w����LNB�����Q�_��=�%���cO����lv���z�a��G"n��X���-rS����������1�7L!�r-Qlgc��#<�6Q
�����B1��4[��,�t�������[D�������W4<��7�TP���������xn�H�J��"F��{�Y��=2M��bi��#�O`��Ml���FUbM�,i����gh����.��A��������g��)�c�����x>?�]��-gLE������V1%�|W�������[�wx�j);9�V�J���5�FC*QlZ�S�^���}�-��x*[Az��B��38*��B�[,��Uu��O��##�B�l�x�h��f%C�Q��	&��LN�;3�Sf���
����_�]#Gr/Ua5�s�AR�)�|bc�h0]�M�2�<�K��~��%�AUH(�En'��������M-�����~�"��6���g��U�hI�<V������Y�)�:��5[�<^@(*�4"�Q��^�c����!�&�0�����F]�4M����P��)73D{�A��{��.��:s����+0��Xh����&�����va.�GT��v�aP�����7g�e���Yu�r��U^�A���������V5�����oS��F=��+���V���������������d*�)�X�Q[�B�����������r�W�����eY����,�Y7K��h��$V1k����w�}�"u�S�	�
'�7��5,D~�f���.k�UL8$6U�`���>��0G�^�����g�#FC���w�!o@�73�q�O������l#���FK����j*-����I���<��2�Fe�Dr�t����Y��4&N�����w�A�Z����tEgM�'�m��������������[r�[����-$��P��v�d������������x��
��"qs�\u{�R�?2,U��[��������.L�C��P6�B�+������uO��Nb�>v���g�-��\���
��T��k�R�6*��k����h���p��E������������R=�Kl�^�w������^���?�C����L���P�����Q�`F��r
oOO�cP�N�{
�}���x��Y���ll����{���ag���}b�!!1���!Ry�+��R�v|��%d�	�A�u�T]��3b���=����^�z�������
�v��v���L�Z��Vq�`�;0��+������_�U��L�I�o2^*�M�
^��[]\�������Q�*7��?Qe�}��L�n���X��z�}��/�4DYjn|%�&%q��y6�y�8$�����;@6�$���wO��?�����G��!���H����B��\oPq�4J��d��vzrt��s����^R��0~����f,hg�X�_)��$��w�;`��D���:G�?���i��9������q��_�=�����X1�O����O������e�qY�,�d����%�ZM">��~���D��������	l�&���g|B�I�����l:��W45��3,��?����@f�3���
���QZ��{���!����;�7�d���3p���;������/G���� ��;>>���������oQ'w$g�Cod��w$`[�������,���%;�rHv��CO�{���C-�Q(���4���g_B/�L~&�/�}65
M���ZF�+�����*t��H������$9���r��uQ�x�r��+?���#Q�	������&��[QV�!��*�h/�j�8Po���<�,�v�18;?.c�}pr�`������}�[w�g�H(Q��!��1
�F#��S
xwzr���k��H'qN� w6�w�������{����3�
`Gb	�pOMJ�|�$PS�N�mGhJ���]=��,���v��v�s�
�5����a�cS�Zl�_���P�t��Z�^X@)��
:O��������P���fU��.VV�rm���5o�F�P~V�a3/=I�|������f�UYV9Y5d����s����[���`~�����BH��
]8�l���	]��"rIL�b�X%T��k�uY&y.7�V�Ul)O;%�����x�����������`�g0�H���M:�l�������6��h���$�-
RZi0,����GU����k��0���%�n8�-��E&�Y%:���G��i1'-����`E��h����t#$[��^�nv���V��|����I+{+���RY����{}Z�8����+�N����JC]�c����3���O��g�T��R��RP=��FJ����Uv�e�7}*�n<�����x���O%�����S�v��-�T
=������u%%(%%(�������h!���U^i����z?o{D��u����D�$��0� P�<c5,��Y�pEsX���J�8�������p��|n8dC���P��ta)�L�e��l:sD;6�H"ic�����
���������CA�s/<4�;������S�"	���t�M,�@y���
�Q��}�yrm^O�����iKG�bw��t6���.�0��yA4�(1W|D�
�-��������z~�H���J$�I���\�?���9>�o��y�}�����T,	� ���S��Tk-w��������#f�Vn����0�WY���L��T�-H�����LG�����Na�Qin$��"�_������O�,Q&���j��� ��,N����	��
O����A��Z���#�"d!_�����g�+�g�����&{E��~w�)����h�&��C���b�K7��8a����x�E�X���b_s��%.U������Z�W=����5R�Y%�M���:i7��Sr]��wY7������f�Q�I9�S^�Y��8� ���[��e��3��_(��_���c������q��W�[����:����N��@��T$DGf1o��;��t�t���������.7*e�e��l#%�����1����vg�}��c���$v�+��\)Fz7��
R��F~A�D��(��I��G���h)��o����F�z�;ew'Z)+K��l$�h�!��K���K�lpV��_9�>��$��%*�G���@���q��S�s��IU�C�Q-��
������7i8L`�4o�X+p�8/Z@,}��xM)\R��M[�gO[��Q�Z�����Q�������!%E�X^Q�Ke�D�+4I��*b�&Vl"P[a���`^�r{�BGq���~�����V��Z��U�����jM�XKv����k���~Q�������H��{�-i/�p5��K4��WrQc�XR��o�xV5�Rm|c�7���Z�D�xA�on	_�[���p�r�g�+��:��{a�pYJxz�	�,�{��i5��m�{��+O�v�j�es��//�{��s�} �&�:��P=@y+SO���w���Z]��*��m�1���e�����x��G��������by����BZ�����Ktt�^��������p�TZ}�j�+�X�a���3,�
���w>%(�`A:W��Y�!w#�����G�w�	��+l�&Pha.���Z,�c:�JW#D2O-�P��g���cwQW��EF�_���&���������Ffw3���,�8��h��G�F	
2_I����>
=�����Ht,�+g�������S�N����>Ft)��r��>M�1
�#��2��5�K:_ZJ�c�����^m�>��%��T���\����^P^x^�40���t���X�_�)�<�	� ���d)�IH�	E�$(�'��7�H�Pbb�}��"�����
	.r��0�kuuUuu-��0�|Uz���K���@�m�������~ ���a�����p+�e��i���$f�;VA�
,�����)����`
T�r�����D�LA�{CE���l:F8Z�I���r�.]VK���]�.��_��������Wi�a�
h�y���l
����v��j�>�:#��-����w���^t�[3���c����{��^�����}�.�!5�`���������L6�����I����L���X?�PA>�l�\��(`:��o���<8���W7��cE
`���.��F:���q����]�1�[�����}}]�������[�~|(e����XT���9��6E��(��`s_WY�8���P|����U�nk���4�L2$g��w4����0���?���3�U7?c:�o"����{����+=�3�k@@�V�������G�	��7@@���"h���&]�l����W�l�+4��2�l�a�W�C�G��#�	����H�h���kc�SN<!QN_(�e�O����'�����$�o�^j�����������a6��z;H���IEhH0�Y�_q����gz��V
��\j��?�*m%V)|�Z�N�y�uK^/�j��<�����iY���`WM}��$��%,tG����d�$YK=Y������ �:�����;���O��-���,�|O \��s[��X��O���'q������3��V��	����8Y�z;(��J��$�>u(N����3C�^0o��6����A�#O���WT��S�|=P�z�P����V�����'��Em�Y�����!\p�?\�9����w�l�E����=�"t����)*�ep�������V���&�����+wo��`%=I}���dJv��V�z;����v_���Q��A�� ���2����{��_���v�wA�������:|}���EA��T>-�z�*��
�����Ay����_����bnQ��W���E�vo�|!J�����	�Q�M���p�0�����1v��Q1��������l����������X,5���riV���A�E5� ��E]@=Z���_���Q�]]�xo����1�S�<E-���dOQ�x+����1�$%��N�Q���R������s.�����������3�2�{V4&����O��f>��p�#��&� ��1�x�%�5{@�|uj��S��������G�uo���Z=15�L�vQj��Pd}����d�>�$M��0a�+�mM�R=������I����T�����X��T�z���<x���'�e�be�����]�b�)�ph{OK�bD�~:��Y���s�|U��E���Frot{��X�8�:
�yLE�����O}���������4
0�G�,jfON�L��U���p~�������5��)O�����cnFO"��"��u�FM�o-}���o�����m��a�o�D�0I���pQ���nOS��W�\yb���d�>]��f��)(�'�{��jyAf����y���_���@�/w�����	��	{9=.������|	��R����a]����+M��=Bt�E��k� ����f��Bi,���HK��R��_���7�s)�.��W�g�J��f��Wi
�pF)_�^������P=�����iQ��4 K���h�>�T��cD�Y���sk�+�'��5e*����T��f�y��{s�{b�9��_�w���n�	}�&n����i������l�+�p���������3M�1���\��a�u���0�cx��0S���f!�zh�l���V,j�~5�����/�c�������
��5�.����+�����-v��F���g��2��2��C��B�1���ox�D�������p����k&�a���G����_ ��(����8<�
|u��{�$�T�3��?U*�{;��@�L����{��?gR�''�}=J��!TI������*9�L0{���k������L`�����4��F$�����bF����_w������.F���1Y��!(�B��Qt�H�Tbf�+�������f��T=.U���R��5����p�RP�)�������{}UJ���s�*t_i�Yfv_n<K{�?�rg��z,������jz�4���'������[����&w_&O��!���i��|�D��'�v�Y*onI��vcK��GL�>�0���&)t���)��C���:���Wn�	�ui)u��_��y0��������x��;����T�]�U�K�-J�SU�����Y�9�j�'v�^��<9P~�����HJ��3�C���)�+�����K��e���8h��1b���O"F�B�5��L�i-]wf��u��\wfZ�eh��,�2��,
�Y�gJ�|���);y���v����O���A���Hu2����q���	:�<dX�H�S���m6���E��]w��rm1���J3]��Qi2��C���|rA�ro���JjMf�mZj����d���"7����?�v��4�����z�Z������<���RWq���!2]/�!D3���w�]�����lyr����C�S��y�k��N=��#�t��'�t�K����c��<�
�x�<������B/M4�s}l��,S]������P0|v�+9�N���|*��C|��=�!�<��0��kQJ��kws)����,%���i^�),�
?�F3���
3k�O��e�������gq����Y���yp��NLs������{�^���vI�I��+�����t�>���������0���Sy
y���S�{�:��!��v�gHxs�s���*)"�W�Q����Ci%da��wO��.��h��@C����`�.~c]~c���o��f��o�4&�m�������������h�6~�F(==%�4x������l
w�6������'������h�z�����XC��"��}[t�����wx;F�`M���*��0�cnyP���|�m;8��L,N���g������/@e?�r�0���K��d�Vg�ig�y�#�mBA�K��D�����c�'�����$���1jz�T�i�"���l�[[��%�y,rS�������,<&kN�p���U[�M��pf�7��dAq�s���TU�w���?��;,g�/�����{�����fd�n�X�8����i]n�]�����A�H�/Z�0��Z����9�/qY��a�����Q���E�-ne�1����YC�h�~<�C7�'��A�����!�L5��(������~?sL@=r������T�w9��`�q9�����x%�s�����E~kH����f�)�ru#�
�79��HN���}=_������W?���������3M���/m����{���vyrZ?�5O���:����A_`c��#(6����{v�F�o��V�c�� �������F��u�ojGM�^�(wq���x��\K7"�������U��J�[�<++�����2h�Z-��_D��4���D$d����vc�=�;�p���/^���B����d@*�M\�^;��u<(y�v�w��#	���<��D*/�N��Cf v�![�P�x��&�WO�����~�&��~vvrd+�����n�X
�@X���(?[����H������q
$���=��;���8���wjlq����-$�(
�f���������[k�CUM�
�f�@����]�� ��'�I� ����[~����60�k���ye�c��uI4�KY���aI���d����TI	�wxt�;���B;���@���Aw�2Gl[����O��i@(���8?����W�DRi�i�����q����m�J��G��u:0����bP����$�����
`	�9 r'\(��i���_���.P�����g_�k5���������O��������)�,T[�
�y�Bw�H�������K�������������K��y��U�K�+��U��_���~��YB	�������=����`V`�$��A�l��_�`���#=%�u�i�5�Jt��E������;���=�o������z�x
��:���s��Y��z`U*���i����x^��`�X��F�[g�o�����J�`����N��<Eh���Y�:;�����B���u1���d	^P�0
��0��������:1��n3R�!��~�>$����������������}�Z���<
q~�_��������=>@2��#h��4g��P��[�5���l�m7&��o���C��==0���mB��gF���� �������{����p��'�<<1��$<��>N�����	�ri D��l�8�R��k����*\���O������]C��Jd] T]� �]���+/bS�ud�6��5%5�=���������K���+X�$���<X.w���xc3f��'�&��t����'(�o=	G������7o������\��"�SWt�����&2�Er�C@=}�w���1=�Y��x��kLn�n5)Z������z���[�d�T=�=��z����mG�{3�VS�����l`s���9����j�QAt�����m��"s�dLZ��q�����z��o�$��c����5�,�n�`Jv�D�8��9E�~���r����F�sMk/1-�-aY����������E�;��nZ0��G� '���X(n�Q��R��O<�}L�.�@J_��V(q!"�-7.N(M\���"��3Y�	�a�#��� C���~-iXG�1�^�Ak������M���w[VN�������7ncf�wS���sn��,S�tQs��c4��(7Hk�YM���������1*���������/��f8�E�yM[:���[p�!jN�T�k��6Ih1���$�Yz+p��Qp#��4�����Vs��z2idf�w��]��������%��s�eg2���SK��q(�n66N�I
>�����#�H�t�y��i� E�F��Nr}N�DT��}��iu��0h����U���w"Rg1@���O������������x���8�8	��F�(pVw�#��.���_���k��/���?9��3��n-����7�Z��Am���?�=����~~N��������52NN��|�������9]��]+��Y��C��c����4WE��\���s�����:�H`��������� ���	Ya8D��Y^�u+���u���W��U��������-��3�?=9j�����}�sC�.r��W%��N/~��RA��yG1�����+�������j��]���9�a6�'�~G��)%���~L?�N~G�u�.@����=�7I��
�*�n�����Kb�l�+��$U,4yv���
������B�P%�����Q�w!k�wU�.����[�f��Q���UD��`=��o�/q��I�{1�66W#Z�\�W�\=��R($o��8��z&I7FA&�x1�R��	�$(~�UY�A��Qv���1�P#�&������
����1i��5��t�ul����H_Hc��+P<c=[��[�y�Jti�5E��A���vq�z���cu�#�����m����.��Z�(�h�+��n�^��Z�p�!�5hN���rF;��9W�2g��Z��I��\������x�3���t�^Oln^:p�x�{���m�{6�9�Ah��-�sew� w�g(�������+�W�K��b�[j�����%K�K�����MT�������l���obsw��+6�/��
/D���yI��P���c�9�N�F7g�-��k���� ����G�@�`)�G�~������;�K�u��*l�x@�F[$`���'�x�z�K��������Y��;><�o��m������]�Gx�{=8]@��{�������g��-����'5��
p�������7&�)�����5�!����gD�������RF�6�S1g`M�>��� VD��^��*Ww*�R���J{;�v�������2�?^Q�\�.��
�0�?D�U�$����\�S-��j%em�U��|R�={p�ZN��9I=>��NS��[�yJgEx�
����CX����<�k+������b�T�������]Z��]��9�Vg�~B���B	�P�-U`�&��p���@���'K��pd�~����-'�k�s0DI��������w�\����� ����%}����Z�"����]�{������O�:o�;/������m�T��������7���	��'��Z�7%w�����o@`}7ahs4������:�C���������KV.�����P
�!�P�EC��q�?��0q+��S��Gw���i(�g�B����#�����>iv�jZ6�r_�,�����������.N�.��4[��_�|��hIMs<t~#�C��[OV64�d�z�����X���r���g��v�zbC���dXr]K�f�?H?��%q��}��2
D9��<�C'�u`����3��ZOs��i�����:���
]:;�
��l
�[�Vl�D��k�<�@�vRt9���)7�������f'��w�;��te]��s{$&v�$������ia3��S,i�j�0 �i2��I�?���>�~�c�hfY5�H�4�o�t�#�����!��No���&4O�W��[�������CCQ[��W}:�
{�p��dM��2�����F�*�-f��R)����`f������6?����u���n}���f��\�x����6�buI�(�>fm���qs�?���d�)��T���~
?r������/��W����L|���#�ox�	B�s��!K�:���z���-���/[��D4�Z��,�,�����P<�o��`J_��u&����N�n3�����q���}��Er��3l
�/�%��>��>&�gXY��mV��~����z���J���*#�|VB�=�������:}��SjI�Q��������@�D��x0e���v�@�#������
6;��������:�Z��QP|���x�x��X���HJ�y������RTJ���Z�����]CC����A�x]A�xmD�����Dd�O��
d��8@	TzG\OI'JZ��)����J
��J�����YY��R�o�
G�u�~+���t�F|NVcR����a]�	���Q�����j/@�NT��Yq�p�l����02��,`����'a�7� X����-�Y�����]]-�7�~J�J�$?��d��D�\#�[vP�8�zS��"�M�Uoj[�t�����x���+�x�%������o����?%5T����3"z��l�r�x��l���<�I�����S�(,N��kg����M ;�N�9��	P�!�g?����B�HH�'{G��0$Z��^hV��99v��	N�R�8��C���������a~l���^��F9���7,4�DZ����'�;������K��3;i�,7a+�vcqp��kL� �������V+"���f�P��Y`�2��N��Rt �4��9����H"��K�
��R;Pjg��L�������W;�d�vX�$tl�U�*��K�H���'4�9|'� �6���G!����9v�#^#��h����we�>���baW����R}���������Nk-�yyr/��e+���>���uE�XL�]�
U�Eblax����8kC�cW�9I�K���:&'2"/mz��)�3�[�J�i���F�!E3^$���4 �JT���� ��S���^!�U������\+��)d�Sf����\ig����P&1�\MI��N�mI�E�DF������4�,���.V���<�.R�Hb{�@���w����S������2���F&�k)�x�yM2@X�w���m�����k�Z���4+���V����
����Y����KO	X��~�
�n�vg��O��`����0�������S�o���7��NV&'�w��cv!��T���F����"�?�u�����d�����	�Bc����D[���`�������vV_��B�_JK��R?��X��1L�@����/��X_?���:'�X_?~��U6����J��{��2���B�d�.C:bE"N��	m
�1LX�N������0h�����]��?���+��n~�n�P�����������i"HAAz"=��o�o�R/^������
�
��H���c����^&.$�u�I�duwL=��D��)WS�0%�:���Y&�Vj�S
;j����I�>k���n�?�NmO���0��8qS����C�IIK�La����3�����F��-��j��S���2�%^��%^��%^��(��O���~z:���4W�{c��,v�b�,v�b�,���b=���������wO���2�%�]r�%�]r�%�]r�(�u�C�{Z\�0|{:\v�A-����.����.����J.�z��0��i^��Q�"��d�K�d�K�d�Ke�~#;��=���"��d�K�d�K�d�Ke�#;��=���B���K.���K.���K.��#;��=���B���K.���K.���K.+�,b�J6~*|w����.����.����.�l��>���t�������.����.����.�l��>���t��7�����.���.���.m��>���t��������.���.���.�d��`�v6������B���K.���K.���K.��~;;��=���B���K.���K.���K.��;;��=���bF�d�KF�d�KF�d�����`������[c�������[�a������=������������c����3�G�N�@�>(�}P�{#����4r�X.��B����T�����zIBxO��N�h�����{����^(������-[�����{g<�^B�`�����}X��-:W�����;���Z0��w�V"����;��s���L�K���g������/@e?�r��G���-����ti0��Vru.v�������Fr��Dpe����X?������S�V��F��4��D�Z���M3���
��wX��^
8�y�k�������v?��6���D����u1���!f��8>h6`I�E��:~�A�0e	�s����-g�/�j�o/jo�a������Lw���xT���G������u?Pj���I��m��.^	l
W�q|z���z�����y
��n�M���o�&G_�Y�]���`�vWP��Y���
�x_��hBg��}imXo�#���K���i���<9��
����]j�� ���<�cw�����[�e����j?������v~�Qd�C������QS���&�]�#�h�m3����e��C!�eU~��V-��Jye%�Lr�"�.���_DXhFV��>d���vc��N������a�q���$n�����?��P�]�A�����GR>qmy>�%�_*/�N��C��":��Yh"(�GL��a����E� �~�6��|=>>��!T�����5�!�� � ]��7v���h���'�Z�K���'���r���C8����a�O�K��_��~K�~|x���!+�7x����!e>���3�Tg���9�7��s��,|*|L��k�q~��@�����h*��pp8��7=��
(<~��]�c����P�lx��D�\��;h:�L��>�����K����)�A�#=�m���H���O��������)
���P;��ml{7�����%O"Kj������8o��q�c���,h?�m?iQ����{?��������A�l�Y��4����QMV�M�!���~^���ri�%���`��&�0�/
Vz���_u���1P��/�"?���+5���uG������w�[�e��9����C9tV��K'�Y����W!1��H��yv�G�]�e���P�q��G���J�7���IQ�������k����rq��?x��w�
dF5>�4�_�u��k�s��
�vc�3sx�i4Wg{�u6XLo���f�/qprq��=���Y�
�;�
�������lN��E �Xt��=�����bY�M����|t0f����$5����Y��;��_�p���Q�}�������z3!3�9�),�������!�zh��r�!lC0�E����.�k�O�4���8��6y�1{<���Y��������#���w�N�p��Z���nuA
����\#�+<y������6K��;��;�y��&�X����� �qie^*���F����G���Y7-�\��!������P)EVy����)>}!�_Z�����3�nX��-F��� �l���~-���e���������|�P���w[VN!K`	�0]X����W���$_����I��1j���x�6�9��B������9'A";����i(�������	D���R.NF�S`
0�Q��Qp#�T��T�V��~6�d����4���.���Zx~�����nK�3y����E��l@O�'jr���{A?�'�P[��0��[8��9�9j��5j��q����Z�X�%����8Z������;c�(7����_��:gc�V�/����cy*`V�?��Q�8������h�;9k�_�.���1����V����.���XD�
����#Ck`�-�d%y-����ra�����u5�u�F;���������C��e��1�o���ne�X���6�
>��W~]y�~��C{f��'G��_�|Z#��tn�.^�1T����G(/O��z��[����+������Igp��~����0_�p��	�P*����������Q�C	��0�_�����q��1H.���6^@A�,0
�$����
�#I/���+�.���H*��k��\���������<��m�%����\�dB�Uj)�Z����Y=s1�=��y}!��X�.V�}��?~�(ge]|Ix/�B6� ���M��`�M_���2-<����d1������a�e��L�e�.F�t�h)<��vr,��j�X�-�WW�������\k�G���.o?/����Z���u�T	"	���V���a�#���o~p����-4����H���Q�;[[�Y��.���oz��K�
%J���[���*;;�����C`���_����r�����x�@X��n{�j�-{�T��m�mo��n���-��{[�]�jowJ�=0�s{$�,_*����
��*6�B�
0��%R����7��\�/v������C8��o<������8T�K/*�/�[b��_*����x��B����<x'����������� �Z
DK���
�ls�=�=��r������K���D�����Qm��b��U<��:���L:)�u3���vIl��^�q�����o�o����UQ=*K�Q���;U�B���|A�E����;��_]�:�����t���|���^�:���Et�j�������E���z�h���{X�L��o����������rU�������M8nV�w����G}�c���������@X �$��e����f��*���
��y�����{���<Y8zZ�c�$���\X������L_�'���4��x�N�s�~��g�	�������XM4nz��y?<��Y����$�wl�6b�clz1�I�Q9�
5o����z�g8�S)Su�n|��
�riQ�Z.��{��
�a��pu�<�
���_[>S�a��{�ea�����_�O������_[hv@��"�C�F�=���k���US�0��0�/���a��l@�a \"��3���Bu�}Q����F���7�C7HD��;H�"0|+k������7�#�YB��:w�,@�!4G�P�v�gA�z�i������F0����OApY���������i���9���8>=�yz�pv���k��1P���-�����D�c�.N ��yH{�
�@�����]��U*+{�N�].[�	zz�)D{z%���raWl�_�y������������������]������zu3����N:>i*���Ddw=o�^'J����������?`��O��&���G��������V�f}����Wk�����>��n�1���s�����.��~��i-\[�qlF��S<'D��-$��������U%����T��rJOx���\pE�
		u�N'�t�J7z��!lt�x��%����8pa9���r�.�}�T��Uk�z'i6�v��W��x���>_�CC���T{�
��E`����Pe"��E�����A�����!SyN��|�E0�R%c������K\|�!������:�k���g4�g4���
��A
s���b�`m'�k�a��������|Q��G�,t��B>�f�i:��p�v�\ly^�W)�+x�N��#f�o\,�/M�k�dk0m��y��#NB��f�I�"!T�R4%4��1�R�?���-�;o�S
�Db\v�~O�Y��BHO� �F�����MJ�i�Fz��5`0�6�q���� �Z���}���s/�B�����gT���l��1�N�~vvr�B��)������ndr������>]�z3x4��e:<�n��� e�f��l����6�]bQ�3�����L�j=NRF�'��^g.�B=2V7n������	�����Rm-�/yb�����P�y��� �b'���r���.ek3;�,	g������9������+���P��X�GY��Y]��4����Vb�G���x4Z0��iu#��i���7S�,"k{'D4�f�Ut�8EIv�y�wp���9�)<!�X"��&��0��+ENA9��s����~W&����
o
���,k�X�<|
s:<%M'�$��`����`��1���k�� ��ed�wY���#d1����iS�a���e�j~��~��u~���
��/�����*1\����5����d���p2�;L�����!��FK�5����d����/b����%��p���n�OhE�xU!���f2�����^x�����}���M��0�0F
L{����c���C����f(VgB�����KC�B��SN����_��J���>�����q|x�sA���������Ye���\7�5]�U���|V�������RMd\����Ou,����i\����%sI�<����:�`K�zt�������@�k���F������|��@T����a�l{�z]gh�J���gxVsV?�$u��J*gr/�o!�g�H�����_��8`���*������v:c�*v����)�����0*�^?���]��13�FR�5���Qw	����(�U��O���n:��K�=B����
�Y�������>pu<�����6%4 }���Q�����@k������=�JZc%�1L���9F"L�B��u�~#������N����.����U��U:���������d�Z��#{W��.������3��C�f8���bl�����no��mo]���D����v'(����U8�Wk4�M��5�4��d���h`q � �����kw��~�z���*�:^��\�'��������o�@�r��=���U����$l�X��(�H���h�����x��f������<[?c�-`���4pH�1b<��M���,!WN��/q�����(��e��R����P��Y��������x�����~�Co��$��W��sg:���@�)��}��{=����`����-�_!@�A��J�,'DX����#������Q��E�y��;�������Hp�%PN�
��������R��W���W�36l�"�/�X�ou���
���vd]2���T�����dV/ ��vm��:A��ux�g��pR`��!(�;�����8����--�<-���Ov�B�����F�nW��9hk��t��A�����+������������D�\���b{�VZ�FX��	2�N_'�	37ld	
{A�X{�������ern-������u)C�B�h��+H�8;����
t�
�����! �`FWGl=��4}�
L+�J]���^f^^��@M�H:������b�*7���Ay`~�R����B�i���F�2w�����in(���1���NE~�U�VV97��1��/��A)t��FZ��7j���	_��_���*��qk�#��KF��������;������+�1���R��@������k[\9��=�G\���g�\����>OGvr�p8�����hh��H!�a�r��@���h�L�C�%O��G:)G����iQ��;WzzJQu����N(���\�t���%�s�c�:���tCs)G�`� #�&��:�/�%\I��}����v��53���n|@b`����d����������d3�p<`��)���uM6#7���7��%�����"���h�*{�t�O�a`!yV~C\f`����RX�����������p�`1|�x�O#�(<Y����3`���'��^��n)����m�c��>Km�
�q`���=�\�`3���g;��
�	����B=�qO��c:����)�}{�eR�)����KcT��}����Eg��.->�dUI��%U�&�M�pXC���x`rv���A#�"�]$��7
��*;J�F`��.0�&�#J�������l>�f F��*���V�����v��R����n�s3�'n�p.��,Im"8J!�yt�3��]�K��(��DXax�U�kI�t����`+Y�K;{��������!J�D�>�k�<^����<oI�QsC�L�q>���	��Ol�Ik��AN���<���V��UT��Q�I*P�8�(����r�x!���4�P���>Vl���S�lm�
R�)���
b. �+��)�-�km1i�M&@�3��1K�t���pg�D��Pb�#9�;Q74����3Ht����N�(?&M��8~��;����Q�F]�d	TL�A�TC��/&��9�]4&��'!=�R��^���Xh�"m.Jvd�p�"����"���
8�-��guqcJ#{�x8o*�(@�(���/���f�vQ�Yv��\v�\�4�.H��X���sn�
�f���$��Q���y>�=��s��);�	-�s�'$�G�s�-�0���I[��m�����}����-�C���&�f3�Q��&w��7��6��(&0�Y�)�#��Q��x l&�H��!}p0�b���zo �����~;��8���X!U�����k�6�OL|5�6/�=BF��q@���M~p �I(E��14�'c����}<�K2��`�c�
?H���+_�P�[5����I����f�$���5IO?<���;<0��!���eF��:�
�	�o�/��bB�!�}{���Md�S���u3�S"H$L�����[��XPU��PF4���w"�X8�t�}�Y�ST�X���<T_e��=���OdM5Q�<U�c7�����Z}Y��������w6�i����L�_�]���L��x�3Rw%J^����a����h������_����K��\V�=���W=L'~�>�f����B)��#N�Y�<T0��0�d����D7�%ob�'�!$�@�?��]+��������]w��<�����������hN�x|�V�F�4��������=0�1\�6^��:=����r���}1	��c[a\��K�p��K�����t�u�:R�g?hh�/�$����:
�,~`[�;=Z��i�<�!5����6�\hQ@�e'���a>M�T�'��	���
�J����m�*���W:��L�1n
7��M��6\���?A�e+�Lyv^��,Ff
���e&C�0��$�V��[���N�r
Q�6]��I�D��� i�J[�� �F�������F7����|������#N�awd�B����T;>k$(�.vr��~��b����a��7�]q��,'y���yY�0�����Js�|\��C���V|��i���8�����[zvZ��%U�Q>��d�9
�U+���MO�=tN�����d�NUo�z!��#�,�M�c���4����;���z��+����Y���DZ�L�q7��@�,3��|� ��1��lV��tF��dx>�e��o�^NOcL�B�<8J���q����������O#k�p�gf^|�1� �k
���CHIo����+�
�\|�cVx�������S�D���=;�a��ih&:�B���
d;�<������/������	��&��6E����)�3�'��������$��B�����/��jB/�r�5�}7����������o��uB���"R�;�	z���L�"a��,^��D6�=��5*��Z�������m��CHlM��@w��G�.Y������1q1
]��XJsJ5J��I6�Tv��+>��X�(�,n�������0���g��I����8a��>ux~� ��9��m��s-tS������ �n����g��l��Y��[��j�d�f�u��o��JS�8Pm7���:���y"��8Y�)*M�M����w���[3��t�<	�q�
zx8����q��	�1���8��&��ph������;���|�&<��!R��'z���+��3.`���	i���m��)���
mA 0F!��bl�]�������{����;{����6XrrxG��!�KB���p�
��3S��kM�L�JZG�����
ek�K�?��k���|�o�pe����YM�)�'��yn����Y�v(N��a�Y74�X�Vn�@��Y��^eR���
�
���@"e����J����i��^���8�8�L}������������0���;~�3���8	X��v��V�.��K��M6B�`D��3R���8Cy�:`�I�t���f��$����w����\�D����(������N�0��3���n>��z�h-�;��J,t�g��x*��L���/dej0f��fT��l�X p��o�*���v��k*o�v)����P�"W�f��C&�5����}���g��#��@�n
�}�A!W;|F�7�(�m<k�"M�vo���e���]��
JG�acU$�;0�a.�J����W~�mh:��W|4_E$����k#��2a���/%�g��NH�io����@�	�?������R�\�������,�>�?��Y��z{;V�k������v�vu�����;��Jeg�[��������?K:����&*��UD��U�l%N����-aG�@��/r�P���v����� �v��!M���\������6>��'T��[a��Mj���Y)��yn�t�~2�g���YodL�^i��/o����������m��Fbe��1���RL����4�OJ��
��&J'�y�������=���+����XY��++����OQ�:�������S���Z��S��
��\Y���X�������U�{��������<.�
���������<��r���B�Q�n�y����N�2��T����N=eind�[���z�[�1s{d��J�D�����0^�x1�l�����2�,���~|��Z0A�KCd�A��<�9\C���9+?�[���_��i�D���H�a����A/<r%���
��N���v���5���
Y��&�����������+�K�]�?�����;�N��2(�y�E)�X`Di�b�`m��z�2��
����oE�	h��W�\E����k�R@M��3H�u(�Z�����u�?��CVN��m��Fh�x������Za��;������;���e���(����^A�rk5
� 8#��=k0��hp�����
�����W����������Y���!���`���s,�K�F���G{��=����������&�AG�di��C�h�Ha����q���
 ��i���� 5���>��xa��%�����JS�w��n�c������U�V�{�	&�J
�����y�*��O����tc�������p��Z|����O�p������z��1_e�{������A�qr+�Z��@th�
A����5��H��ki%=�;���R����>���I+�|�8~�:>i���������Q���LoU���JG���������A�[�F���uq�����)@5�)#-�94C�dwJ%J&�S���%���T���'������=}p����z������?]��p6kj���D`�H���C�8>:y��~���sV���v4�$����rM j��X��-`��|~u31*=�$����<<��T�y����
�q�4�S<�%��N��q��o��u�Tu+8��y&�/�"�0~{W;�`\��{�%L�_����b��V� +
�s3�g�r�Up�s���N�^��4�.������nF�4sb?�-"�2H���S����6�����cf�w���PL�D���l��2;?�z����)<�3x�	�c.��leq���y��?@��y�xF`Y*
�_R�W*�����o@���:>�6#��6��}�c�p������k���ye�.������{��r��kow��vg����;;;�-{kg����n�W�[{���+m�(U2�����Y���L �Tr5U�_]��a�L���eZ������2�,�]%�J����K���\���W����R��3�V�8V|i�,��'�z��������x#���rDk�HnJ�$�!��<'�1
oM��W���6��dO�>�{�����	g%��,��*9S'��~'���Jy���u*;�bu�������vMm�8���G	u���GP�0O��xk�������
o�a��@j��,N�
q��=��,1\�s����t{?x��+'��iF�8���=��^��-eL�����t>��-?R�E]�m
���F����^��x�m����a+����� ��uCP�E6���T���_Z(/��+#��^�[��A�����;;t&��)��W����@B�)���k�����xwrt.�;�U����_�U�
�9j���!���
��2?�����A���������c�sptqX��a������~���4�'>��V���W�v�8���62�	���m�D��	���,{z�8��C��0��y�Y�\�YI���������y>�� ����I=�8?9��R�����B��\�+TI����(���g��������?!`�:�3���x���i�=���Tb#����>��=����
�?R��a��O��u���ep�E�<�����kv�{%���>'�Fx��R����E���;/������J����FK9~����d3Zm����n�r���a����_��{}��
8����(�����4@�c���4*/�2�'gt���+�M�������;���e���#�Y��mU���o�g�IW^����?p@����J4�p��w�}L��;�wo��)S��y/���t|�(|��XwD���y/���r���3��~��J��9>^Jn��[�GfR@y�A�^��6_�(q�w�(/I"�F��Or]���
R+�}���G�W��CZOU��/U8�W� yK������'�Uc�h������^�\��_�h���������������E�m.����}���+F����)pg���o�"��H��Q���+������B���n��6z6���v�%s���(a���IC�oM�wS��%@�1����/o�����N{�{y���V�v��n�9���_����9#�����]�@	r>�(V������N`�s�����n����c�d2,�Q+
BE�#�H������b*O�L�������k�~� k�o
i�q=x��RS�c����Q�B����Io���^�R���r�X����	�S.W�1�Jr	�%h�������o[������an���@P�}�����z�_����8����8���3���`W�V������^o#���7k��f�V�j�������������(�!��^��B;������W��l�!�L=��&��ojk��������J%�����������G��}����w*{�����n��������{e��[����]��J�^���/*�,�oE�Z*�14�T�a*J���*Ex�M������f
��o����Yz`.P� ���4
��$��r|��lk$mJ��6T����3
��N��������,P+[��_�x5�	��j�C����W�M����<��������q]��$Z�K��<���n���-OP%�����R�;�J�����+����K��"d4��&�
�t�_�B�?H:h�lN/�Mt���Id��Bc:�������
���~I������bS�����vU���`�9��R�8Y���Y��<������U
������8����{�#����,Mg���D��~���������FIA�/��3(���]mgu��e�K[mN�<����B�UYh��vg�R����]8[����rw:��V��O� ����~��;i-�����hG��iZ�{��,�rbl�K�����S���j�55���m��pws�!+J��N��NV �G<c/f���4�������E������`�vw��ri�+w+�j��m���!��$Vd���Ki��.�Y%;�K;@H�����C������ ����
a�>�++gT��.�rP�%�n�	3%Z�������O2*���n�{0���v��7���#����������?@�sg����a!�xR[ye���S���d�Lz��^H=p���E����z��yvq����+�V� �&;�L��Tp��|[�_7$�B��V���;�Ph-���
���lXnH���8Rg�\���X��nm�*{��n���d7
��I5~�!��G���5bf�P���%F��;jd
���$U��u��l��pG�����*/����Z��1�a�`t=��nd�� j����)���������'d��2��-��d�iRd���=v���j�b!	���vQ ����5)
%�5)�X��*��G�8�5kG'os�X�\��/��(`�(�?�������s��~��������b���r%F�H Y�%41���P�16l����vq����V`#��JUK������M#C��
rnR�)�;�P��G�w�����z��3�`H}-l�������K4�L�H����K������N�#��;;{��Re�6�N!�e�e����=���C�+�N���I����SJ"�h�H+G���`x��Zs:3�U�,e�C���h�����R(��-� ��������_�����F��/zMA >6�-'�Q:��6H�m�T�)�l��WR���������J�?�R���A8ao�U2������!��"�9���"~�by������;"i�6��V$�1�!����Ze,���D���3��E��_^E����Q����v��U����������O����	�X�K��������N9n��]�,�������ngk��������]`�V�W�lm������ve��������\~Q��������MrZ�
�K�B���:V*��`M�/�������]U�a���Fc�"&A�2�S�L�E�����"� �]Q��h44��Rp:�AAK�fV�@O�s1'J�(ja�<,���j�E��X�<�����[]%���_�~�c����@�f�#���P-2'����2nv`�|
X�+��������]��o(p���]9>�xU@A�hS��B�U�Fg8���8W�a����pb�Xf>Z���BwD9��s��}"1���QI�jx���"��l���h��jp$8�b� Q1>b�+c ��
4�VL&�r|���#�9�����1����i�����5+�7��`V2n\�hA��4�ga�u��M��~_&�5�M
$�r#�����u��R���jD*�/S� ��S�09���
]�E1-<J2P���m��^�B:R������n����A��1r�����ISV�ME��
�1������Z�a�<�7��MJIq�F���0�Hxu�3*#Gq6I9�k��[2,Z���~���#�U��j�3��`��U�z��$��s'����y��D9ym��
0c]ER��X�|������j����x�����c�
�9����`�p���4]���AWV��J�=(������^����F�nQ�C��*�3{���*��-�8&u��
R___GR��u$��|����Ju�6=�4,Fw��Hk�himq�J������I��mYo6��@���x0R��m./teb��Xqc�F���JK��U��v���Pc�<��1-����X}�N���+1�mi��Wu�Z�o1���Q�f�J����e�%o�l����^�X��Y��Vu����vNo+�uN/G�Q�������$������p<(^���E�h]���j�,�f���)/���
�GVI/��9���=�G����C�����P�6$��q��}Q-�W-E��M�nfkA���[J���H{]��<�@`��>��'��U���gwt\.G �)*B����Z�QN&J�Aox��ve�f����9��g��B�~�g���2�#�����W8��N�qD��	��b�qN���5�d�2���_�M#F�D�������K���&+?�0u��8 0���o��@*�_��K*�P[3�����-��J({�������tK��~�������������R��SJ�rmSt"�[������[Ky���~��������p;�&���������M�6�]����#|���)#K���2��yd�Qf����K������[\�Au{+D�����4�y�{#�z�e��4%etX��yW��v)X�~�u��a�4��������\�9�@&���>P�g>�-�����F��Sn@��|U��W���~�f����U�MF��
�;�4�MFE��?Q:{$@�Z/�8�w���l����N�F���g��\�� v�H����!�n3��4j��g*����9��~+Dd&H�e�����C`�W.|p@^����!;�p����Q!�A�����"��a��������d�C��4��e��������&��s�x�{*w���9��8CA��`��/�!\eDF��Q�8VpQ��Cr/����o�;��^P#p�p�n��������A��[e�%+�����Li�����P��j������t��t3�&�&������U�!N/'�-��*�SNP��L��Vl�FEN�~vvrV��D9��:��ON��7�Z����W����S�4��Uq��>�/sk��Y�6������z	W��HWP4�&�S)�i��-z-O�3Xs"�Na�E*(<yH#|y�z�������z�������E"�T~*�;bE��j��Du���=�rf<�
�()�Q3+r��T
i����������Q�qLJn���~Z;����9�jEo7���a��;�/�$J�FB�#����;�w����+L>��I�2�xS�
J�
����lv[������"�&�x�Z@��y���������H��s;��B�nm�=���e�6/�.��[�*c�����2#26��O(����b����
��6��e��N��vG�0��!0�D^�!�L2�aM"$�kzL��dX�9v�K��`�7�=��,��{Q6�~�&��GZ�\8�UP����M|�l�(���E�d�#�h9}�#m�5��Q-$�����h{��@��s�����@��]?:r�a�Ki�&W-f��B?�P���T���!���U�48dRh/���,}h�������.�Mrg��Q�x|gJD�+���t�Drz�Cd"��z|�\<��L���X��|=;�Z����Y���2h51�c��dSj����.&�>�V��
�[���#)|��"�0R)'#T�����7����@���P%���3�:����\>uI����:~���PBZ5G�0�I9��Tx����s��
������A��%������P��W��?�H�S��"k�H&�*�7��V�P����	�r�:,'2T�Mqn�G������*�
���e��H2�]p��"z!"+6	����?�K�G�d�+��]^��i���kC�p3�+��'�+��*(���������1�L���3���d�n�����2��`>@V�H��8 Br�#L�94�U��
��S���:�������.��Z��"�\M����!�,#	�G��Z=;�vNA����#�(|���$�����z���Q]�J��<����
����4&r�H
�2�`�L.98M�!_�)ns��9�=4�yX���TP���=@b��B��-?��b�y�N����)F�Q3
k�/~���~����r7�����q������)�E��g���Th2�P�B�I=���L9���_�	�� 0��<o��/A=����Ez|���\_��)Zv�@��g���E�3L�(b�r�|�d2�����<��G��~`���.M����}�B�@��
oR���w/��^�a���{9�6�� ��������i]Jp�I�t�x�
���RC!aPy�����)�M���&��G����e�sr�`���BUl��T
��-���@\�(�#��Fq3N���<���j�$��f��-��5>+�����e��>Ll�`D�/^Q�#���|Y�$����!�x�V� ����|���"��I�h����@Q=����(���PP�'��|��%)���K��$a�>QaIv��y:}E����P�����K$jc�e�������a���&D�v����H��E�s�,�_�����-g���6�5�"����h�+m�����#c����ua�f�_�<2f���x���������P���aM�Yj���BM-cQ��y�[3�5���$ej��I���J��3������{h&*���:����8	��������RG�����rD6Gn��}��`���I�.^a���~N�mx�G�O�p\�:U����\���_S�������f0��{�A�<�k�L*�v|6�q�)�%�Q��EP���f����+��_�F��h�.��<wA2:u�>0wH��o�M*r�9�'�q���+yd>��-�]��cPJ;�d���������$�}�;��1LL=r�����ss2�}/�H�3��qX?���~mQ�${���e�M���Y�N�K���r? +wO�����Jv��sH���'8���qz7$�*l�f�3N��7��q�@�V����Y��

-:��0u�^����g��i�#��f�0Z=����Yq2d,�M�N`��
m��D������KA�������e]���	���1V�>��"6�(��1�P�����ye{6;r�\t�s�x;�,���p=02��}D|���������G����'�Z���E� |G���������|~���y�y����|u=q2���ho����������~F����4>S"�P�:�Esn�!���$�sChv^g'GG�@�����D<�~(������ y�����qr�\�m^���~�d�������M��v����6W{0Bb�A�q��~�����E��!�����]���`j�.�P�o�����P�e
7Zk��^#�[)a��A�bO�Z��	�^D�}��.h�M����B���5L�ul��_K�Uwlhq��w�8O��������0r�"�%b�
�[&��1���2��2���*'c��3 �^IF�_�����>�����;�;qF�;��)B��
�got��G7{���z�Yl��Uw���@ZI@=\YU3�{^G��MZ����������lKD��N� :�������J���!���rOo�EY������c��;5�B�p�o�Nw^�R���[><*{P�N-zF���E����Y\1�R7����x�oT��T���g5��Q��W�z��6�p�$0b��q��V,-D�{3������������K*�*���YA0t��( FF�@��>X��)��I�X��~�RB��D-�
�Mch��`�.#/G�O�c��1����F�f�Y
���������(���3�gH�#��KQU�����=	��Gb���x����O�g�R�0�����kav>2�z�;n���4.J���~���i�C�N
�qZ���A��'ip��}n)���bm���d�(C��lp�����&�m��?����G�����#t�1�./=�g>y++�$2���#�)����������Q7,����1�bz@z��0u]��Wx�&��g��a�I���R��~�F��Z�2V�����Vo����������j65�3�,F��G�������fyi���io�j"�#uFG���^�+������r/���.	�[��m�4��Y��Hkr��C{^���x&~�E��)�����2Z�M��hZ��Ie�%�H�x"n^���s�����@r����1����\m@���}S��2��8>�2m�[a)�U���������7���V��eF�I^��%�y��c):�a[9���mA��l�r�+|���!jQC��f��"���K[���9�
�..����?L��H�0�!c~t��5�+t���j���(j7i�6�J����RRh��'2�-�rA|��O���s9�Z��A$P��a���2��x�*��x�P��o����2F�F4p���f�z�D�:y�;A$��Fo#����<4��.�z8 Q����~ #Dqc|�!FX8�c�A�2��. �Uo�d���;�~�������m+-$���&����J��m6��s���HcSa���$����%����w���*����i��v8���"���wFx��d���f��@�xIQ~��*���FJ
�@�
�=)���P2����IL����L)=��]p$�X8����eq?+����Y�UZ��y�tt\�Kn#~��g�%�X3XlB;]2#�7����z������S���M��M~�{[��R2�2���O���$��`��SE��xSF��?���v0���iFT~����[���v��������a�E��;��"���n!���=�Cj8�!��p���;O����*H:�/�0��$�-�+�+�4�@��,����������G��@�8�^��qr|a}�y���#������B+_p�h]> ��P�F�v�1
}`��K�k�A;+��|4[#jLd{�ri������lB���i`����P���1��\�1Du���7��_c���xE:�/Vc5�HC���8�T�q�NS���"V�a�D��4��=)l�N���� �@TP��H�X82(82R@9���mZ8x��a��I����W�
0�������1�bl�+U_a=|�>	���r����p�����F�[���\t������I��w����&b�=�g]������$B��zR���3��7k-�z\{_^
P��ka����x1\UL�>B���aL���2vs�8I&+ mX�_'r&�2�)}l"����sN�$��mg����u��P���*Rort(YmS���A��
|Cy�1�v�26Y��2�8��0��{6��z��\^;�;�x�\����!����U?�d�����(�B�fBD��[�������0�CS�}�p�tl��3c�>���Z�?<�#� ��3h���&a��2SX��ui���Q��(��QAP��{:��j4p���>r�����Sy���8�R�	����1V�#}�TM�"�^�hD�#-p8�0nH�$b�X6Wj�j�<�c�8�FZ\�[�%`�"����������X8-<��XA<~�1�����_z����-����C��=��s�h,�>�,�������p8���V\�2!N�%�f���=<�b81��f��a���(�}
hp�YgG&��%1DK.n}9�����
�2 ��89F8&�6��q�C��!s�����i\'���s7(aP�	�r���w���g�x��S��Y?��h���K���P��e�I�)%brb��N����1��$��!\:n_)s����U�,�)2+4_PQb�8���Y<�d��S�ha��"z+���������S���S�"u1���E�G���#�=7��|~�e9��� �Yx�-��g�j���CG���Dc��1a6���L�n��x&�z�Xc���l=UyP��w��E%N����0uZ�\�3�B��=�1;pG'|�HJ�h����@�l�(I+���:��e���,�4@��#���� �-R#�R������k��D�,_]�6$���!}5�3�;�	)�f�Z*l���U:���e�$H�(��@���n����g��d)b��r��|�|����
�Zr|���@���[��
	
F�z0N7�Hd��HZvSL	�7}��/Q�VF����$���/���I8�L���'_�_p��B"�qQ�o�������Q^&f��K����g%��1C��~��tJ���b`��u�Q%���@����'��nJ�7/�	�h���q�~����>': ��'�=pG7:�o(�'�~U����s�������$s�����D&�9���x$z���QWt|���k��L���07!{0wI��P�+�"��H�#��Ty�5R
Tkp[\�������H@F�N�Z�W�^�'��{���<;tv�0q����Xr+�;9D�i���ah�3>�=��gdhV�g�7g��w)6�r�:0Ny�zoT��`G=�����4B�f) 
8�����#���!<��\�\��^��(Yxc��a f>4�:�2=D��O2���*�Y>�t�v��6e���`�������6^F����4:3�0��A����v
G���A
 G3��!�HE�#���8�1[���$vv	��b�`��p\
tU������xH�O>9�EAEz�����7�LCQ�H�R����?'���)����$��&��3s����Fu�Z(o-�	C��_���_ZD���P� r|pqvV?n�Javn4��� k��������h]��9�@����5uy)�&��4�`q��|����D����,��+�S��U��c��N�����"�p��% ]��@��M��^���R�H���	+��G)
_�P�<���/�e6_�q��?�r�,�4�b-)��s�;��kt����^��5�����Q^�3��()��6��(_�?J�%V���7��6V4����TJ�u���=D �N,�F*�sx3��+�m���������VZ�b�����b�?E�I�2}�����r-����[���
�1�����������!�M�5
q	w����7������8yC��G&�664N�F7���Rf�����M��,���	���Af�0JE�P�v]4p��]��Cu=>�U�ve��{��u�
'�X:��i��j�P�T���CJ�)Ve�V�P�a����r��T������irKH(��C=��9�v���7T��dz!6w`���-�b�cA(��K�*qxM�F��oa8��f�P��a����`�(�x�uQf������va����/�0C|
�,�(�����w���C+��mcS����8jnhqI[HS�4��v���Y|��w��Z�ds�W8W�cR�" �a��NO"R+���D���/-�#�����h�-K��)�aN�F�(%�<��t����"{�&]��E�SO��o@��#�;	�8F��1����\�s�l���U�WT���T���Q/��@�N�5T�����.�%���:*��`�_��msA�i�!���*�8��2�v��%D�������Kg�s<1������,�P�Da��K=���6�0��V���f' )_���G��uw��S�+q���KY}ax�`y�h�s�:��j�T<�x�0���7"�����#�bP)#�hX�.QH�
�����.��m������wADvO"��6*�p=	�2��{�%�Pg���	kvr�:�$��8���99{��'�-
�r\���M����T]�
*W�{=9�?"���c�������@�
m��=���J��v_E�C�����r��s�(�"(���M��!�N������"�X��H��2�L�-VY�G��r4�%C�l
}�O�I4�S��n��
����H����&�7fsb���d��n1�	0����/��k���������,���
Y��F��d�� H]��u�4-pR����/�1u���e�����X"������&-a
mS����5B:�Q���������0$b��zc�EYa�)VX�M�&G�%���I���&D�N�:tN:Q���(��`4Pr�k�I�x�v:�n*�rd�*=�Cj$����������&r<���B���k�������$8�O'TC��s�p�Y�<���S6XAN�{q��J��B���Q�V�(�N����wY$�$oP\���k[�)���.�R�����0qd#��'x?�3����/U�\��PYy#�x�8�*�LTN����7��Jc���e$�%2����-T�&�Y77)�o��W�!�E�O���&>#l�uavE�}Q����T�h�V� k6{<u	���P������WJR�n��P5=��Q�n��P��!����)���j��I�)T�e�Bu��������2u>��C�9�L=��4=�"f�C�9x�;�d��O��v�54E�����s�c��V�V6(��@�p��=�6}H���tZ��o��e��^������L��"^>Etx��yJ�o�^�������tL(}
}��6������:��eW<�t2�is���m��8>�Cv����dR��c�3�3�2a�8�E^�:9����
vy��
��%}%T;��/����FR�!7��7V������
�6xs�u�C�^�����;�X�LQa��������2��0E���;[\����M�
x�������F�~X����S=g���uy�!`�5V!p#f�E8=@���g9����8���k����ve��6����J^N��?+�%��Q����_^y���t�O���P�xK���4|����o����CQ��,2BEa
"��I-��f����K���7a*�����1/ �h�2��Pn��Vq�E$�_��2��h��34��	�#�������d�>:9�;�o�}�5�p��i�y�H��6�O�6v)�bc��;��Fsc����n���fl��N�>#+`��� ��'C�C��i��Ao�-AeR,_cNx�PI;S	#�Gj�J���G��|�b
�q1Q�Mg������2�5����� �'Z^zG�+��zk��	y(HG$?�7�v����W_^��c�������:3%�WE��2S�.w����B��P�(G|\ry�h���	������F:���/���E�$�1^
��s��"e�/�4�2���C*���R�Y<CH��oP)a�z���LoZ#�TC8_����6��H�t4�������Ib����A6�w��X���'�7�>��2|d�{V��4���j�Ha�p�Wm���q)nh��2�G��_�d���,P]��*<0#}���J���F����T�0�����nJic���7�#e�;��
s{�j���0Yyo\���G���D2�%O�y),��R�������"����l�=5[y�z����-0snriCn����^�k��rX��<��� ���3���d���c�o`M�@*1�-5�������YW1�0Ty|���	��eG
�e�(��0����f��K�rH��'�J)��8V�3P{HHj��A���� �*p�@���E��9e�j���;2$��h�1m#��)�|@"�/!��%�%4OS�b>��L#�Cg$
���U����IJ�V*]`�yAD����a���yD_�T�]v�@�5P>��e1~�B��h���<�w^�	���'�(�����g_r�*1�������t�~�"}@�����5���"
C=��A��<�A�8����og�������Q(�Eh���u�\������8�,�q�)�q�Y����9�J�j���`�xha�Q�T����wmws��zw�������
�����!/Dv���	�F������W"��i�����d��IO9�r�1���DQ�������Z��z;0�x7/�P���7&��B�{������gu���]��z�Lu���d���%-�e������7��e�c�l�-��B�,+��%XDLZZ�n��59����-omS�a�T��u�l���b�������/��V�����Rv�;��*n����$]^'�&�1<�������~�����_���S���c7h�r9b�&�N�a�d�X����k�mA��u_������?���D���*E!j}�cy"���o/�N����6&w,1	�P���d�(�(�+���lS�V�e����T��wy�&�6�Z�/������s�#9l�{�@���xJ���z:*�2p�xL�wq^?�8k4�6���NN�g�����*	P��?<���>��T��3�����A�7D2������t��
�Vs��_��3{���Z�3Q�]+��0<�{�����9������$�����D"�����)��BW� 
��cHAq�nAb�����Hk��\LPv�^w<���L|F���!�#�E#y������zjS�Eo4�V��N���#f�
x*�u�N��
�T�(���h�������u�����,����������)_#O"���!R�g�Y�.'��V1���d����L�]�]��dg0��<#S�Hfm���k�����)����������|�:?��� ��G��;���%����H`��i�_t���<:�l�jD�P�R2M�)��{*3�h�21q���8���m���� ��H��%1�P��XxRV�T�q���++F���w���\����ai��H$����J��"�k-����[�((
b���2����FGW��Na��RL�����1
��}$p����C�]s\�Ro+���������2���#�4;6?=��������z�H�F���!��C3�����f�bdOd���`,a�t�D���F	fwPk���/���Ux��5���z������\��:��4��m����E�����(@d
��R;�R/u�(����J��d�����U���731���U:LU���c2X�;�2�z$�u��Q5�2y����{�w������
�S�������-���>��`�����]����NoxS'/���Oj��J�g
S�gN�'!ZAL��5�q���NQ�s�	L�}���PC��N�F@�u�-]�0�(��B|��bJ���eZ��#���5����]�� �f���_$p��CyT��G�f��TF�z����@���BO6v-D��
R�d�xH��,��{��Zt,�h��BG]vH�I����"�� *����+���c��D���QPW�1�jw����a:0og��$�{�D�i�y\$�����s�Il��W�E�N���K���}��m�`

Zj����V�Y�b?�����(p-��M�[� 
�~2!��������W�U8�&�J�Yo�_�H�5���2�<��:�K]��"������ia�	B1���u��� �@`��?v�`�D^v�'��� ��/����yR����J��/���#�%��p�2(q�Y��B	������{��w��f������z�z��M_g
�\��#��=-�q�4Y�~y)����D�j\]�d���������0�C�H��.<�2�NW+�*V;�Noco�$�f��f��-��TQ��7���k�u���%���)���`R��Z�"����l@*g�l�9�'XQ8���',��e��s!��1N��J"��^!�+�y�D��?�>��Fg��8� g�a���ib&�N!�P.�2�
�G��g�Q�������o6WQ�*����]�H��o)]��?C4��0�o�t�R��L3�����F�7�!b�e�X�hAy����T���z ��0;
�@/N������z�,g��(����5���0Ni�����\n�9���q	����5�n���E�zj!0U��[m��.��r��94)��:�V��b�)�O���9�+���OA]Ic\B�Q��	�,yD4�rb���[��b�����H	�cD��vFN7i��b��Y<���{�o2���mc�/��������-��k��Qs���Uo��������Ub����]����M�5�<������NZ�h
�Y�$N������K���9C��@���R�C&G
x�|1,��9�Y�<��d����
)�����'%����F���1����)�Q����XIn���������CnUzh_s�|���t@�&���rD����O�'�6vI!`�	g� ,~�r�L��3T��i������u���wBd��{*�����
Z���%�=�
��
{C]�� A@g'��w����u��.�\P�2p�y�)��w�/P���`u�2���3YK�P}7��Ch��U
���U^z J����:����!���$���NqO�@�����8n�����Xc:�^rMQx�h�k
Lr��H���c	���[����Ut������`��$`k��.�����4��J�\�I�JTf�LG�U�:Q���!V��jc,���Z�����
!/3�3�&��CEpk��t�N\7ELy��PS(�$E~��*��AT1�89��N�:��l���
��S���i�Q&�(H��LLI�V(n��^K�H!#��g��G!�mu("�+.���
d���NE���r��"�FN�]�p`�QmS#�g�(,*�`��c_��z~���t������9d�������'�H��l](����A��������C�2*�tvT��xK�3=?����5�� J��W��J���������Az�����#~�=���O=RCW�0��OW|1��RUN���4�H������Byr\=p�~r��6����N{L��.����<���x�Bu�����Vt�W$\��9���R���H�������N����6%~]�X�D��4��lC��&�G�(��LJ��q�$�,R������f��~��P����!%���(w�QX�w~��q����r8�����
��\�=���3�������;��
���_?�)��@�~�IH��#���7�����8I�7\j
�|�t�.�NB����E���Q6C��"�5���ti@2*��Y%�l�P���`8�d�d(.���������Rr�$����ew�\��Y����:I���O�����YzF�������;b�
-u���>�d���5c�i�_�J��F�0���;�<z���T��w(I���/��I9��&RX�a����?iR_R�����%��7<M�\��.=w<:�,LY>���]^�:?��A������~/�����w=Y6j�2d:B����x��CR�z)���������L�����~h(�p2V/����������w
���������"�x���>����YY�H���fJ�`����9H1^��(�c�	f{#Ev"MZ�c�46�E&�	�����I�5�xfjhIAL+��H�N�R�q����t���P����$��HE��aRS��Wit���5��!���q�C�'�=�uK\>J�\�Jfb}^C	��>*��Z"�#�����Ju�r+P�]d�K��� �� y%9 M��$ ��	�)�1��,t)nhxL4��)����)�lX�6����f$���2���LwL�\�tZ���V�L�(�����IBKR�*m�e-�TF�,E8��DG�W���xc�j��T��.�b���j�/���YMM1(c��q����������}����'k��<��-I��������:�
kE���b2u����0��)�r?O����Ge[d�'G�.M���U'��	TnClopM�0�v4J��H�Y~?E8}a>���j�*��S���O{|��w��s#.=k�.t�nD�A2]��D��SJ�������"]��L�iCw$���8#D�z`��~��h���,j�=��L�b��E'Kd��GTW�j;�$����9>�7a���=2���syVR>�:���VC?�9��#�|S<���<�l��?R����0B��Et"�����2KW����B�����T��f���\L#:��=��������/f��gE�7��5�1��T��^NF��$��c��`����g���%�����q�-
��x�I�^��~d!�����p����^���S
��H8'���(!�cYq~�P�k�P�����6��N���N��:89>�4�i��QA�,��0�
��s���P��8��6b69sL��s?�\GX���/R�F���<��Ph�������w��y�@�#W��7���5�G���������R��QL�f
O?xA&}qR�)�z�;��s�����@G�^t��%�YF��8HD0mc�B��y���:���������f�LF]��k��R[Z����S;<���!W�����mr�S���2^`	����^��"�-�������BX���A��o'�_@��3X_�,4���G�XL��>���$����W|f��C3� �#N�� B���	C����b�oK�^���������O���2�����)��6;#6�{��M,���������DI�)%�Bl{�QL�:�zT���^���b��YE������O-N�8X�8�4�����
��g�t���Fx���.c�Y�=U�{��r��0m>��`���#���[��J��V;njT:��4��\ZO7F�C���/��s?j9QM��>�O���F^^�� ���/�����-�	:CQ�v�9�@�'+�����s���0����0�}�������T��kL[!(8�@+�<8�K��0f�����"Dj\e�|z�C�(�w����)���zSt�����r�$��
�@v�R���	��)E���Rr�4_�1^}g�<R��V@U�{����Q��8���||���@����I���!���sSA��/�|5a�S��E�qH�����j�+<x�mL�R���O��X�L�*�@��7����!�HA�A
15�Y���eL����l�-U�"�/��"�)���Bx���S�����%�:sQ���G�6��g���v+�b_�m�T��:(J����6��H���^��5�5�����>_I*G���jW�-�]oS|}S'M�%�k�BZ��c3�#�e=��Z���i	G��8
�F��>��$B���:-�V�IU���)r��	#�hP��1�\O��L����K�y�k%����h��������})!���J��8C�P�q���Mz���:N�6&SQ�b0�C��,*8��Yn�a��*/��}6����R$�~�|�(��a�gk�qj���e��5��_[�v����r������/�bD@�R�)�����n�Ct�1��eq�r<6�"�w�i{2@�[���ISR7�S����$���,sV�\D�/.��oE�����
�J6�r�q71��T�y�D���;����O��4��W�F�.k��XC���
&�L��AJR�LN����Q�+7-)�c����d����
��!�.�ce)��Rul��oB�vo�"�5*�	�C��O�):;�zJ'-����F*l�Q��XV�Z
m��b	)�$G$`�2�~1�-@|�.��^a���J�����~����	���8#�s�'nR$���;�o�(s�8�]��pULz-vU�fGC����3*�"+�H]`���D�8����<�qLg��[�!��)��~n��'�.����^�����)�,����?�
�0$���<|�^�������?�t1��$����������Le�?��N'�6��IT`�t�:/��e�5�K��4��b8�P��&qe��a��*��UYE�RStR��7U�='~fl�0�$E�,���J:��x�~����,�������y��+&��-�����>:���I���T�/=�������)�/:�[��B�,=jk��O���P����id����������H���p��7�_�23��6l��']���Ms�SV������cTH=�U�x�<������a�c����	v��d�<�x5,��B��2w ������i�D
�dNJ�+s%�Zd-$�Lgns�O��)��(�gKJ�|m�J�Hew��x��C��S�R3����9���������i��hk�������f'�����Z>���rA�������h���=eGe���2=���(��m�V��k����������~R��"!�D���(��w�-uMQ�t���b���y��P��<��+�F����d3O3�9�5Y��2�d9J1��VGk��[a����H�	fl������������H�Ah�s�"#�> �n�!�$�mG:������^��T���D�	����!D1&��l��#N��D��9O�/���|�Y����z���OS�&�7,Dd!AL���T�s!,���|������i=�]#|Xx��J���Z�4�[�win@�R�����2d�}���(c�l�Hy�C����2H���M�C�_h�
�m<j����BNu,����j����O���3}�����?��_N5�������<^6�I��WRn����y����[���7���a�����QT����gWx�"TcK�����jP
Y�S'�O�����+���/u.��a@
��U�7q����Zq�������_5u��8�$
���\�w��w�# _A�'5������UBZ��O>���"n1x�I��	��4�����GQ�~`h2Yp�A~E��Z
��[{�A�����h���q��"�5��y��L�D��7-�r������G'?���Xjm�6LE:�'����N�sS|N�Q�L�ba/jS��]��5i���9o�����C`�c�=I�Q�jFJ`�vZ��s��7����D�A�������+������fX��29�n
3*$A���4;F�9�i�c�������7�t�M?MdM#�r��{���������nnk��P���������,�Q}��{�A���� ��c�^VU��()Z����|@S^�W���q>XT���gJ�!�B��,eFC"N1�I�����f���a�����B'��[x�QI*��tH#���Y��&1���SF����`�,#T�3��H���Z��T�}HW\���$VQ������(�e�
;�~�����/
������qY�[MfHL6#u���|�BR�����;~�z�w�2�T�6��)��5`��^/)i���F\�0��h�)I�&H�|�Y|'����#��"���{^'I�}����`����#F������:�����e���N)���x
�$C�5ddB�)GE�!=�tj���(��^y|����q����(D�2�H������Z����}�?I��x�)�0�o5%�����/<9���O�
�j��9<���:�����NbO�d@w�:��*a���p=CQ|�P�8�#��0.���g2������o-#xw\��A���,_�8g�7�
:��x7��O�����[�>vG��v*�&���J��z��49h��q$�c�t����N ����y�y���\����S��A�/vD{j�U��P��v��w;�n���u�������(�J;[[����3�����1K�����.v���������I������Wk���p���]83��)o�\�UPi(�"���� ��m���=������3���By&
;��P��[�7�nU�+E����2��,)���D�Fb%����
F��d���*3���16����6�.1���n �|��cE�qt34y��nl������G��z����:�8==��0q���Rd���u��9Z����n]fn\��E7>�8�~�| ����+��P+���[�8�r��eLgx������s�.r b}F��"��6�D��-�
�
���T���N�T,Z�j{��c��Y6�nh����hs�V�aco�OxT�3�<I�[�vwX�.��-y�y���:	SP�\p��>2�n�
��:��P'��qJ�\���1z3�=�h�<\N+D>�<-8�\�\����q������Z��z_k7�����z>s6��=����jF!��e���2��7��E�D�C����R���H�
Xr&�aB$��o�d!�������N�k�V�����m��d���l'�]�2DI���;����rFDA)x��C�s����_[����Y�M�~t��!e�h��U�e��D���d���������*�QF��@��0h���MY\�>NZ]��\��^u���o���zT�K��������4����B��;�Z`���23���T���m^���5�V>�@l�����Oo�D�D#1,Hi!RJ���8��	�k��vy{g{kg��������
R�4<0�����@����8@��P"���)RW[g���Y(�����W��Lg��*1a�U�ON��V]�$�����{�"�-�,%f2����{%lm���v��[,�����V*Vg��6��ZM�.g�����I���X$��|-�P8���W��NZ(*0q����)��,�����C�bH�UF)���v��mX��bq�]����;�R��e��\�������g�q��q���S?�o|��K}ym��������G���b��>���>��T���r��wpJb}&�0Y��:�k��^�����^ �X�5�i�[�Q� !M���sju=w4��8�)��J)^}�h�(R[>KI�����+����(������xQC�v��l�Ttx�k���:��>[�OfL�/0���t@�=�U��=�U���T`�� �s��?���R���F)����������|��T�q_��S���,x�s![����nO����^��U�/�����6���w(����$D�e�J���"S���
���>m��N����M�8��c��'�<���p[���%�F/Ila��XY�y���0h@#JD����`���������DslTm�p
 �:�h��Rv����a�����A.�_���
�$��W!�����Q�@�/��eH�3�o6�S�c�t��V���P��4�X�I+{{U��N�q�����$�5���WMcT�3��2�U���h�v#`9�TC&�����������~���e� ��i5:���4��b3������L����C��\>�Rp��Q���F����c��i^���|���-�K!4���j
g��q�}
GX������@G���}�<�\^0e���%9������X��f~A|�����@�0p�#�g#�X�c���o�a�}�5_j*	��7�B���0�"�}���dH�nl�i�tt2���<�$]v�$�QJ�$,8��	�Z���n���u���	'����'���$���^?���Z�l��Y���A��)H�
�!�i#J�rN�"|���� ����@��M�
��|&�v��*^�����Z�����,���Q
�)�2"���}��+~�]��������G2'�#4���H��C��q�:��1��)6��"���M�3g�(bw<�����I��l��W*���~�X�;��^u��v�����I��vJ�t��Ob��$������
�z�X�z���N��8-��n�/�����VT�Ck��I��	��
������V�	)�i���'[����/�,�pB�Y����g�D7��~���e��� g��{��W�b���u���5mT.�b�?��9�	H��c�=au�-#U�PrL��*�,u`�;��=����	�����
��G��^��J	����zo���1k!��t�x#�f���r�Wz��$��)kU�a*(�J��5�q����^�
�?��Q��L;j;74>�F
x�R��*����V��CvD�S@�k
oDn����-��v�<��I8��+,K�T�$���>S�y����X���|' �w�R*|�N=�����.Jr������)����	�w����f�g���t|�D���4���4��CNt��q�P�U����������]y�����;0>4�-�S'N36����d&��.����W���&q�p[��(�|��H!�A�9)�7v�������7&��*��LD�&���B�o�8e4��hU��c�n�.��h�n�Zjcg�\^w����0pw�da������]^w��%���,��p���pw�4w���S:w�����������je~�E+z�?��'�Q��5���
mFJq�Nf-GJI~���R���=T����n��Q�����l�X�4���*Y���m��JD�s8�9�h�Q�����m���?����|���.��s�?AG���#w��-Y�����i�1�\o���o���>>����/���5#��y�?���&BQG~f����s�
%�]�����i�P��h`{vW���:W��m��������@��d��*EyY�W�����/[��r
��Z�Ss���Wl�����i�������K�����C���[r\���Wp���n�c�c�n���F}��D���M��o���:[���r��ZR��;Z
v�56~���XY?:���c�8��.��N����� �!�"6b	�����t�������Lv��c�_
"�8�Q��7kG�q�/4J.��H?EJimN��I�{5A!{��������]�A��������v�Yj���W�ub�='������;�`�iD:�-��_�����������:F������&���O�*w�9��jC?r�{��H���jtWE	�)#����k�K.�����_���
�^^Y��
�b�F���������L=��w}�[�����%�Cv���Au;N�U��+�y9Q���R���k�OY�xe/��/u��}4(��i�[������h�h*{��J;t�I��e��]`c�6���S�C���z-�#�/���ha��$���\���?�p�������5jG���=O�X�_��Y������z��V����^��c�����=����D����	I�hQ�Z��)�I�����4�����p�y��j��?�a8������?������~�8�h��1F�Qt��qXk�^�W��
�qN��_��w�q����[�������M�Z#�nqr-�������s���'G��_���bc8�"1*�q1"��A*�u�M�s_wd�3rM(�<��ju��6:z��K�^;�uRK���T����~u��B})W�U(�"+���[C������g��
^#]��J G�������?\���i�x<t�2�mW�n�AA:�#��J����cd>���h�U��K[y�V�1�{�3�]����(�]4.�s���td������I��p��n����h��
����`�$x{�k�kWQ�A����	%$���T�+�Re{�X���*w�v�vo������)���*TXGJ���d�����,����	k���������*F���@�}�qG��HU�L����^;����6�{G�s�8�	�x���@G�Z��Q��y0@���8#��>�Y���:2�@=��<O
��T��E�>�����5�:�0��Sw2���mybU�
�K���?l'O�N�>�5v����G6"mf���Cs��:AY��$c,>2�6�B��
�\����o]�9�u���9�;��e�CX�3�c;t����F�W�MM����?kC�r�Umk����+��T�U�-m��wR�U'44a���_����
7�Bkn�R��qbBR���}��E���L��D��)�j37�or����%[�E��2�q���w*r����R6["g�V�=
��h���F�����]!�	3��]��
��V0�4��;}����jgo)
��Z��T�����%���3�o�'lD��e�!,�{Q.&��&�����[T���"�i���.�Ym{��)�{{��mk�kU��hJC�-�R������g� H�j2\ZQ~2���k�r������Jm���;�zNtt'EG!O~�|Q�p9�x�F��XYF�g�v�h�0}����J�}2���o�`Oy�e02mLu�Y��+�y�c���'���	�!�~�x���l��'M=C�����6*[d����.�V�?��������H��P�T�#�[�.Fo���NO ����F�\��PB�D
n	)�
G������[H���by�X^]����"�!!�1�������K���&0��0����t��P�H������P�gyw�d~���V����rJ�vww��)U*�j�/�t_�6��1���_����r�����`p���V*����^������vJ�r���jW�Ve����ze�H��=�]Q*����
��*6�B�
0��%�s~���|�r��w�����U�����s
P��|��n�Ey��vYl��K���1�_/�?Nk��w����\���4��H��T�o��oJk��\,����0���/H��|������o�.�o6��r�a{��a��|!��N��z0�<(��)(�<(���@����2	��5&���Z�)2�&���i�l88D�m�1R
�@e�a�7��X
u���t%E�m�C�-5�B�14}��
d�����7r(r���\�+�D��M'-�>��i�dqRmc��D� ���,(93���W�C<����?'*`��jW$�jFTzmI��[�
\5�(�x�QGMq�8��
V�BBaU��H��C�S	c������������`j]���i��$���6��f7	fc��2�TC@GE��t#�~��0|\���E$|�r��+�"V���.���+,��a7����?����sV��2D�����GS@i� rT��{��u�?�f�>uOr���:������y#a0���*,u��bN��I�!���E�q�gq�_N�A�_5�R�(#}�\lj��y����Ha��(�L������Z9`TXv������H^����D\D��+�������8����qt�@a��pH:B�-�{p>���=�4|�m�L
i,3
��i���`Tq�l$R:,2%0$�PT��D���?�69�#��o�~������{�&_��a������U�!���w�g�3���J�&Qa>�'�3
�i�� e����=��l�D��*��������%����Dt|�����- ��-��]�$����/�����)����b�MR�|���iR�$�[Q�/
�����p.��d��/y=�����/��y�r�gy����"��H���62���"5��V'w��BT*U�A���+�WG@y8S���Y�����9��]WA�+ +��D��m��%��`3�:C;c����R����nw��=�W,V���m����(��	���Rxh���#��n"�	)����s����.�����o�G�H�T��{���Y�V�~zO��
.=���'�~5C���x5��{��j��{��3��u�*.)����~C�����wg #L�-��%'/
��dTz�L��&Y-�q�3��zC�kD%
������8���}��&O[�[�)�u���_�PXdtosG7������Bg`��[�x����YKz��]y�Y;{[o��������*��T��k(Oks�$d��-Q���V������+�w��
�
�u0�q.�<�1�'N�)�4���8�E��|�15����4��O�������^�kt�h�F�Q}^
OTt���3�|n�I�4�G%�<?k*���z�b}|c����k��>��M@������R��F*�8���������1��6�((���~��`/�q����������`B��=��r�X"6D����F������o��?=�5��['�C#����,G~6���Ejg>�IH`!��|�<zkt��+G_r�n�	����u�� ���{y��4&������<��#y�<�F���"�25���P��J�*�?.�VFU(�����u�7q���F�-V5��=��g���I������*��D"c]^��#�A����s���k���x8*�
Y��qf��\�S�<a�0c��]#���1<
���sjf��I�H
��k]F����_^���H$��M%|��9�����4:�3`d[]��y��J���:���E�G���) �
Z���a�`�k��Ez>��[�
��{�<��O�9��7�9�H��^������H[����%�*A��$�D���Qt6QHnt��K��'�"����6��;9e��r�(��1|IV�:<�P����3�	���@<�+<���\�F�������0�"��*�6��
�����q��s���p
�-���_�V��;>����u3.�|��t�����:$D&f�&�XI�,+Qj�ze�$[���dy6�bcz��!�D�	�W��IpY|����2���z��� "�{����in_�� �������9�Fy���'��o��sr2=o�j��#2HK��H����2��T�1���yI����0���z�B�Qh}�C��+������=��~��e�O��9�fG�o8Q�Ll�/��.���8PR������>���(���lSQ�+���r�R-T�u8���l6F��X��1�';y��9����5|A���l1��0E���4L�4�SZ�,�
e��,O&WD-��{��<��f�u��*��.�(9�Y2<L&�I�WoG�������#d�D�>�T����A)�'���|����&{rvx����D�X�r��-`U��s|E�9$���"N�BD�(��Ha��?;���9�#'}!�h�'RQ�i�7%!^&P�P#A���J�S�2�7��2_����3	��7��m> +&�63�����g���V�������1���__��x��J��6Q���=F{��W���2�W���9:����7�}�?9�'F.;��}���;�x_?k�S�����j��NM>OT>OL
��=&^��Z���^MN�//3x�8������0�wi��������'�����3�oR�>�5�i����������&3T��f�������faU���L{������S���>�T��j������S�?}�::?60����)�_�1��b
?O.0?Ob
<O�x��)�Mj����S1E>OYTx��)�J:��/������)�<S�U��8RA��)4�L��SVt��0!���EyM�G��y�}rx��q��(3�����m[��bq��S�jW+V�#rvC.��B(,�����V�����E��vzv���^�����>'�
b��~zT;n��jG�r��y�������T��}�Ew���|����}����$n�!]j��6�D*�%�cO
5�*1SsF���K[	6��7lf�J����������������!���s���_0f�5_��a��	�X����x����B�U]��4&��,���h��?��1:?n5
�������4'�k�7���NI��Yt�T����W(��x�r��sWQ�0J#K����]|�-�8�+9yx���>��J�]���x6�n�
����Bf*���e���wd{lc�+����e���/������_��o\NF��~|���SCEK/�������[���f�U�k��b
�R������a��U��8@��*��#��E��,��A$.T��Qjx��L��4,7b���x�������#XEo=��T�a��/���\5���)�����+�R����F����+��Xh|xt�gi;f>�������5��:�qM��|Q;��i�D�s���t9�gW�j�R2�'��9l�;��E
*n��y�o���N�����8]�3�� |�R��Y�&����sN>�)c���h-H��,��(�d*�is��E��D#C�!O+��u�q��5"�w8J� ���K��c�cI�f����!���N��8�JL��n���N�p4������z}����-��Z>����w��@�����,'�TT����i%Z�<hFa
s����&�m�P"e&�Vh�YI7x��T�B�b��&oS�h��5e�����=�z��TK2�P�C�����	
�IE0�/����J]�X��p��B�\-���&���jX3R���$?�
y�������#3�4���v���l{�,i=e9K��-��0N�F����8�l��\w��B]2/�I�@q!��sN)�yF���p�)�!��I��N����&�6L���y��t����u
��N#���"Q�9�����x6�^�;�X2��"��v/�*����aR��SCc�x4��<��~P��
�T{�y�<�!,������u
f/��F(������� -ik]���`���������)��z�����"
�+��B��L���'tsQ����J����L�K���t�|��������NJ�pt`?�����{���h��+YR�������q��b����q��<��� &Z
L�>��
%3���+��dh�H���:��i���Q���0Ds�(�o����Az"'
��4/9�v	�p�-�O%�����H��M�l��I�33`H��)�5a�F��P���.a��E�����r?�n��������-g��2+�zE<�Q����2��cw5}K���4��ds�0���� <�~�,yGF�~��L�V����]v�4h�L��dA,q;�p������"��;�;�����o�����v�O�<���mgj�d���H��\�n/M�����5���	��P�C���t3�N��oSv�)����U�t����r'.F���m�~Z.�t���Q�= &�')��.��O�R��cGjT���^���q-l\�����;{��\)������/r��*v1(�R��L�2��-*�a�X�zw|��|�EPs��6&�@�H_�(5�J��{i��3�]�\��
�$�?V��*����~l�0S��L�V�(��j�%F��J��}?���B���WK��cb/c�&3�L8���8(����o�=V2/^+�!���~�q1A��b�����L���&��8~���&,�x�����	�X��fJ/�#~0��@����^����a��J���V��[�����{T��y�)Y���q�"Z�����-#
�AM��v��@�!����fP/K���Y�p�����$�3c����L��G�
���h}��Me���I:JR�Jr��h/���qv�����9��w:u�C7����-�9~jU�8�B��d���d�3���������4����vaV@����%p��]��V���Y[yJ�t��e%����n8��
0�F���hX�4����HB{�I#���������#���B�b���jGML���>��'�J�����E�+��ZTCE�c���>�h-<�L40�<S���_�"��	8LZ�1T���LT�P�+rO!�++QC��h8��um8B-�#��
��^J'!�6��<I
�J���u�=��U�
�Kte]�a�������L��������H*���7Rq L���l.o�feeZ8�K�g���rK�m��Hs?bG���|B�q.)��5����y�v���.�&�2a\������m9]�-���>���5I�N:aRY�\$"z5$�8����
�t�t�H��G�����m�"%��4*��C��H����`�|d��(���P�����$.��4��[�2�	_�MC�����a4�)0�(�H�[2D\x
xW�[�2g�*V���~0� l���J���YN�����1��M�y���������Bc�4��}mJZ�F�������W����:��S������h���(&�lk�F0$��u��074�.��"��yG����Wd����C���D��6v�J�*n�����w��'O7�ccq]��5���t��c����L8{����ot��d{�@��px�0����������#�0@��"��M��L�z�
J�|����g�_��1�h@S��n���V{�K�_p/�G����
�Z0@)1���_�+��<�.z6���,�1��N��dnI�"�(����|\�4�j�M]�;�i��Y%4Ii�5�������Z~"���n[����m
������zL�TS��t�"4��p��\��/���sz���9	�89����'g9L�i�����@$���Z����f�r����������FwE�S36���&���`{�xW�y��1?'|xQe�M2#`��J(�<��e�-��n6��H��`���)�+>4;�1����c�,����
����{�!���Z����*{^�d��uc[_������+�I���[��+^��N�����������}v��o*�g�������]�6��Y?����~|x{�����Xo�����2�D>Q����D�
=����(T��%N����]y%���k��,�7%\�����F	�3�r@������M��2X-6@MH��W�F�Y/����AE�nz��:s����R"m��3��&f�e~�'��P����a�!2�Y�x"!���4�����#�84�2 ���P���c�*S�U��V�Q��������$��y�7�a��o�KN�a��2����TE �xRv�QP���~0�0�V�	�V�@`X�u]��&�����Gb��LG�W��#��-� ��e����'��\���������"��>�I�`�C��4�6$���9S@S��R�;E�e3���,��NG����1����8#�S�~c$U(F����\�[�����4��yS���=1����4/KO�\;Z2���Uy����ti�@��)4�	2:�C9����<� �UAq�a��R[R�
�h�y��ax0�P�������/%���~���~)1R��uG��P\F��9�bkc�����V���$j��Yz#|/�mV��
SjD�U�fRa�g=U�����r�1�H�L�4em<�4t?�����z��m�	I���]D0:���lE<D[�0�RwVI:_���G1j���eNV�0�06&��kcX�af���8!'�1�7/���o��H��i���b�+e��.����������i�R�*=
N,���j���at�w�x�5���i&��D��"��*���J"��K�
���j��sZ�0�::��N�+�(CY^3$�=��nQ��Mr4I�[F��.F���+*����oM�?���L��b�d�k������E��
��%S����#<[�>�z�V��8]�������@s<%#�cX{sv�I4�=�.�oPW���{H��lQf�V��F��Z���`k9	����:�
@����g���S����H*M:�2p� ��A1�B��jj��/��Ms�`Io�3�)���N�'M�Ha�k
v�%N9pr�a����%�f@R��HfB�P�Ynqq��K=�t�T��X�����R%�����mZ����'+�>���w*��ZdD
���5�/�^��k��5T%�PZ?�|�\ka
�?BR)A�lR�L�@l��(};1Hx#�����Q�G	�N���fe��W����x!�V2��$�-��F�]��d�,�Qo���l�)OR1/)�HY��ue-� ������ad2��$��j�)��
����V��iUy�r����MIf���yX�*s%���\
R��k������&�z(q�
�#����~�T*T�v��lC'��G)�GL��d�,�#bl�)����4��{�@���Y�����<3�>*���55�2"��p����7�]��dV�Qu6�d2K�������I�jaln��a����2 �T�z|4����,
2��r�7	/�x_�&��a����7t��<3��O��F�A�gU�Z��h����;��� }��>����W����������%so�����,��H��hb�����;sh�	d�
�1!Ht�h�������	pJ�8*1�L��
��"�a /����X6��{����^�e�h
�giP��l����x`��)�}����>S���\�����$N�D�Z�(������m&so�B%w�`���
��XB��n)s�P�ES�p�C�����o�
UEUQ��3�Y���n��(�F
+J(-(�i:�
��(�	��v��<�wI������XY��XX�|JNZ�=�M��T�C�Hg�Ke���z��f�f�"u����Ql�8{���}��$�_
(j0O�;PBw��8��)��Ri~uc�2�{Y
8����ab0�����"���=��w?�]��Tf+��j�{,�� t�������Z�@�n2�E���{��O-R����~�(:4~bQ
���~����V�$G�A@�&�TP�GK*Ok���������\	}X#3���M���������E�t����Ku���=0�:6G1�>Vo���Q��c���������kF
�A�v�g��*k�����LFl�NN����a��u`i`D�4B:��D�hiw/^U�����p,g��1�H;`6I��y��iy����E4�[�[?��p����K�����B�?WQ������������d|�1ZH������#1|d�
��ty���u�������W2�����LVX��t��ye�yM"
�L:5�	o1`�G-�����������Y*���q�T&����%���� ���E@�
c���.����������1h�y�#��Q�T[����hL�������q��z����$p�������P��'�������e�0pI���ETn�U�,hXT}���H���Y%K�M���
�����h���eZ���cZ
mV(����4f���!3E53>0�p�/vg�a1C����P�� �c\�(0V;�{�E�|�F��%���E{���>���Vy��a0���KV�.Y��+��~���.�!$���/esO���|d
)tX��b|��n���F��A��
��G��x7���s�d2*�Sk�l�(�����6�
�,;3l�e|k&v�0eW��r����lrVH���[)*�c�
e	d:����!5=u�S�Q|�����_�3Jl�1�����nn���<���L��m����Be�-���,1U1f1���=������oJ��	���+�u���H�%����&���iE�A��7F���+Y��p\�O�����R�$�EH�+���D�M�"������(���^lz��2�C�C��#�K&��_J9f2��J��^f�����1�5A	+� �{�]!2�hZ\>���������G7�'e�2Y'��5Ax"����:�?�"�I�bHe�����a��Y9S�VG��r���$��|r�b�/'��C�����0�q�����QX;
����!����>��H=������qg������}�j�����7'�7�D�Y
������CC9����P7�w>I�J����-����jA�EVU��k��%�A����h�|[&��R�"�D�[x����RKb,�G5��
��T��X+�LQ�i�A���Z!5^Ru�7�.k��M�o7���7�tA���I���E�8i��z*��A��!x*�I�A|�������0���\!9�t��>n�E�vQ1�N,L*@J]""b��#�P���9������sT3Y3�S^�eJ�����D�0�]��F���W��+Kq���7���+��6��K=;G��c""�M��cL(�&I<<��c.
��O���NI�B�RD!���A�;>��6�|"la�vu�>�^��D��'�ia����U�`y�`��G����R��d3�nqd&)��J�&K�C���$y��.j��W:�����C@QL������EN��W�	3�(�/��Y�����	keA�������U�Xu^o�>��.�3JVS��t�J(��Y���FYu�?]���d&�����*,g�_�"��������./m�b�n�Q�H3t��EN�_�|��<;�9��:Z�Z&mj��K����3����Um�B���x�����0�5x�\HO��(F���������IT��CW��^M��h����3��rN�w��w�#��>{t������� J�~2���"r{�[z���T�����t�T�V���;�	JO����� i���T�\���l�tQ?�gr����Y�'�oZppx/~��?��xNk$�LW�O�����	�l����_�����������3�,"��i,#�����7�2�n'��Ur�9a���w������7���xZ5x���E^n������\��x��2�^�I�E&�2�<��I������`���w�.o���I_1t��A|��Em5���dc>C�.��~�N�����F��A�9�!;6�����be���W'����aS#EC����`�����)�������g�4K&�k���H���0�f�i���b��Jq�qH�m�p��K��&I���
G
�����*,4f3)n�kv'#���6� ���3jQ�v
���]`*���.�>_-��>�^pu#j����,�r�����v���9_��z�IV&���+I������������0�0&e{��}v�C�~ �?c��^�_���t��:t?pqz�m��r��[f2N�j�3*��E��4�O����3�+�E8`��d�t����M]��w	S�W��;F���FD��$>�[MXR
yn�d|:1$����{<B��R�#8n�\�px�}�9�Q�@1}���q�~��}�%���|��.b���~�����=
�h��`���`��f�
�qcS���s[x�G�o�
��.����N�]�A}4l|x��{w�M����*\������a�-����+F2�m��R-Q&���zW��R�'�d�hDt���Cz�a����R�.���z�����0[jm �-�����by�X^]�������[�L��Q�~^*���@	6�
lcE�o�#d���Mg�	G/�������Mu�VR��6K;[[�Y��.����Z)W�R�B�����<��l���"J�1
-B��f|�n��M{���{�r�N�l���[��������������S���+��R�]����}K������(�^��EVs�y!������\����C@��W��o���;����!���������W)UJ��������%6J��������^�����D���\�1*J���)=��`/�5�
FP!tu�I���:��J����8�bn������g���B���'�Y}/s�Yo�u���0�Z��R7���qXk�@��(>U����������T��]���N{sssU�^�y�Ir�}.'���
���wQ\�v����V�

V� 4Y�o�;W��5�[a�:?������s���Ur6�������|���S��:��������N��]-���N��e"8�z[�����G<To��?�e��8�������&������/�%R����i���~|�k����}��E�;8H�d�i�i������wu�2�K\����=�n�=Xe��2�/Sf����]f������x�CysT{Kc}�1m�(m#$��%�Uwz/���fOg�����$fN���E$fZ�m���ng�X,[����~������M��9��]��7�/�4FZ���p<��,�)&��8����z!ZBc���o�H�E�@��H
z>Z�UM{\�>�$������?bc�����R���K���H������c��2� Jx8��2�M���v�S�+��W������C�#���[.uv�;�].o���{ve�]��no��V�Z��*��;��4��W��8�.����!}�������o�j������O��Y�HX�Hf�Y��f����Y������I�~�������]�����h-����������i
\_��,�����Y�|_��|�����2�o��}�,#!KJXR���4Ib6��R��(PA�	3kk��#O|�������#��P�1�Iutr�����-O�9�6uy���#������]
�=]C�'��N0�8z7M��v4�"���cvy��`���O������� ����xtT���m���G�p���q�5"�34�>��}=��@]t�����~�"#G�	��y�#�5l3���L����:k�c;;�����` �I�,���������fA�SN���G>�|����*���4Hi�Dr�b��hj�e��R��Is�q�����k���)��z)q��&pe�S��(F�1���{�s������l=o���~(��r��v+���v����������]�z��v�_����wa�<�������
���R�[���o�,s��/�������a����W#B�;`����2F�d�|J��9�Oa�![h�N���v�m/���q`/5�n��q�!x�G;������h<�2�O�����-���o#J����������b�����^;'�`��~i�]�#o�m2EiV����*"1�����)F���v��7��������<��#4m��xh<2���~*y"|���:����JCP	���w�x�����u�G��>�ik,�������=`�S��wx���H����#���o�Q�?�v���w)�G]7���?'��n����������Gf����}/Ic^13Z�((q1�����?JHv����;4�Q�EY�UH�qu��wr�Qa��F����$�-�k��h�8��������B?�'��N��s}��"�x�#2P$���5��\��L�K�JXH���s����EU9���75�v��K����q�4���8	T�$A��zt����������q����{������/�_:���_�o���J�)��?w��U����\�����-����Ma��
��u��<���x���#���A�������/Y��::R|�RA0���p�1��i��PU����F���g��l��������o�GQ5�C�]�]mDh�����'����������b�?K�?������������0�����w�������&����
�=5��u�E��0�!�����^�d4�9�y%:��sdR.��b"S�D`G���"�\������c�������8
y@���B*��r��&�e�sZ	dae]�!��7��wB����|0Z��4P�.����5�f���Qb��cd�9s�O$4�f�.�vO���������������3��H�<�qW��0����^q����M)�V�����n�h���4�Ve��2�3#���~�%U��:��L��������z��Dn���t�o���H�����9������h��Up����@m�r����N���O733(:5����V��������ooI�P�
�SgT
������]X���1|�j"�p�����b�������A�A���l�yY�m�J���g��cd �[�R���8^�,OM���ey��'���[&���U����7	�����o�GTHC,�]T�:�S�U�t�����[��`������m����^z@�_qEf��#����u���_����������W$q����2��x�h����~�����9�G�e�f��'�����H����sz���7h(�M���9���dA�0?|�&}��}��r��?R�PfBg�Ka��8FC�J��s�cD����x�S]���?���,�@/P����*k��Won�"�o����"��23����;2Q���������R�39��9�o"=L)A>�J^�*��Ir#�
%C<p���&���4.H?T�g>�dk`P~�DT��!�s����i��Y�����,��Zh���r
4����N�����/+��_���acQ,A��_�aY��q<�
VK�����j�>�MiXB��_���[v�g)r���J���S=�������-�d����m����n[����1i}�:�+1��JLY����/��e�&���F���Z��"�����e+���Id^~*A��Y�#?�fsGp�W�C;/����}�nN��'I���8�K���n-X2;�`@�
���<���t������[M��z�����\�2]��j-2:��]���P���l^������%>o�eUPA�)B���R�9MvPt �:H������>�����I	�vu��������%�15�tc���p��h����qt�-[�a4
�6�Zvy�}S�����>��2���S�����-S=���M�����M�K�������q�X�P��!c}�9����mF�,�b@~�������	�!�QR�')X&�>� �N��Ih�}o�s�h�e���JK��(t�]�W���	M����i�vO��Pb�w�O`g����j�z��F<l�w��V������h���/D'��j�/3��%7���#��������W�������y^�wA;4������r�b���K�b���F�H�0dL�Be&������R���?��G�[�?�/�:\z�#�j�{�����z�#��n���2���l����A@���|j2��AM��\���&s��T�-��zc���}ElS����/��A��I��+��]x!i��9���l���O�lr�8K�{jE���*4��
�Y�d��`<���e���6���S��#oq#������M����'�>	�>W�=8G��K�6x�M�*����M<\qj(������Z�������z%�6��+��y�V��W0}w�����Q�,��go0>�������f����v
S�+��P1^�`5"-�
��@*vY(]����l0?��
9x
���CH	S��7�XE��n`��[����Yd������UX������F��2fl���k��DYI�{E����|-��j�N-�F7i��O�Q�p�-�O0����S���1.0b���u<��O�t=m�k�h�8����+%���X,����%����3����
����2�a��}���b�o�m[�)��'2��`�F�����Q�o	�k����'k��x��Um���K���XD
i�l�?uY��a�X��^n�
���l6�/cWg�2��b�����(����/0{�����_��w�,����2�wI��/���{�,8��*�����9CtX��Dy5�{��A�i��2'����������Y/��q����;4�8��e������U�2G9j��~���a?
��7�"�w��`a.�t@���M�e�g�I���&t�'2��T�G	�/He�T����SA�o��G
�����'���k���%'����YI�J-^����I�T��&!&�ENi!���3�;3oR1?���$�5�R��o7�'��i���W^�����OA��j�s���
9���lT�q�]�^l�V��0������Q�I������tv{q�����\u"�;��L���/���1�DB6��a�����qPcP�jU���T�|���t&��	�+���3+�����]t2}���Uc��7�B[
;�G8��,����W�P�23�<��m���� �J�Ya��v�M��e}Gvrq�w��o��N
���4�������FXs����u��~1$#3tvL]C�e0kt��B����SC�5��
�t��*����s�a���.����r�^�HD�5��h]����p�:���s�C��R<��qz��c��89>���)����"��;��\��hr�����8��x9�����h��)���r:���\r��mlLRXH��,�=���'~�']c���c:�g�(�������C�3"VJ�3����a���6�2��Jr8�B�����tx[=)>3������<>Q���a|�=a�r^h��wg�.XK���*��C3t��/:2O'�11�\�F�6���n'���6"w��+��`�O���.�i�y���;�H5BK�R�9s5:��������9D��~�v����RC��!w�����o8KX����
�FGU��9��p��n����Gymy�Y@���������=N3�c]������L6�@$PGM�U#�yiI��G3{:��-�j��5g=W["B!�}o���1o�Q
�7	l����*����' &��h�'i2_M�5�	�x�,���U�-S��!���-����%/�'^���SBY9p(������N=#"��A���Z��	��H�j�)Pd������@D�y�<��%���%&R����C�����������7�j������B��iRk`���K��H}Npt�Lg�����rm���{_� L��V=(�4*N9&�]f�����x�U� ��H���;�nd�}L����era[�(���r��VF,2��u��n�G�8c������Z�[��8���Y������O���xX���t��%'@�a�f<g����[�~��Hs��ro�0�j���"��0k�#n��[�{@6����h�����{�E2E5�|tm_���
A(6+ScaL��������/z;z����/s�`�����N��K>E���*�qo�M��0�lw�<�(��V$"���76!�m�h�����W�\��e��D[)jN6�,�;�9�ZU��@�C)�jmj��J)z���'^���{��xP ���g���'�	�R8���
{_�4���$o��W�Q�`�Gs�����*n7�m�����F������>�i/@��WT�]�/������?��U��'���#"�A���w����������@�a��3�@1��:X��B"X��(m�Kh�������<4�viS�35�iI�F���p��fK���k�bR��)���FC9K�?���%�u�B�G�����o����7@��l?,�_��������5������m?����Z���N�>�����x��=��i��v�[������~��y��*���w��,s��	EK �/����� !��F(����I���d"��Q-bB�7O3qM�o�~K�Y��]g��(��a�g��'���q��2��������`cr�o�oX�n���G�&�����>u��\�����������K����z;�Y��=�E�h��}�	:�����L\�W)_o�	+G�q������7{��]w�2� $q
����n��O�� ��p3����^a�'�����kE�2^dk�<2��Pc�J����s�Is��	��.n�o��.���TLG��q/ar���64�AT��Z���+�1�W��|��<��S1�f��g�Q�X�&S2��`Mj�!�����;�X~LF�����8+6���j35�K�f���V�����p��F��wH�8�-N�%��4��Z��^������������@�
���&o��}��=���bwg{m��&I���e6���Q���X���kk��?�����I�D&b��9,�(a��~x[u0�H�Z�d
*����U�p
��0ff����6�������x}$g��Kt{�)���C������Wre�D�l7	�r)����[`0!��������	����l4v�1�i���`��m��\�J�r-wE]BI3��o^e���d?
����
:I��a�����e�Y���C?5>��:������������L-��������Ov[���x��w*k�a��M�gg� ?;d�����g��z�c�����pA��~2
=�����Af�
^����]�����+����6]4[��~�a�c���1�`A|�5X�Ha��6�fh�xUXFun�������^���w��{`�"B�:�����DH���^/���@QH�x�����P���������H��W�M�+�f0�r�C��	��q�+]�wY��r�o[����,a�-;�C��`C��h���'�k������T�Qr�����kw:�����������@-R��,G?�������Y� ���1�b�Z�2�?9d����q��l0d]�J����1���
}��+LE��pv~��05�v��J����/$����i�O�G�&*���t�����aKX�8�V�w���9X����w~��	��6$;�"K�ff!l�j9�(��m��$�7n�E%N�V�6�eO�Z��*�������s�s�\A�^��r�wO(?(��������5Q0�Y�����Y���E�0+��s<V��{���������i�C��������eA@����W�����y������yx,f�Z����S/���\�H��D��K���� 4?��U���K���g�&��FA4;bB���RD%�r��-���v���~ zsx�6��PT�6=&
��7�G=�"Z��U����at'
kOn���{��hPA!m2E�s���t���]���.�#�AZ�PYb�S{������dv3����#tR�:�EW@R@!���g�x����������t�L�h#��;	�"����n�h�*���W�%|��H�P�%L�
����vSSaA�*i�
�sV'su�v'7�����*N��\+�EG*[�����1��DS��z�-�U2����^,����A��K5�g���C�w=�����,���:Oiy.�/)~6���er�F��(~�C�5�^���C����h��?�&���]���l$qtz�c�����a���c#8�>�/��/vv�2[l:�bn�����c�;��u����1]�oo�h�9ie���V�6�%@�c��3��d�8�.��/�d�m��W�e�#��_��?u��9-�|z&0��� Q��������2�99���qj$.i���b��\)��U�
t
�Su��
�������T��B�tz��I���\�}�6��0����?�����irH0U4�c�F�����=�����j�-��Ma'g��������F�7�JO�X){�->��?��{�]F�����S[aR���V��yIVaz�i�RC���q\�����x�l�u��h��-��d:�v	g
��.]"8���C��q3,4�<I���h��3�~��B�><)�(��%��	����X,1�������T�wA�O��Y���(x��w��r���,�^EvC�����p�V�y����rB��
�fV`f�*�k�������:�5)as��[�]��-��ZZ�����.�bMF�1[F�I�q[��,YO�'�G�tB1dlH�+��+&�{D6!9Uj/��h��y��Ue*>�4���\�oc��#���Hl�u!L��y&!"?~}3��f�O]"�b�4��~���
�aS'��`��;W8t���xS[��8�/�^WI�x��]YK�V�*�[@^\#3^	���L�����!i,\O��:a�(����i��A�TD0���iN�tDu������*<�dv�����x,p}
������@�"�4�J �eKdz]����L��f����~�(j�������C���)�a������_�8j��htu3�N;L���\��+�R2�����\/������{{����HbZ=ye�N��wh�t���&�K�r���PP��w���0������&�aJM��r$�����V��9���������){�i|�5<Lr��*�f�(z)��/}�X�]W�C��n��>��&8�
���SJ+[���i<�@vK����%[u����%�h��w:#��G�j_�B=*QBG�����Oi79�D{w�w�� �Y�C<����	�e�%}�x���'Z��
�H]��wI�������bS��~G��?���O�tT�t�e��H�e���m����*qOZW.xay��Ap�v�Je�A �`e#���aTWKr�FUMw�L�	�<�������@�����V�������d�c�N}�������y���v��/.�;G��/�g��b����P�NVi�^����G�p����b4Q�d@��r��(��k��)X��u@���*rD�?���,4R�������y����dEzeP��)"��\���	'�F�����PS$���)�J�ug���5O[{w�Q�7�
�aS�
��C��@���@�������S����4�����^u$��6��=�\��6�D��Tw�)���[��������`m;�G�Z�+�rW�^D�3sZ�m��S&��R��k7��������$|��>����w[^�;�B�Z��
�u�����V���a(���P��L| �X!F)���'��n��2bI�g�9?���H@�|j2�#�
��UI��l���4�`�9��d�PS��)�M�~B�|Egw��1[BYJ���)]D�d����s�T4K���4��G�u|z.8/������%����c��UX,���1_g��h��`����.�q���:���s:�M1���U{o�����z4�E!W���'�?�8�SWtv.OIYXZ_� :&����	����j�
l/OP��W�����Z%{�M��	$��A�����������h�	���z$#�C��~��5���j2���W�u�k�������ZtxA�@�U�4����������Z�]�&�V���^���;HJ��Rps�;�xT�j0KA����6���l�
��a7�L��zd@�\2<�?�7!R�h��{�K�Q��#��c8���x<�H����f< ���8�����?���	���6����x�M��>:�P������S3}�+����(���������#�D���T�������/#���!�Vk��������v1@�bT��~�~���������^2�xWLD���	���j�)�B���I���	��.�"P
S�W���Ja�Z�c?U�7����*E���Y��r`�
z������I���/���'�f����S�&���[
|%����+���^���jfp����7�MM���,��(��w���Eb���y,6
�[]��;�]x���������x�u1zY!�%�	�����:����ePN�o�b�Z-��/Z�*m��k����c$t-��9��+B���V{��
�a�:�P�gV�����X�+�dO�z�����X
3��3�_��q<	��T�k:'E����#�n�	��|\�\���@�<f��4A��+�d������������K'��N$�����j�L)A��
�H��Ja��9L2�&]�r��k�~��k��^�P�G�DX�~�P��5����=�������y��E��C�-&��C	�{+�9��\Y/���l��$l�1���D�"!��4f�J��{<~s��
YJo�$��U�c�����fB��-4�<��z��qK��O	sA�8.������o";����l1-k���&C��*��g�V���i�S�0\6bK�Z������,�L2�ro�|b�Mg#:�SR��}[O�2A����>{�	��g�^�'�.����
y��z���N���fQ5K��%hk]�Oh�MQ��O��6P�LOjx��N�@�dK���)�������`!�ZM�*3�?c/�zd`�+'dj(�
�������d��i%����v&$'������(��W��������@$US��m���C���y���8
�����ha� ���N�~���B����[n<J����@�#��m��].�-�94�e�\c'y�������j���������e!�x����w�sH���F�j���P~^R�z6_`���Vi������k�c4N��L~.���?ta�g�0��J�gi_�0���J��%.�Lli�+��0'���Z�
���l%�2������
MBe�W�-�l�����t�wx���pEl`�*�R��������d2\bQ�rH�\�Y�o����h��j5HR������p
�I�J��B���]�3=������^��F�1��8\�|����Ha���sA��;������-k'������O(��U�%u��'�FN���B�XN�I��N��n�-������A�	|]���ofi�Lq���;�cM Qv�N�����3�w��Q�1�$�:CE�+l�K�,	��9�`R y�Rs'�h�
��I��K1XEy���,�s~QB��h��<�Y:qF�z4��fW�Y,����A`:~�E)-
�AU72��3=�yC�2c���h�������q�~�]�+���L�kv>9�h	���:�m��.�X�����HTB�sD$����l��
����xZ�2/��}g�,�
��E��c�*�],���o�j�����1
UFO�����K���������X-�Tt���/K���^�+ �U��eo���k2S @�J���(�h��W���Uq0�����bG\������2u'q�fB,���v�0�'��l�r,*���&4��j��a)���{�h)���tD���9��{4�',xe���'�`^���.y0�6bid��)����.�0��p��4b���J<fo���n�����-��}�x�ge�i'��������F=[,�,��HR,����d�Q471@nK�R3 ���+	Y'�����p4r���D��TK�C��Z���i�iC���L�"7�S�+��1 �@��+�r��\i'v�{�<��B���P��$G���!a�[W�4|��/h��5��������a&PS��������I�PJ�D�~���������Q.���*���?k���E�Yu.+91�p, �5�r'�yVeO|���>�
��DM���-z#�[�`�����6G7]pRf	vJV�V��O?=�z��!{[T�@�i��3�U��rSR����p�9��0��g��y�il�U�d�3�S�z0�B
k��o�M�%	���
I�UPEk�p����������
s���vO�%�Q��$�1�0�}��>�~I"��p�~Qj���2����"���0����-���[)��'���x.���I���/��R�0(��K4?��#x�����'�EH_*	pJ�1U\�,���_G�����W�I�^K�����i�1i���7�|����
HW ��hE.��ed��Rt��+iQ����T1�]X�B~7�����4��*�Iw�����8�7F��@2��}<%��+�>M�:A��5x�i���9u��[�`��%MB!��[���<	�v��b����/���`.������1�9�E��$'}�ON�tV��':������`������V|Z����4����v�mc���'f821�f@�(O�9�
�rE��V`q�e�J�����1�V {�
+�� 1w~�2������`�d{�g>3�V�m��^�n;:���N�xN��2�^��c'2����K-JN�T�Q��b]y������`9��UM�$(���/$"�|��Y��|�l`�?:5dERM����[��B�;����4���r�qjS�z�p�l���{p�\H���*���Fm$��_�)I�������#�n�������:�#��{���h�L����`�����^%w����t���VI�lX�^�^*2�
j��j�����)�Y�����1l��������oQ��h���"1���8
qr���S�a�G�����1����E�,���C=���d����'{�'Xz�T�E]���������o��|�kyt]a�o1[n��<A�>������s9u(y� |�<���
���W��O.�Gg'<�]����,����!��s�Gu�S��9^cF��v����hD*;a��u�XY?(����hF���YE���U�C�(�r"��r���m�a�V��6m�iK��j��5(n��R����e9�+����y��������@,D���w��3��V��v������_/�N���t�	�����.�N�W7��������G�;T�}��?�����j��]�uA}r���������'����\:v�G���=�4���9���E{�����P�.=�1���MY�ti������v-`�Sq��� �y����<�����Rj�����>e��	��x"����5zt���p������EM������[sH�tk�|�/t"�s:��e����t��t}Q�2��hR��+��n���
B1�2��)��>eS[� �����w��&J�� a�Cg�s���w�i�{8��\7��W����6�H�[#���W��+Y�=sBe���1��,�(�0�\�E�x��(g
��������-����s���\�;7Y��(��;E�5���cL�8���+c������o�>E���R22/_+�uU�.��[�w<y��S���9��_����-'o�j�L������1��I{���7N��]�&������J��NMbC�yr<h��M����sD���p��P;���9A�����Z�Y����I�G��9x�z���Dn/������q�xf��S2����XS�dM�9\���"��v��L��b���SCpO8�;�G��h������d�H�#����VP�j�v#��3g�1��C��.��(���k
���[Y�\�C1<���M������zkw����R�lXX_1�p18��p��l�=2�&91��({�~�l~�C�#��f��<��]*Z���~�W�?�@���rY�� �b.a��'�01��"p��r3��[���� ���kR%�>t���@��0\�p�w��}-�VU)Q��d���z��������
�wF'mft$z
�E�}~���b[(}�f�n6�����8��"��^j�k6����8��
��	3�}�)��������nj@�2!j�Y=&��B*�Rq!�����`��Y�~�^���L�f[�,������$��X����mN���w��,%-Yz*Q���b|O�\R���1ph�1��T�[����O�#:��y
3�����q�s�(�e����1
M�\�,��������ob�aW����1c�;�������$��������[��.#r�d������B��O+���m����6�?+�����~����A"���]�m����A��J�\{����������K��w$�2E
����Y����e�����Ba�V���0�{n6`���*������A��5]�"���#�{�Z��9<}�^h�X��{F%���J�a���:�k��S�,�����5�$���
g�����S����`���A#���,����?��zF��?x�X����LN��@h������	���(�S�?&6_L��n?yb���:v�<t��5A�7n���	���R2�:��z�lV��E�ES���P�~���v5�|����p�wT`���a�8��u~)	�1�Q�*�7_����01}`�`0�{����K3#�<;�������@]�Q�-.Q�81U\O�	2����g9/� �A��Rw���j-�Y��2�r�EN+�>?������;���a]�W��2z��&��j����g���k����[C�D~��8�����]�|2�:������;ynXp3�\D�g���\v��+������C,S`�x�!d�[Y�\�B�on����9�bn�z�����2��^�a7�_�$��cuM�&�Pj����C��*�U��6��)|�������>L���?�D�Q�m�w�=z���=MW���Hk�����:�����R${�7D�^$�[���2�:��"��s�Y�O\/������N���6��h��oe������Z��K��7�\\|�w����U�Ny�%���g������q}:��4<'��(�J=�)�X7�ts����usN3�����_~g+�J�l��)��
2{��P����j���p_�g�y_���{��1��r�����X����5���O.�p����2l�
\�b&mMC0'�����J�n�E%G�JK�g�D�D>J*������R�O�&��������8�az�����\'��s��Ie��/1�Y��2tt!X����]A�����_�G9��X�3�N9g�.!��1%����h��h>k4���������y�<~?���������y�<~?���������y�<~?���������y�<~?���������?! ��
#119Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#118)
1 attachment(s)
Re: Implementing Incremental View Maintenance

On Fri, 10 Apr 2020 23:26:58 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi,

Attached is the latest patch (v15) to add support for Incremental Materialized
View Maintenance (IVM). It is possible to apply to current latest master branch.

I found a mistake of splitting patch, so I attached the fixed patch (v15a).

Differences from the previous patch (v14) include:

* Fix to not use generate_series when views are queried

In the previous implementation, multiplicity of each tuple was stored
in ivm_count column in views. When SELECT was issued for views with
duplicate, the view was replaced with a subquery in which each tuple
was joined with generate_series function in order to output tuples
of the number of ivm_count.

This was problematic for following reasons:

- The overhead was huge. When almost of tuples in a view were selected,
it took much longer time than the original query. This lost the meaning
of materialized views.

- Optimizer could not estimate row numbers correctly because this had to
know ivm_count values stored in tuples.

- System columns of materialized views like cmin, xmin, xmax could not
be used because a view was replaced with a subquery.

To resolve this, the new implementation doen't store multiplicities
for views with tuple duplicates, and doesn't use generate_series
when SELECT query is issued for such views.

Note that we still have to use ivm_count for supporting DISTINCT and
aggregates.

I also explain the way of updating views with tuple duplicates.

Although a view itself doesn't have ivm_count column, multiplicities
for old delta and new delta are calculated and the count value is
contained in a column named __ivm_count__ in each delta table.

The old delta table is applied using ctid and row_number function.
row_number is used to numbering tuples in the view, and tuples whose
number is equal or is less than __ivm_count__ are deleted from the
view using a query like:

DELETE FROM matviewname WHERE ctid IN (
SELECT tid FROM (
SELECT row_number() over (partition by c1, c2, ...) AS __ivm_row_number__,
mv.ctid AS tid,
diff.__ivm_count__
FROM matviewname AS mv, old_delta AS diff "
WHERE mv.c1 = diff.c1 AND mv.c2 = diff.c2 AND ... ) v
WHERE v.__ivm_row_number__ <= v.__ivm_count__

The new delta is applied using generate_seriese to insert mutiple same
tuples, using a query like:

INSERT INTO matviewname (c1, c2, ...)
SELECT c1,c2,... FROM (
SELECT diff.*, generate_series(

* Add query checks for IVM restrictions

Query checks for following restrictions are added:

- DISTINCT ON
- TABLESAMPLE parameter
- inheritance parent table
- window function
- some aggregate options(such as FILTER, DISTINCT, ORDER and GROUPING SETS)
- targetlist containing IVM column
- simple subquery is only supported
- FOR UPDATE/SHARE
- empty target list
- UNION/INTERSECT/EXCEPT
- GROUPING SETS clauses

* Improve error messages

Add error code ERRCODE_FEATURE_NOT_SUPPORTED to each IVM error message.
Also, the message format was unified.

* Support subqueries containig joins in FROM clause

Previously, when multi tables are updated simultaneously, incremental
view maintenance with subqueries including JOIN didn't work correctly
due to a bug.

Best Regards,
Takuma Hoshiai

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v15a.tar.gzapplication/gzip; name=IVM_patches_v15a.tar.gzDownload
����^�\{w�H����S��fl����3!6I�k���33w��@�-[H�$H���~���~~�$��II�����Uw�����T�F���������_�Z�}����^�d�Y��Z�"��$YUZ�g�x^2���i�����]���-{����i��2���K��`����'��
kS���U{�Mg^U��p�����xf�<���n������-���P[��e���4[����C������m�j�JSa-��T��k��-�^�u]U�Mo���Z�c��36������Y"2����44
~�����l�������u�m��s�
��C6�yd��\���%�JR�l6�b#���c��������UB�*��AX%�V	�U�tKp�=t�u�
��]�J�������6s|���Q;�����9�@�]�c��h�R0����Q_r�k�6a�����`��X3,�Y�5b`L��xH�k�j�Z�m��5����g��vj���.l��06L���X�
>4,�9D�-om�J�1C�za�l��R.mR2,�}��������VS�;�������D�r��
��p����
T[��&l��.�%*�i�K���s���7_��M��/:~~�^��=������9�s��������5s����B<��P#Ks.\�i��6x�������b��@;�s������MX�P��a��)ss��8W�{%�-��+���]�"+$���%�_]�,���q��Pvm���V}���~�W���������������,��_a][��g�|-x����6l�������=�������������C
f#pY������2>w�����i�������x��������IQ\^k�`dz��g��%��I����\*���}�+/���m��?S��Y��uQ5�1��S������h�k���^j2�:�<���_�[r6�7�������i8�u�5nJ;�y4&@��wvv0�+C���Z�S�i�U�Q���<�s���[%�UY%�c���UY�Y4��1��J'�qaX�i�T��a"v=��.i��wN��qq���n������0L�
x(�u��������;������T��p�a�L9�f��Lh�,�R�\js&&����s�2)|��64��M�x�����f���O��K���0��F��C������=�h��n��k#�$����|:�52g:���D:�o��7B5H>k�rP�&��E0'����>�az�!k����jM}����h(C�BY����r6��:%m����M	���3y�{���>.z��	*q���xz���F�{k���9�vC�%h��^��	��+mn#%Ltkkk�7a�o4s�[�@��6�� �7����QO�+�����c�zn����6}�g� f��~�:�2����1���l�L ��J0�C�Z
1�Nw��C��C�!��'�>�����m�kk#�F�������z�%����O������j����`]t2�c���n[�s�:y�f"R�o�������E��4�n��-�@����@;s�R��`t��� �r�
��(�a��M%�#��G:��(�����9�u�����Qh��������i���
�0~�*j�T�������5��v�����'��������c���#�5G X�:c^?����G?n�����L���D����H|"�&�oph��u/� ���^���=�J����N4�f��y���=���E[�y)a�g�\>��0��2sk��?�D����E�szzrZ��E�����4�|��w
��@Np}C0�+@�"oD���(�����_���Q~E�.�`X��K�\$�
0Q���6xC�����a_(^����Mz|}��:�
x������"�D���j2�s��&�����i���8��lS*s�l��N���W]C?)�����+�i&�#�]��f	
�3� r�K<����C���2�]?c,/��Pq.<<�D�7��r�c	*�@���7�:#�� j�^r#�����>V�$�d����m{�� q��s���S��F�F�������A�M4�B�k�����P��-B8�f�"�(��n������Q%w�r��
�8�M&T���'4���#���0�����-�Q��^d�1c�L��X����X;f��9�Q�X�#K#�����3���e�.��~��������gj�)�c�����x>?�]��-gLE������V1%�����OA����l����Tv*r&h���"E9&&k�T2�
������0|/�]�B[\W�T�������)gpT&��v�X~[�����GFb���l��h)�J&�p��9L2��2wfz���Zc6)I��.�F��^j�j�����S������`��6����/)��I>��_U!���9�x�K���b��6���� N����.������)�T��%
��X�K�>n��F��h�Xm�tLl��x��<^��Fip{��2����D�t:�~6rtA�4���Ca����aXX�����M�������;D���b�QF�~��P|�{��nB�����to�1A%W=l�9g�W��h�U�] �._�E't���M�*mjU�M�{��6Y�������������������R��X�;���������zs(��MZn=��������eY����,�U�J��h��$V1k����w�}�"u�S�	�
&�7��5,D~�FKmf�5�*&
�jU���E�g�#b���M�`�3�#���w�;��7�����8��~2�����k�-�����r��J����)�bRd~<���L��QY0��5�;1�g��1��Sl�����]tP�Vk�w�]U�i�D�����53�;�w���T�`K�p�{�����}j~���L�\�3�\CX>qy��A�B�!nc��k�a�\������Zt�����w��~{���|@�
�vAqE�����i���I�����y���P�+��Q�_��|U+j��^�_��DDs��_/|�\G�%%^��p|�.�Q�=�r����Gp�7N�����t�S��
c�'��J�:G��>���5�;=9�I@C:��)��I��m�g�����?u��w��~G��m���0'��H��/ W�������+�*��N�p��|Qg��S{:3i����E�}���8��;��}����j���6�pw`?W<v]�
W��x��s�����d�T��".�T��*.U����������up��T�p�0&��A!&���v�w{Kl
Q�����A�F\6p��h���d#�;���
,	������O+<�<���q���<>4��P6*�T�>�R�9������m�����#8��%��|���$�W�v>�(�v���3>Qa�8����/�u����;pprt~����s@����K,�E��St"����vxz�q��{\3��2���D|xo���V��O�����9�?��'�$�rB��'x���m�y����;�Nm����M���h��1�b3D����(�{�`�K�|����.��8u���
4���i����{����;���K������7�%t���9��#���b��[T���Y���1��	��y��-��G|-�we$FG����������^"��PF|�8�1
�_F�~����G&?���p�>���H���ZF�+�����*t��H������$9���r��uQ�x�r��+?���#Q�	�Tz��[���(���Le�R��@-�W
(�7�bN�r�I��������>89��1�P������e����3a$��\��W��~�Q�)�?=9�H�5��N��8�R��;��;��b�����E�=�@�{��|���#�B��&�^>h�)h���#4%�T�N�mG0K���]=��\t�v��vq{X��T����)w9�;]5�U�P�$e����au=�+b�&��&���YU������\+������(���Dj���BO��6%��)=G�YEfU�UNV
Y�?a���b��1��o69���,��w�P��gC�=���(iBWE8�H��\��.V	��En]�I��M�Yi[���B	��I�h,�En,�?��p.�~�-�L0�)!m�#�;c��':�M�����5	{���VK7��k��Q*���#�q����%�n0�-��E&�^%:���G�?�bNZ\a�+����)�����FH���^�nv���f��|��
��I+{+��oRY�����=Z�8n���+�L�/��JC]�c����3���O��g�T��R��R��RD=%��r�*��2��>�h7����h[\���S�v��-�T�]z~?�BOy,A))A��������5\t{P�-D2�y���+�U��\��m����.�������f��g����1K�h���UiG��� ������
���lq4�W�.,��������Mg�h��IB$mL��PB�|�a��1?���<q(�s��'��y'��6B�� pjP$���Jw���	��Hh��N��'�����\H����dq(vG�
Q�Ag3�Kn���
���Ds�s�WA�����r����;���*Q�sDJ4�V
 �M��H��R������|S���+w���O��bI0�iM�f����Xk���O�L�1��r��� N������<Hd����Pl�@:����g:���w\ w
�Jc#�=�����(��d�2��UK�=(��dqE�'LO*<!���f1:~(�0�0��|1�w�^�m���UBC����u���%�������&��%.�p���9^W?�$Ab!$C�}��w��Ts���S�jQ_\j~_7�k���J��:7u�n?8�������n������f�Q�I9�S^�Y��8� ���[��e��3��_(��)�W��}?�������/���/�u%��NE��P�H���b���w�>;hv���	�N��'�]nT�X���l#%�����1����vg�}��c���$v�+��\)Fz7��
R��F~A�D��(��I��G���h)��o����F�z�;ew'Z)+K��l(�h�!��K���s��V��_9����$��%�G���@���q��S�s��IU�C�a-��
������7i0H`�4o�X+p�8/Z@,}��xM)\R��M[�gO[��Q�Z�����Q�������!%E�X^Q�Ke�D�+4I��*b�&Vl"P[a���`^�rk�B�q���~�����V��Z5�U���u�&K�%��[h
�����C��(hR�YrF�W�~�������i8�b�%�^�+���x,)����j<+�y�6�3�����C��
�a� �7��oI��\|��O��3�5Us����0��T���uBb��^?pZ����������SA������k�s������}�������B��J�(o�b�)�Z�S��VW��
-m��@���{Y#��]���(��Q��Z,����1WHk��u������>��s~a���J�P����%V�9���3���d,H��J2��"�nda�����n!<a{y�m��"
�!���/B��/B��u`L'�@��j�H���������r�.��B�C����+0T�{>����}U���n������b�������0AA�+�rr����g�8�VC�����p�,>v�q�|*��������.��RN>����?�!~$^���FtIG��KKIs����B������<��7B���������������\���R2����/�T�������z��8��_�)�<�	� ���d)�IH�/���l�$��#xf@�9���w�/rk�������"JL3�VWWUW�r�n+������H����0K3�_�VT�:k�|�{H��w8�� X�Za+bi/S&{�;���?�������)��&���T�9�t�p�:�^C'��}���t��� �}���V��[���;����*�����5������L���}�uFx�;�]����������f0�u�o��d��M����]�Cj����������l�W)(�6���
�#q��u�~��|����2BQ�t�M�3.��Q������+j�P��~p��7:x�A���K}��D��1���d}������~E�������C)��n����$P���A��)jGG1�������1����K<DO�Bv[l�)�Xg`�!9���������R�G���}����
����&r�q�/�������>��� ne����-~�
���|4ix	 ���h�-�V�X�}���B�z.c�&��p�<d{��>�� .�+�������6�?������Y&����J�����iOr������~
�i�Qy*�	-f�A1�w������T��������=�{�GOl�@��������Vb��'�����gZ������M��1}�������
v��'�H�L]�Bw�)�<k@�O����E�\z,<b��	Xmy
��1���$
�rp�����i=��\����dk��;�q�[O{L>��m���0� �?����w�����?I��s��d���83��vkC���$8������E���8������E���mh����z�^���0��@����������3+��z���Y����X��/B��m�����I��H�.���oE���j��
��r�n�[�
vQ����>J�d��fu�w���
l�����=�m�B��.sA��=�z@
�M(��D��\��`��G��7�(\��M���B����{�z>���w)�_����;)�%k}�j�Y�nF��t����y�p��5��	�c����ci�Sli�9/ ��fiz�zG�_������Q�-�*�f��=�YTc2�[����e���m����������\�c�x4�s�2>�M���w�;�WiOR�W���L��)�M�<�������?<;=�,�z`EcR@��n)�$+h�c�g9��mr���B�7P�Z�G�7�F�>�.Hx���y�]&0>��3S���m�6|
E�7��O��J���_&�!��$-�����������ZKu��/�����M�u��9�������z�Y�� V�����C��.F�������)Ft���O�ePO�O1��7�+Y�f� F��:���c���p��T��l����<o��X�K��3{"m��f��4�-��!�������;�=�I|Q�X�����NQO�>�f�,��/bL��o����7jA�\�F=;����m��K�G
����Q������4eo���g��O����[�{���p�����d�����{��
y0�
��r���|��!���������m^�������z8��h=��Tm�DWZ���U
���K�`�*��R�����T�-�z�5J�C�?����,z�o��j��}��P�g��M��=�	
�#�����uI��=�F��#�LU�<E$���i<�f��z��\S��P�=���k&���G�y0��g���C��x(��{w=�����k2��>�Z�����<����!
��.�~�K�?����q��u�A'���X:���3�8��a2���1�vn~j�����7��o��<�/��w�i�P������2�<�Q��B@<�)��b',m��z�}�Q/c�,c�<���.��"�I���YD;Y����������fR���Od}
�E�[���+0���c��7w�pJ�L=�Jaa��C��~�cx���:�,��>�c&�yv������B��'I|}�P���S��G�(�����Y��������J�_mD2��-��.f�C���u�yD(>�o�b���e�N�	��,dPE���K%f���8-�^?�`��K��R���,U���*�[����2��i����G��7��{<;�oBA�����ef������c*w����R=~������@��Q=}��y�=�^��-��mr�a�\}�(��6)���LT�x�`������li7��{����
��h�B�y�����?���Z�s����v��\��R:�e��GS�<~���������Q=�J��%�������<WU�{X���c��xf�������7�xx�<=O�T�63=����2���X=�9������X�(z��6��"FQ���,b-dP�������ug -]w�ug�X����,*��������|N����I����O��I�U���T'����M��������A�e��?�\��fc�X����u��,���z��4��HO�f!��6T[��'�	4+v�Z����d�������+M����+rc-LL�ciw�M��?H��']�ej�?���s;�/u��x�"��OBd1�z����O����'g��C9D<W�������J=�Lg�Lg�4Y�<�O���>������^�q��,��D�1��Vi�2�eX�;���\�	w�����S1�����1���������R���A��K)�W<\g)aT�O��Na�V��x7���5m�Y�}6-����������=����<4��{ub��8>��f�����_8�x��H�L�F^�}>�L�s�����<m-�e������s�����H�C����>����=C��C�+�WI����2�',J+!�%���ev��@�\Z
�t+w�������}[�<7�@'�1��l���[��<�N�(��wOD���6B��)i��{����^fS����8�'�g<y����G#��������������se
/m_���1�k\U���is���O��l���fbq����8;n��|*�����H55_B�� Kw:[N;c��y�h
�]�$z�vF�<���6��&A���Q���2�HS��Dpe����,��S���=���T��f�1Ys����D�Z�zhj%&�3�1�&���+x������k|��It�a`9Cgx)�>����s�vW�#{u�����q���_N�bp�����.��
@b~�r���x�"W���|�����-g�/�j��/j�q+���a�7��E���Q�9>i6�����`�
id�9E�����e���c�������0�����������3����aW������#�7�O/��[C�\4#Oa���l�����Dr�m�������<������}�gmF_��hBg��}i�]o�#�������Y�yr���A7eX^
�GA���v�����5������=[�i����5r�|�{XW�8j�w��D��s$����m�Z���,?(�����7���YY)��$�H��A;�"h)��*E�1��&"!��$]�����)��^^�*�8��V�W&��R�o�B���o����]�A��l��$����|�Rm�<��:������-�la�C9h����_\=m�6�P�M����������
h��c��Eb5ta���
P~��a������[1���He{��w�Eq�������;�;HvQ8>��
�9'��!1
�:����B��:�������UA��O��x+@f�����f9�|�m`,
�8��:�������h���������� �E������f�:v�;�Y�v>�����C��$e����+^!
_
���P�-�y~$5@�/o>?7���2��)
-��~��?��� ��<��t`T�������E4��1���0s@�^8�P� ���<,�]�5��K����j\����;�r_��K�����STY����@������yq3���������7i���y�\��hWdA��$�v����c
�� $LS���{?������I:��R�'���(8�GzJ�����k���P���E�#�w���8@�%]/��q�`�z�u��;���������T����1U�(��!����E�`��<�
$�1��3����z;���$�y��0v�A�>'tv:xo�������bx�7�*�8�
	`��a.f_�y�{��ub8��f��C��4}Hx!B��)�r9:��O��|����d�(��y���#�|+�����1{|�d4�G���-*h�:���5	�vk0G��6�nLI�8<�I��${zd6�7������g��A&����q3�"'��f�O�yxbUIx �}�,S}����@�H��Bq���)�"���U��!>�4�a
���%��
�@��"pAP���9!V^8����bmlkJj{�OU����1����1W��I6�Yy�\� ��
�f�.>�LO"M����e�/PJ�z$���'�����)����1Eb���B�{37Md�����z0�&��9�-czF�(1��O����1�iR�|����G�,`�9���"�z6{<���Y����(�f���z����1�9�@���N�L��� :������6�H��P2&-����I��}=}�w�F���1�wp�I�{?T0%�P"hA��"h?�j�@9Q�Qr#�C������������OF�V�?I�?��"���Y�-k��q�����;,��$�T)E�'��>'tZ �/tr��������������.~T�j{������c�l�a��n|���4�#�Y/��5SA\�G���s���-+��{`
�]��
��1�Q���)�I�9�d�s�)�
��9��1|����?���h�[��h����?Ifd�P�e����i3�����-����-8�5'T������$���
Fi����8��(���gA����Jm�9�`
=�42����	
�.Nal/�?���J�9��3�����%��47'�$nF|�h$�]:�<p���
�"x��z'�>'L"*N��F��:�x�Zk��*�z��;�� `�'�
}�A����v_�vg��VZ�_���x�T8�;p�snZY���D��y��W�qA�_���^f����M��I-���6������\z^??'~q��pr���'�Tf>���V����.s��X��A�����F�����$�U�7#W.��\w�����N.�c�my�b��Hr�zBV��1�o�b��r�n��m����W~]y�~��C{f��'G��_�|Z#��tn��E����p����P^*�<o�(���A���t%���^�6]
|��=g6�&����jtJ����������Q�S����~����j�M"`C�
��xi��d��8��
0�$I�M��.���F*���3D�P)T�7�+~c�M���]��K7�-��Ydz��`}��?)X����k��rR�^������1��U2W�o�
���.�����D��W�e���z�7�������mA��=���fj �R�7�7��Y��4&���d���-��z��h�|�g,g�}K$�U��,q�(�8���#>B]����w�.0����n^�m4R����Yk�M0b�����V"#U�4d���>TL�h'T��2��}���Q��2i��k��2CS�/u&V8�������K/}��2���K�F�����/�Lv��w�������V�a��*��{���vi�R,vK���R{�d�r���������Y��������Ml��v����X��h{�7/�?�i�}�<�����������}-���V�=3��8�,���R������s�#��U����X�M��h���V��b�V�ui�>w:��;�=+�ks��g���#�m���B�����|�'�h1Tc/:�qq��L)���l�����y���`q}u���O"e�������q>������>�V�W�h�iJ ��)[��}5q��H��k[[��N�R*{Vio����;S�?�T���!�����������*�Dq\=���~��P���
����/��g��V��BQ<&���G��i
���L���S��]|u��s���vmv^���W,�v���;Vy���e��~�������%.C�P�E(�wKX��%|\�#%�k<������&�X{���g��;A�������P�qm8�F/r��]�V?+��)���"�rI��*%}���D���n�Z,��������������r���G�5��D���2�j�m`�`FEG���`-����IHqg�w �~�0�9C��``w�!~�~�CTQ�:�IP�%+�R�Weu(��N(���t����gf����b�)|��7���4���V!`�@�p����R�4��
5-�H�.�T\�q{Z[gp�X'G�c�-X��_�N��`���9:����!��s��'+�m�J�t����X����r���g��v�zbC���dXr]K�~f�?H?��5q��]��2
D9���C�u`����/�2ZO���i�����:���
]:;�
��l��[�Vl�D��k�<�@�vRT9���(7�������f'��w��8��te]��
�r{$&v�$������ia3��Si�j�0 �i2��I�?���>�~�c�hfY5�H�4�o�t�3�����!��L/���$4O�W��{��4����CCQS��W}:�
{�p��dM��2�����F�*�-f��R)��w�`f������&?��~�u���m}���f��<�x����.�buI�(�>gm���qs�?���d�)��T���~
?r��������OW����L|���#�ox�	B�s��!K�:���z���+���/[��D4�Z��,�,�����P<�o��`J_��u&����N��2�����q���c��Er��3l
���%��>��>'�gXY��]V��~����z���J���*#�|VB�=�������:}��SjI�Q��������@�D��x0e���v�@�#������
6;��������:�Z��QL|���x�p��X���HJ�u������RTJ���Z�����]CC����A�x]A�xm�����Dd2O��
d��8@	TzG\O�&JZ��)����J
��J�����EY��Rco�
G�#;C�d
H�P#<'�1�nFX��.��D������e�	� y'*v���8Z�Y�
��hu
�e0e���x��0��b,#�N��,�bbph�����g?�n%w��H2`n"V��-;�kY��Al�����75�-p�jz]Z�md��t<�x�����7WRq���*V�����z�s9Y�g{6��w�G����g�x�)K�g����_����$�C
'����������KKPu��O$$�����#I�-TI/4+[���;����LY�������]�]N�~h�p�0?6@�}o��K��7����
"�W^������n�c�%�����4F����T��08���5�]��Av���R��~�c3B(�����,0K�O^'�G):�G�Gy�]$�����rZ�(���I���HRYE���R�`;,X:4�*g���J�te�����yo��a�����l��;�o�qa���l�{�����g�	����s�cZ����m~�A�J�����<��������eV
�I���"F,���i�����"1��0�?��fZ�5��!���'��$Y�%C�J��	��6=H�����P%�4F�{#��"c/~�dq%*J[�x
��P�)��RL����@^^��C������)��X�3���[hGZ(��Z���{i'����"["#gK��Fa��R�\_e+���e�Q$��R�v���I
ny�)����UJ�]z#���X<��& ����y����uA�5��U�T��Iiv'������r�E�,�p
����,�Z
�n�_����3�����0��_gn�Q}r������[C���Qs'+��;U��9�`G��t\�JzbR�����JA�m�VY�a���N!���~R�u�-mv�n��]�3���Q���������V��~p��H�!b����94`A�_J��~���u���~���d����t/�p�e@	# �|�~]�t��D��Xc�`c��>��x�'C�a�����2G����k_a]u���5#5�z�~_|/�#?_�>�� �y���-~�[F�x�����h6Dr#����)�
��{�������$�5s��3��I��\M=��L�t��g�P[��O)��en�!&u������{��{�=%��b��?�T��MeB�[�&%-}~0�m?�����s
B��������>���,x���,x���,x���,3�>�Sn�����rg�\����^��%�]��%�]��?*�����g�c��?<;&;���\v�e�\v�e�\v�e�\�m�yqY����p�Y���K.���K.���K.+�,����(*�y}FQ����.Y���.Y���.Yl��>���Tv�7�����.Y���.Y���.Yl��>���T~�7����.����.����.�l��>���T��7����.����.����.����t�}�+����Y��.dPK.���K.���K.���1.������\�.dPK.���K.���K.���1.����9�S��.fTKF�d�KF�d�KF�d�1F��w��Y�S\�.fTKF�d�KF�d�KF�d����}���8�z�����.����.����.�l��>���t�������.����.����.�l��>���t��������.���.�����2Z��R
<Z�o�}{>&���� ������R*�|���b�d����=�{$���!8�#q����A��<�v�g��9(b� �J
��*3R)OX>�Vd
�%	��2;��,����Njz�DdRGw'#wlu�n�(s���h�z1�q?���;�E`����\Y�K���n�k�����;�?5��;���r3m/qVo^�7���R�����Q�����@��f���L�[�	d�1��C�~N�GdwF�����-|k`K�_b�,X_��{NeZmf���p�ja��7�<h��+l|��It�a`9Cgx)�0����s�vW�#8��X�8��	����������������%�-g����m��%��K��3���:�<�����G��9V�7�0q�E���Q=��9�jv#{��@�y;2&)rX����x#�5\����E~������f�)t���7A���}A$g�v������}A�f��?7��}u�	�A��M�a�`�P~�.u�'���Z���+����w��_���#(6����{v�N�����V�g�� �:������F�����jGM�^��wq�D�x���V7"�������U��F�[�D++����2�U��s�Zj|a�YQKo��UHz���I��;E~����^���f�J��*�k��YG��������}���|���|@K��T^t��q���sEt����DP����%,��A=m�62A(��mr��z*|.|��7B����	Bk8tC
AA��	Do<��8��?�/��(����/�����#B��pP;�#�>_��D��E��D���N��CVo����C�|��cg>��*��)s�o�o��Y�R���o�����5���7��1�T,���p��oz�gPx����N�
���q1����`����8!w>�.t��h/}�/z�[���}	�9R ,%:�|Gz��0-����r_��������S~U[�v�����n;�ya�K0�D���h��_��q�<��,��)�Y�*~b�.~�(�v���~���Y��=8��R�����h0:�Y����D��C�gC���;���k����\/JMa_���%��9��,��c�*_E>~6X3Vj�1����$V_/�C��$���.4�sf��r��"��Nb���;q3>�Bb����S��B)�v7��0�I����:���C���oH{��&�\�G!�/>���b;�����.�����*>�j|�-*h/��!�&��n
��?���hg:����h���f�l����g���_������{���������GaE��(I�@j��N}{�a�>�)�:�X�F���'��`���y�IjN31>����]w���z�5����F����w��fBf�s$SXuE'8�M��C!��D1��C�2�`4�
P?>$5�]��>�i��qDym��c�x��y��1�J�7��M����{
g'u8ZJ-��x`�� �`{}���	��<qC����A
�%n����<�	U���d�q�s
����2/��Kt�Q	�q��#�����@.����a����s��"+�<�sB������-��R��t�^��a�#@�a�q�j|��^D���^�AmnA\�G>^�s���-+��%��y�.,l��m�+�VL�/�c����
����~X<H���]O���[��h��� ����B�4C�G��i���RMB)'��)0��(��(��G*Ag*	A��wo<�st&5M����8=�^��!!����L^���y�q7�����h(��^���5�V�����b}�1G��}��iu��0h��2����+����������(���p�8��x������������x�B�
����eq:������.�N��[��(�r�*y}���9����k���po�wdh
��%��$�%��;W.��\w����N�h����<x1r�x������� 2���1��,���F_���~U���g��[8��g�zr�8�E��5r�N�����C%j�N/~�������w�!��IOk�o[N/l�t���
��+�/4�J�5�c�yv��8��#�=����V;n5�EU���(H��!���Y7V�q$���q��e�TI�Tu-W��Bu:�V�����������NC��L�jC-�TktYp9�g.�i���OLbu�X������7 ��uq��^��l�A��5�P��&p�����
dx��'�'�b�j������K5���8Y�>���Rx�M�(dX�5���J��[,���i�������M�l�u]��^�J��f�����x����M���������xm�7?9���GM<�p�(R3i}�����~�w�K�'�����T�B���R�\�S����S��(=�(a	������9�.7��7�����n�[��������������n�S�v�w����oUKv�b���=�����|����/*�����+��H[�Hw���|�r}��q��GoW�t�J�����v>�T].��l��n���~��z>n���+���Z���(W^�+���"����RX)+E���*P��U�������A�e�w/�"�)�~F��*��"W���K���25��u���&*�%�!��{��};�K��������VEU��,�G�]��T� 0�6�Q]�o���f~u���zbs�����	�oOx����W����no���+-X/�������F�N�af0����Ml���v;b�>�U���j7�D�Y�����=�[�$o���X�AG��/��?�4��UYUT�(����7v�S��������B%C@6A?����.t����}���k#��~��;A������
G<`�?�4��Fc5���-������f�K��������=��M�� �&�F�D7��y�'��7p.�3�*R��:��L���>�B�\�)��^�J[�����'?���<|�`���!������A���=�������������E��>���v����<�k���a�%�a�_�ye�f����@�D,�
��Bu�Q����F��4�C7HD��;H�"20|+k��������3Z�YB��:w�L@�!4G�P�v�gA�z�i������F0����OApY���������i���9���8>=�yz�px���k��1P���-�����L�c�.N ��yH{�
�@�����]��U*+{�N�].[�	zz�)D{z%���raWl�_�y����E97�6e���#����5�����f��?Q�t|�T:����&z�B�N����?#g�/�B��7���MJq����y�oU�����x���o��6�}�-|\��Xc�	�������g]�-�(>�7�Z�������-�$xN��[H��oe{b��J��	��������.�!��72(�$��&8��N��(�n��?�;0B��&-���Ki��q��>r:DA��]��"�����$�N�>l��������K
�\�CC���T{�
��E`����Pe"��E�����A�����!SyN�<�|�F0�R%c������[\|�!v���Ho����G�Z��3]���� ���H|�f�6��5����d����D�v��yH��D�I�!�g��4M`8|�C>6�<�����<E'\���w.�q���&�=]�5�6������'!Q]���H�*c)��W�[��w����7��G"1.;_�'���g!��@��+���TE�&���n#=Os�G���p]�Am-�NP���a���@�ht���3jRg�6X��l�^?;;9{%���H��c�72����E��a��.\�\f�2=V7��b��r3Lm�U{�e��/������zuc&
�')#�QH�3�`�.�w��X�D��JD��)��:�<3��p@QK(�<��B�s�K�jy���g�����t��38��II��x��M�D��	\�g�^I,��,����GOJf�MR+1��j�B<-��������4}���)S���"O3�*��E��$;���8d^������SL��t�`����"���O���9����L�+�N��y�7��j�5N,p��9�����lO��cVW�����-Z��5�P��t�2��{��n���FAw��)�2�����B5?�N?��:��������t�E����.Q����	��p2�w�8q�&z�����CHO�%�rt�J�dAX��1p������~��KR��'��P�����DU3���H�t7<�B����^�Q���bU#��@zr�1����x`�V3�3�G�Dc��!]!�L�)��S���/d%�=D��zA��8><�� ��p��anj���2�KG���.�*���O>���|I�E�i�&2.m�t]��z�F�J�4.@�������z<���w0��x;�lu���/�Z �5���l�D��qGd>aI *JH���i��p��3��d%A��3<��;��H�����%�3y����������$�rH���vg0VYBpdWxbq;�1^��F{D��_�h�w�R�fF�����H#)����;������bs��������s�7�A����zwl���B�QTs`y_�:���KkL���{�V�(P]���� �5Ej�YZ���J%����iH�#�	&X�
n��I��FU�Noo����k��]k�*W�*�IFUai�S�[�w-����+~n��d��hx����8���$�����9}�[�����������	�d��}N��
a�~
@/�f&��i3X�(n�wh��]�����lc�J��W78W�	*o6*p�p��>������@��zU�����	�1�D>�,���&r�~�:�;�����K�#����f����"
�b��6w�,y0M�����K\s�*�7JG��G�@������;�mzVxb�l��l�;��@����������d-Ih�����n�#�vJ)s��(�^��80$�sd�lmK�Wn����2��	V����0Ck.eh`��6���a��6�s<���	N���S��#�w���C[*4�
��
r��mX���Xk����6���:���K�p���9����������M\P'��o���N
z=D�u�c�>��A����%��e����N�S�=r������J�=m�:_.=8�v�:�w�s���w�]|�����K�c�XlO�JK�K|2A&����6a���,�a/k���64�C��L��%W1U��.eRAM�|ig�v���T��X!^��2�����Bc����~��/��i�\���[�������II'���z[L6B����(�oY��SSS(#
q��^��������#�
��<&�������B��b��*��7�#f��%�1(����H���Fm���6�K���P#W%��<n
�v$�q�9zu���x�A��@�3��r�2F"�`4[j4��_�x��um�+�����������+~��td'��0p�n����U�>V�!��
O]�v�t=�_���z��rt�������s���U���^�Q��2���OWz�
_�;�=����I7t0'`�rf2mB��3���[���{�����l7[3sX���$v`mJ�I���J���o�N�L60
���aK�b�-Y�d3r�:i�q�n\�]�+,�8i�6�"�wI���F�g�w�e�	-��i
�N�q�I\h�M�G�����4B�������~<��x}28`��������K��?V��������Gf�l���U6��}��PO� ������*���4�?����h�����G]&���J\ .!��D0Fe������^t�����cIV�dJ�ZR%l���J�5D���&g`g�<4`-2�E<xS��)�����n�`���.��ib?�T��/�����3�a�a��b��ju�Z-�oW�y�+�^����V:7��p�����2��q��d�&�����GG;c���TZ�2�H���]���TO�;}������j���GK�
��_��H4���WN�������3��57����M�����f���>����A�3�>�g|ZE�5��u���������,
+'��pJ��JSA
��;b�v�{;����� ��"<�� ����b��B��������t`T>3���O�(�q�O��0%�>�3�uC�<n�<��AG	�����c��
��i�����8�o����}A��A���M5�������6��������$�gS�CU��+��-U��E����B^�R��P�a��#����~���"nlCid��M�c(��<�c8�L�.j0�Nr��1�+C�fa���K �q5`��m�a��Y��#��R5j�@�3o�����yn58e�8��|�����(~n�#C�eAF���<i�|���q�~�o�����u��V����l�0�[��x�/�&�����&4k2��y3
������X:��^6�A�
D�q?��og8~Q+�*S�0yM��&��������{@����2��u���dc;	��2�&�d��=x��G|I&8,z��aA#�):v��
��2�>��4@���d��~�%�����w�x���9D������8@^g�!;�����x[LH<��o��~��L>c
��<�n�c�bJ����Z��~�R��
T����6p�ND����Ob3�bt��+������l�gs������&
���
q�6���Yk#��"+�!2\��W���1m�Y4�I���+��{��PozF��D�kt�4Rl���-<@�R�+}5��Bw�1����'�����c��O`�g�,9;^(e�v�):���
��^����]��F����M��1�d(��G��k[�a���R�������gt�bt�cu_6�m`����C������s���y3�&4�����`XV�G�ZXn��/&a^wl+��;|��W� ci���9����]G*��

����p<�cRGA[��l+v�G�3�� ���1����\c����YvBP)�����Le{R���I�p��8M�Q�2�i}����A}���f�p�����i�uX
��Y���g����bd���/]f2���JRi5���P��,�ui�Q��KTq'
�6��ezD
Bj�l��)�x>itc�I)���8�:��{��=vG6(��!<a�J����F�"�b'g���X,��N���F��X�x����r�g����EC�_N]�4�����8�zYl%�'���v�z���q�K.��g��UI^R5E���Kva����Q�����$�T�C������Nf��T���2.9��2��;[�HS���s��������R�j��(
M�%���C�d�23���,A2�=�f%+Ig���H����X���6���<���,�����_/����y������A�4�v��xf���@3"�����;���v���Q��J�;f�g)Jz�8
<2ENd]{��������f�S,Dx�@���s	y� -�������iQ���`9l�8n�@P����B?�b��q��xN�N�Gl(d�*������&�)�_�gpN�I�-8�8��a�Y'$K])"EN��� �g����-������Id��c�]�r����,)�K��FL9����<t��{���U��^K*#�������4�T���p�dcIe�N���~�����r���Xq��������z�����F�)X�#�������=N�Nns��4�k���p=����w3�W�>[f�]��n��6�W�%K7�#�|���P�b��j[@��6�����V��������Qiblb���w�.��������Ix�CW���A���&����L���8����7A�ph�C����5����W���0�����}(8�s<?�^y����q����.OHS���hK�M��^hh"�1
9�c[(�z]�pu�������	��,6/�����;2���]��2��l8^���g�_�h�g�W�:��}wh([�_;���\�Gn�K|#@�+���d�j:O�>���sK&>�����Cqp�����)���r������6�*��2���W�V@&��(����&V�t�d�Mc���'���f�k��]7���@v�����������&��I������g,�:t�^�o�B#27��j&�����N*����^%4��e ���E������$�u�}D�v�e��t��9&����Ft����+8Gk���X>Ub�3�8;e�SY�e~P�g%+S�1��6���e{����~#Ua����>�q�����(.�Bm��\��E������?t��9'N3����F�B�5��)�\�X�I���������4!��u���yv w�*(YTt��U����X���t$*���_�����0�_�y�|	�tX?����<+���������%��9!���) ]�s����?��J���Y�)-�>�?����[��U��w�n��;{e���K�]k���������e[����,�����Y��?'�5Q���"V��b
`�(�pjL>���h	;*^|���b���3|���A��+�i
~����������?�
�
�onR��x>�J9��s[�����<����z#c�J��~y���-�����m{�0�(��i���bm��(�~R�7U�dh7Q:9�;}?���g��>�X	8�V��
>\Y�����|�
��_G^��
m����,/���WV0���
%��"�����l�������W�MK&g����/�>���.�����;O�	�F
c���?�.�;E�:R�F�/�)Ks#�Z��v�������##�7�$��p}]���������eKf\����g��5���hG���r\� �������]V,�Y��E�������WL+':�F�����:x��c(��`o� t��|������?l��t�0!�-���-���^�\�����q�=^�8`���(w��K; ����Z�R�F�+6`����)s����vP��q����}#�Ud�y\��&-��^;��^����ky�0	[��S[Q;d����my�
o���x�T+L�s�T��y@�{����8F5J����W��Z�/���
v�L$3�l�����ulEvt��Al����(d���|a6aH%b5��x��B��Q������)j���~����a�c�In��rYZ���m4a�0��A�8O����������T�P�_�Oj�0�l��d�Io%�)��Vu���z�bg{�*U����k%���Jp��J����'�e��r:��ht	�Zt]�
8�^�~->�������p���w'g���c��\+��/p����f��8V�hI����7-������"�����\ �PjJM~���dS2&�l�C��}���uxvr
���]�G9&
k0�Ui�++���_���S��)fl]���������~�L�1Ni�����J%�S*Q2���d��-�������P��=!���<�����n�[�5�v^��E��g���~��M$����8`�<���������k�?g��jG�Nb���+�����U9�v���W7��#I��������H���i���@�H�8�3X����$��M���]'LU��#���g��a,�
�����
�EK��]�4���\(�,v�1~A`U ����<7�y�,�Y��1Gi��oD�EJIc���INI-�fdI3'��"�*�D:�<�N)��k�[��]:fz���
��I$�Z;�&)�����8/����{���8�B��V0=��wX��-�g
 �g���������Je��Y��-�_Q��r�f$|���~d�����[[�q���Ve��{���������{�^{�[��;��������l�[;��newk�Z��{H�_i�U����+������f����������j

�g���.����o�g�)e��*�PjW-_�0�Z�����?�������_������kCgiu�����F�����?�[����#Z��FrS�%�
?�%��Qxk�V��6�-%{�rC�-W���&t�����\���L�P;��<��+�}��������R����b�94�
���%���>A��<������?��W4*@�9�iv�	��8)7�Z����|T�p]��G��/����C�]9M{0"��1�������
�m)3`��������|0�n����.�Bmk���.0��H����
���m����5�`�[��]�����R-�~K��-���By�]Y��"���
B,Gp���3��N������v|x�L��^;���N����C�s�I��8�}�h�����Q��u!U?lP�@����5M,������������:��
��$E ���s�����>��jGG�����q����NH��o&z|Lh�\`����y������m��h��8��:Hz�����gg�h�������t|N�����Q
�R��U.��%�h��^�J:DD.F`0��~�X�������@�	��_�y��4l�c���L���}F��p���N������l�T��'5�<�)�T�E'�P_�[7@.Q����Kn�eg�7�����d���_�;�X���sU|��?��R�6R��h)�o�4��lF+��2���
T.�X8�t<T�Ks�o]��g�0�����6
��7"8��k�������F|���^^���;���e���#�Y��������?sL���.����ML�U�Y�C�����c2�����fL����{�\.����c�3��':����{qt,�C|����
T"
�����Zr����>2��[��X�J7���D�s�CEyI��5*��!����� ���!��<o}4|��:��TU����Q��sy�������{"Y5v�NP�n�����	�A��_��
�&^��<m����Q���2�Xo�W+J�b?��w�=�*��*�
�d��;�Z`���ky�^��+����l��g�,}mw[2��+����_�4T�fp��3�]��{�\�"�&y��>��'�����n�mW���3�{{��]�N�3��	��e� ! ����b5��x�;��>�)k���x��86I&��� Tt=Bq��TYo��)������jK�
�=�����A������������+)E1U;���1=:e/d�<���V]Z��*��])�����.�0:�r%{��$�0^�p���Ax�����/?������Z��W I�����XI����n)�����8�K]v�l��q=�m��6R�Y}�f�h�>aE���Y����Q?��9�R��u�+��i��
��
�&����S��l�?��6��d�9]�[�Tb���������1���w��u;����~y�����tw�w:��^���V�n�d�-��{P����J5K�[��_���oM=�a��R�X��J�A|�:.#�)��e�[:��=r�X�>}���)M�<7I����+�I�R��
����D���ie��������}�}#��V���0^Mi`�Z����5��UcS�'�/��*6����p\W)3����R�,c7�[�nEu�T�1��'����N��)m���J�b��:�R�����$���(�W�P:��4����l�(sgY5����-n8&�?k����_���
�9[l�2�<���jt����� �u[�'���3������~u��A��2�U]cb�g80Z��~�?[Y#���,��"�������b�;��#)���wp��yQ����.����i�����6s:R��*mw���W��~��g��~�[�N�=�J6���}�t���>z'��B�CtU?���z 
A�~bo"5�^Ol�-r���#�w*��\���F�w�
��n���4dE�@�����
�����0�3�f?��8{���s{�R���N�W.��y�n�\m���v66d������|r)m��>�d}i�V�97qh�����u`� ��@�C�oe��*���P.*����8a�DK\:]C1q�I�C%����x��6����&��`D�����������r��~t�8,�Oj+�L�us
�����I�;���"c_����x_o�7�.�X�pe`�J���d�	��*.��o�����Z����YyGj
�"��P�;>�
�
	"��B�A����]8��[���^e����m3���FA��=����=Dp�����F���5��(zpG�LA�[���JV��a��7�-w�1� ���2�2��*�u��F���FV�	��ZN;y���.k-}�y|B����*��X�A��&EvlO) �cw�]��+����n�;[Y��PR_�R�un����}D��Z�vt�>�������Y��By����ONp;�N����O�_N�- &��*WbT����ZB���
�c���jG�\�l6�-�T�D,a��j��42����A!�&E��!�c
qxxT}g�O^��\:C
������X�NZ\�l�D#����	�=���8�����t0�����W�*U&�`��$"Y���Z&|�J�J�>D:���$�4�
�:�$�����rDm�WI�q1�3cX���Q��1TM�������.�b@*����}*
@�����
�;k������cc�r���mo����J@���`�~%E�Nk!]�-��	Q�c/EY����Y%#h�?�@h��*��S[:/r��,f�GZ�l����L��#���a�h�A"C�����U��++M��;=3LPT����U$K/��,��k���P�>�i������d��I������t�h�[���-o��wv�����O����v���Nu�����mk��[���)o�zv����w�wR�[.��f��n����&�
+W�%H!W��U+�dg����	��?cd�������0��q#��B��k��)�u&�"���VY]���(�s4��D)8����H3�F����9��
�S�0^^�L5���u,T�NL}E����T��/i���L�AC y3����GN��YZ�E7;�[>����r�U�hI��|�78z@����S�*� G�)�N!��J�3���O��D����xN81w,3���p������]�9��>�or�(��N5�����hN��z�YEG4sE58f1g����1��1L`��]+&�m9�L_�������Lk��4�ML}u�����`0�
7.Z� j����0��NA�&�n�/���&��G��OAaTI��Dx�a�\5�?��)C���)g����������%��	��6LC/�J!)Fc`�XG7��Ho� j�9u�w���)+���L���]Jg�R�_��0H��y�&%��8f#��Om$�:����8������--FKl?ZK�����}�W5�TQ0��*z=p�/�a��
B���<eM��������.�")wp,{��xp��_a5�v�7�f�N�������Y���P0b��'R��t:ot]Y	{�*e��0��n����{��w��E�M<�����U.k�����X�l��qG�H}}}I���\���F�O*
�5������YF"�q�����*����T]�O�~m��x�i��lu����4osy�+�_��C7J �WZ����J������k���i���(�����t��8�^�	mK6��2��`���/�����4��P��nO/�.y�e�.������^��������l�sz[I�sz9��B7�U��]�#a'��e���A�j-�-�F����U�d�����Hy��0�nH<�
Hz}���]��?r��p�%jegO����A 1n�s����j�j�(��nst3[�'x�R����(G��Hu�QC�u�z_lOV�f�7��
�\�.@�ST����y��5��L����8<&�R�����x��k'r������.1������e�G��=VS�p�I�0���6�������3?jv��e<Q����F�l���3!+|cS�
�3MV~�a&���q@`RO!�d9�T�?��T���f.]�;�[���P���U���]����J�mmW���	�0���+��R�\���V��-h(��R���;���Ck4����.�����(p;��6mze��Mzxv��_DF:�}����j��6fYo��s�-��b�}�����.FP������5�<Mk^��H�����@m��%MIVcz�U��f��h���`��H�xt�`M|�v��] ���y������g\�c�)7�l�r��v��q�uo����t1k�{�Qcu���N5��A�QQ)�O��	����;��_"�1[���|����=�uc���K_�I�����=D�m����&�B���Le�!�V0'}?���o����	�w��rl������k�Px92d'.3�7*d8����Q��0�@�\�B���t�^�F3���P�?���,���e[�x�}*w�;��Y��XCA��`��/El��k�5���o��}���SRTk�udB����e����s��N/'�y-��)��S.C��Ka�Vl�F�G�~vvrV��79��:�ON��w�Z��������S����Uq�M>�/sk�S�t/�����VS����Z��+(�[����hD�n�D��'c�9q�h*�����<��W�;�C�X��$����������4��������I��i�U�����vy<A�(�_Q�)r��T�h5����������Q�qL*n���~Z;����%�j�A����q�������}r�x����JO��2�1�� �P�������Z�����;��vw
�2:��
���"�&�x�:3�-y��D���*�HW�s;��B�nm�=���e�6/�.��[�*c��z��2x!���O(*���b��������6*�e��N��vG�0�����!�kK&�np���I��^	���c���R�C'X�
��25e^�����I���M���\�
j_P��o����8�O�?����W;1B�6������0A�=�_yT�~� ;y�^��N���#7�����`r�bf{)��zHUd����Q^]H�<�������|)!��Z�$���E��e�7����&����U(?���n���-`�I�� ����r��'{0	F�F`nt����9�[����Y�b2h518b��d�c����.��>��H�M��[�E�#)���~�0R)'�9��/������@��"����h����\>u������:~���PBZ5G�#�I9��T����s��
����8�A��%F�����P��7��?�Hw�S��"k���*�7��V�P����	�r�:,'2T�Mqn�G������*�
���e��H���]��"z!"+6	���F<�K�G�d�+��]^��!����k��p3�+��'�+��*�����e
���1n��G�uc2
T�?����]DKA0 �L�xL!�F����*S��_��V�P��l���^h���mtsF����3�,��V���#����t;� ~Eb�@���A�t��f�uV�([%��Tee{�A�k��
�M��	J��E��%������[��=���%�p*(�"� ��vS����_h�	�	��B'�x���#���?����?���~����r7�T�o�q������W�E�����Th2�P�B�I=���L9�w�^�	Z� 0���!@8 +~�@Sj���7�r}9`�h����	*"��0E��}�����������0D�����>�<�s�h��>o����lmx��0]��{)��b
CJt��!����S����Z�N)�������M7����\�)�w@U�k��B�k��*x�.�]d�U;'����*T���N�P��b�����U�I>"1^��&1�t���)y��tK��;�����\��p�^'^����
����!9r|��}�>,����'i	O
���&2
��P��H��
������r��
%x�o��m*P��}8���D�A�d�7����=D��7��0�VM]� Q3s !XU���8h���'�<t�R��+�G"]�����4khq*k����j�H����+
�/c���j_^����"G����12|5���%/rd�Q7kd�*cd2�"$F<CE�BVD�5�d�y��
5��E�b2@ n���&Cr�������{(�^�N;�k��}CTpM�u�c��8	��������RG�����rD6Gn��}��`���I�.�`���~Nv`x�G�O�p\gU����\��	_S����T�y�`�;46���Dy4����T���|���2 R�K��r-���~�+�23qW����D-Z�T]����dt���Y��a���
T��s O�� �=V��|^_[H)� 
���vF�"��5����+}I���wn�#~�z.6{8���a��d��^��g����~Z?>�����ZI����|������<�F��c|A}@6��6\�)�3��&���$�w�/$p�#���nI�U��R��g�4ao�+�P���>1d%P�8Zt�{e�"B�2<�+��J��RG>D���a�y��[�����*���4,��&!�;��y�*��s_	��0����y_������4-��X�����o,cF?�lB�Z\�������0r�������������>�����_<<N��g�������zh{%�%��Q�_����F����w���CN���������f�}{N�j?9��{����i�P"�P��:�Esn�!���$e?Chv^g'GG�n�����D<�a(����?� ������qr�RG9Y���~�d+������M��v�$�6W{0Bb�!�q�~�{�W���p�|�zy��vV_05Lc(6JsJ�M���2��5�D���������r��y�C���/����B����������O���&�z6d��%3q�;6�8��;e����V����I`�v��1����B����U��N�&�e���j�$��/t��n�U�B@��J���8���y�!a���7�A���=n��[=�,����;~�z �$����������l&-�_��������lKD��NG���LY�'���r,r�
o�p����=�q�F���{0i�;/F����-�=
9�=��y��w_��,�o�����i?<����x-�`i���s�����T�Uf�S�[1[�8f�'+
�"����\yqm�f`
a�����!J��|�� ���
##_ ����L��z�$
�+QH?�B)�pJ��j�u�1��q�1����"Iv'����~�H����~�G��8�d�l��d�s{iUg��
��$�M��8���d�������#1E�P��R
U���3�h&e FT�����;V����[4�;���!|���r����q�GxA�B�D�I�yp7-5X��\S����e���
�t���-�b�}����G��������54�./=�g>y/�)�2���"�)��������'���nU�wx�c>8�z� r�`��Hq1��������'�<Lw�4CdxRE��f�X�Jg�W,Z����N���?������M��P��-�M�Ok����������%K�,��]�G�{I�wv���e���\��C!|n�����cPlce�#-X���w�mE����]��5b<t~�x�6���9������"UTF���ry<G�'��[n/����s�(F8 �P?�a}.�j����Zx>���0n��!�i��
K1ob
P����O����q�\��|�OQf��%9^��^�~��qD�n(neS��@]�;�7Q�B�5�d9Q-\�2W�M��< �pq!,&T�n���E
���������^[��$�GJ��fEYM��I��YV"����BC=��mA����}���r@\��Th�
�@���U
������������B%nhC�Cx���{c���h�����x�z�:u$�H�0���4�2�yh�!r] �*p@�"s%1p�@�P����C��p:�
��dp]@r��^����w��b�����v��������&����G��m6��w<��HcSa������������w���*�����y�v8K�����	�w��f�A?�����/)NZ��u��A�A
(��&E<�lA�a^p��i�����)��K�������:��"���FM��\����'�E����F0"|�}^��5��&��%��y�}L�,��[@N��@<��0>��������U�.%#��q����]�7�����k<U�����dD��]�q�f����f����Z�C�E� j���[_)��NPD�~}��+�W�����o�~��2)�iZ�cxA�'m�b��S�BI�PK��r�2��I�������/9l�H��N|,�����u�'��'��Y�9bjm�I���G�5�S���0�x���F����������
�G�5��D�V*���9��&�?���!��,�S�[�/�U�?T�
kx���5Vqh��+���|���{"�N��������kt��@���2���$B�a��Ia�t�NEF� ��:��e��4���3q���������5l���K_$O:5����rl���'��
.�qP�W_��
����;�I0�]��|8$���&9��0����P��c;D����Ld~h�~/0U�vr��f����d8�$�@���"�������YkA����:�R�\�������b:8(E
j�v����h$!���6,X�9	W��>6SBP�9k�	���6�P3&E��o����g��79~��6���l����J�����n:V�,�H(����rB�)i�=mk��
W���RDd.OB���/X���e�{RS�wG!@� �a���a���c��G������E�t:�w��#L?@�-���"V�i�4�EJ��0W�)[;^���OZ��5J�c����S��FC{���O��#w���a25�y,�*U������r��c�0��L�-����8:��#c��M"��as�����9p�ci����Q��^�,"���h-���a����B�p���#����xL�������������=��#Y�=���R�G���X���J�
��(n���(�
Q�hVPN���,��/m`�\v������u�qd��
JC���F����Y��`.�1���c�c�h�<g<�2�1h.pI�/�uR��:������-g���Qx
{���;u����+`�F�[�q:�>^���R"&'����tP�9�*0M�������279--XE���"�B�%�������/��?��f%�.��h���e�y{�@_a�o�U\��x&w7��_����y��"�F<�k�K;x
����o��Q���:�K�!Dl�{L_�Q�97�b ���(��6�%7�@C'������Hb���x���[w�Ez�\�I�}�a�"��a�4~���������^��Q��F~��tR,��2G���Tv�X�U:l�������&@����	��
��K)���rn�|T%����
�Z�p|���B~��k��
v&E�F.O044*dj�H�S��K�U7}���P�VF�����G������y�����$�<_p��B"�nQ�o���U����Q* f��@�i���'��,%C��~��>�%�b`��u���[�@����'��nJ��o�	�����q����v�c����Yn��d����'
�	�Y�<<�Wa��0��w�t����
#��4Gr �D�r�2t�2�����(:�5�MHX��G��*����A3���=U�|��������D��9�*�P�������dN//3d�&���P)������C�EnE����2��WQ1��y��L�G1���o
4�����~�!��]XG�)oa(���~���"�F[�EqB�N��>��;Ca��j�_�n��x�gc��1R�Mb�l��>lX��:=D}�?8���*�>�t~p��6%���������4W��
^G�� �4:3��0��A���
�g����@
 o-TG!�0BE�
���8�.����$vv�yo�p0K8�E�*���������?����!��(�0]�@��"���2����/#	Qr�������8�l%L�N�-$Y�w)�,�z��7���Byk��NXn��r����"r���B/��������q���U�S��dqXk�P$���5Ds��N�B������H7�v�=�Yc��[6��u�v �`)eXQ�z.�"&���jb�>-�_�pPu6��w2l��zN��E���NX�/=���F��@����xq,��v8 ��Q�c1����T�%E������]`9>�YWqk��[k�~����&JJ��M*6�����A��x�E��7��Mlas%�|q�n�F���
��=�HEL�:Xab���1S�6�Y��J������A����7iP�����Z���Q�p�|T�w���;����Km���X�����k��p�Y*�w���Y���wTy~d�laC��lt3��+_�?bz�9�}L�{��Z�d&C=D,�(G��E������x�8T���]ek�PFZ�W^�����8�>��� M3�T�����dRb��)������0
/�[��a��?~2"kSGn		S��w<�����M��*rfF/����S�V��F7��+�Y�u�U%�����y{���
��Lj��;�;W��E/�.�4t=�U)�+]����������B�
�iyy.���:4����66Eh������V�����`�}-ow����W�P�o��/6N�s%>&M&�FN��$"��:ND�@���6���n-i��T�����(�lE�R2�}���m�7`.-�Wm#��G]�>�����t�^1�������X�>H��0
�u:���y]E�gpEeO�4��n@�b�����^.��3d�\�[��uT������ec��x��C~��T��������[����/�!>����b�'��
�$�w���2o�8�Q�D�VX�NV���`0���O>q�gs�����w�J ������c3�*�#����uT-�}��x�x[��6to����3Gf��RF���-\������5��m]�$���-�)r�������D�d
lT\�z�% d>��21��Nvi�����uVH6�qZ?{wr���
6N�[{���&���?���T�$�zrJ�G��%�d�'i!-k8�wK
�����
������]��� 	�4EHEP�e����C�����)E��rI'���e2B�[�������h>8�(rc
}�O�I4�S��n��}��H�v��&�7f�a������n1�	0����/��k���������./���
Y��F��d��H]��j�\'pR����/�3u���e'����X"�����%-�mS����/Bz�Qz���rao��E1~Z���������&V�j%���H��������m�O��Fmr�y���q0(�c�����z�UuPM�T���~7Tz ��H�?Z#��
ry4S=2l�x&)��f��j=�_��WFIpP�N�����h2��y6����:l�������#�r���+I+��2S��}���"a8v����6��2����t��:�]�H��#cU=�����,8�~�R����J��;8��Wae�r�e}���A(�W*��\.��n����,T�&
D77)�o�3Rr����O���&>#l�ua�pE�}Q����T�h�S�ej67u	���P!�����WJR�n��P5]q�Q�n��P��!���=�)���j��I�)T�e�Bu��������2u>��C�9�L=��4=�"f�C�9x�;�d��O��v���3E����0�s�c��V�V6(��@�p����.}H���tZ(�����e��R��t�=�LW�"^>Etx����o�^�����L(}
���6����[�%����+�A:������N���]�!�v��\�
)��������0��"/z�<������m�b}��r�����F�K�")�����+��X�nz�t�9��������SY!��,E���h������r�<p������-.x�S���<Cjvp��c�Y?,��v�K�S�����:���+����"��������cLz��O�5��C}���si�AoXf%
/�a
����B��H�������X�M��'��s�x�%�r����D�����a���ND�����[���G����@�%Z���0��^&�E��A4�|^t(An[������:�/W}GE4t�����We���d�y�;���"�>�G�`���<N$�n��i����G��Am�	&�x�F7���26�@�Q��0�Zcn�����I�4���7z���2����1�6Z�����;��L�]y����2s��������3	���@�XtCn�QCZ�-/�#��H�5Y����$�#�B�V;EmW��/oD��[��|�m�������"	�c�nJ�{����a(L�#N#��Y�CE�����DG#=au�\:�F�����4��H�2����f�R��D�!�&���H)�}�!�P�w���P=��O�{*��a�	�����XU�s��7gZ�z�I���$���� ���Gi�
�x�����y���>2�&�HA��IJ�F$�z�>����{`\�n�Lq:1�?���(��	$B�!�
���.'��f���a��1U*�������O���S�M�HY�NpK����j�'LV>����y������H��i9�e�bSWj�w����V�:���������wQ_�fN�-m�M�V2�k~��]�������|�yad����x
����)H%��������6��*����O=��T���H!�,@�?����a�Z	T����_"�'�����*j	I
�5�0�^US��M�x�C�8�tRM7�tG�$r�=�7�Mb�Q;��H���%d������i�]����id�x,�D#�j��")Ci�J���</�H*�t�2�#R�8��K������������� ��BH�0��6�l���>aUt���>�t�T��,�KNWe�\�t�8�n��U�(]����&~�MDa�g~0�6���3��v���]��~����4
ER����P]���K�<��"�����9.9�9N3Ks\R7�Z�P�=9�*� ��I�������nN��Co��]U��R}�Axx�$��#����n��:�T�(��y�|�Fd�5�}�Y����8�)'8C.1&:q�(
;�13���]+�V��f��
t3���2ShrO��:�=���2��To������l�9�����lZ�!��x��4��v�-�%Y��e������IK��������O�&�0�����m�;�j����m�7Rl���8��Z���oCU�n�w�c_[����1�������!�g�1]���Z��������x�3#zs�
T.G���d��>��k1�1a��-�X��>��=6����G�y�!��rCX�(D�u,�CD��������/����%&J�;��,e���M� �76uo�]6�Q����@U�y��j�l�q�E��z�nm�=�;����7�p�H	P��!����)'�p$~�����F�m#
���~F9������M��yO��I.���.�i]�X���!�Q4��������n���S���$����������R�Z�dr��	�[��������� A' =�x�$Q�O��L�-�h��C

��p2����D"B�h$4��#?���QN�8�3.
i�.���L�S��-������I��1��h��P��Kv��m(�".��D�5�.��>0�K�^
f�d����c��L�y)����:<�d���9�&�����'��e����q�/��Q����L5#���J����~d���~N�.2�*:P�M���f���5��b������"�������
���H���B�J�4M�d������I���Lh�;���v�������8��"u���PCE#L�IY�R����J���2�aLYzGp�)r���7"���F�+�w����(i
�n���4�%�j���z`��L^:�q�z� �����H���I�F������V������)�eek�#3F�����V��y1i�K�xP��a�L
��e�gF3�_1�'��d�I0��a�N"n���H�;�5>���m:�&�����Y���pv�S.�v�k
z��6�p��"QB		r 2#�AA��x���z~Y�c�L%�d����*���L���{V�*�*cn�g�V�8�2��z$�t��Q)�2y����{�w������
�S������-O��>��`�o���]�����mx�Rg.���Lj��D��S�g�g ZAL�S5�q����O�sL�z���MC����Fl�u�\-��0�'*�B�{��bJ�j�e������5���yR�� �f��f�_%p�tC�R�F���TF�w���?���B�6,�B�3�Q������#I������X��LK�.���l�N���D�W\ATIWeW�3����j��*6�\c�z�����t`���CI(��y����*�H���l���l��0P����T(�S3�/��������l��d��S����0~<���W�Q�*Z�#�HW�&:�e�4����NT�������E����l�:%Oj7a�Q��"�@k�F����x���^	1��EY/����H����xK����HtaOK\��p����r�J���]L�W����5h��>H�m�#��x>�����G.M<*����q���'��t����n�~p���
�����!��$Q(;�(�H�0�"����
����`t���u�a�P�B�j!�����|���%@s��{���!�uL�%�,r����N�J������+H�y��D$v���f�K�[����d��4v�l��4S2=����S���Gu��^�/�H�7l��l�����l���R���j~�hh���|;���5-�9&�X+�iN!ln4C��~��
����$���'��@�oa�
8�\����'g�dYNNQ��W&8j�5a��:R�}-��rs@�9�d�K��R?�iu���X�0(�S;��"�����wAl���K��X<�p��~XQy���I�L:;:i��(Q;K�]Ic\Bu:��	�+YC4Dob��%��qFL-��J���)"���/����I1��`��V��=��p�8�o����m��)��m����m��%�m��?���&v�a��b�4u��q7pSPM��S:a�(������l�i��,S��JE����kMi����Veb�T�����O����0�<���������Y���������wR"|����5I�;,?�����O�^��Y�\�.�p0K9�
�
��V���5�Y����m`"���9@��'���(�����c��$V�p
���W/'U�:CE��vN��
P���y$D�w�����:?/��C��[r��P-���7���t5�p:;�(���`�����9�����g���e��*�^���Sbnqr&k�����E�����oU1�x��~l�����y}n�$7Ia���3��4�>q�&.�'��vt$����\S��/��wM�I�	�1=�`���������1���n^�P�jA�6YY��XM39����j)e|�tYp���
�9Ba�<���"	���Y�^��vZ{�����A����}!pk��t�N\]DB��P�$�$E~��*��AT1�89��N�:C�l�i�
��S���i�QL�(H����z�:�[��%/^P�(3m�)`����Qh[�k����)�7�2R]����"�����LK�r����#�zT����5-
��(
�?R������!9]d.���y�x��^y��	 ��.��Iv!�A��F&L�y=�p�����$]��+9���L�`�<�}f
8������3��c�u�,"��8�������vOmo�S����#[�����qS5�xioL���|��I�,�'����'g��E��I[��W�<�"�}����k��)TWK���Ag�D��	y:;�D�h����p���Y��:�mS>�5�L�]p`L
�5$�o�y��bL��D���=�R!l�{�$JQk������:)�!�R��^�2�����o�e���!Bu�����2f��D/�dO���Y���"c�0����}���l-�O�hI=(P�7W 	iydw�@�v�x��i�&���c�!�O�����I(\�0����[f��SB�F1�.
HF%�7�D�-J10��c���%s]2W��}tYJ��$��2���^��|2����V'I�6�	��aB�0n�[x�t�2
D������V�s��l
z����g1B��������a�s�] ����T��A)���J����`|R��_���f���dN���T}��uyIY�
E�&���K��8����a�k����O)i�C9~#����:���6Z�]O��:���q�'�����'��.���irg��66�y�i���"�����������4�>)�]���t�a�,#T�HhJ�h-�6!�oV�+.c���.�CW�}R�|#�h��M��S��N�I�ul�����D�!�R���8i�����+)�iq��I#�	�3::�&����>�7����R�sf����D��� �6�-�D�5*�X�$��IvOh���R=5�����P:�������O�r���&�R���	�aY��!(���>H^I�H��:	Hzkr��g�}kO�>�s����l
�����?)��*���cy)��5�2��^��� �)Jb:���Br�����J��@Y�&U�,K�Ne*�Q�UFWe=���;7�4�K���4��Z�����������`��\_�.���������\�2�Oi���$i�.�FzRy�e����C�1��^��j�[�e��'�nSz5�]����`���V�N>��g�*��!���&V %j�M"E�,�"��0*?�SrX��d4��=�f�������5J:L�� �n)S���)%�1Bb�E�x�.]h���kKj��Tm�o�{����<�-Y"#�[<f��vV[f%8,���)�����+F�cvC�J��\M��j��<g�Sc��o�g�6_�BZ�+�G��=���F����Nd�qU�Zfi:}[���s*n��2o=��3bDo������Q���<������,��M��0�
�W������5|�T���<��*�</�~+n���������������#c��Br���+���9	�=����Y1SN����2B4������\���<O��i�7�C���upr|\?h&�d>�0��s��'O��L6�P6b9WL�rs82�^��{~��5(�y������G�� �I�Ri;N�(�S������~�����C������)G�Rt����YF	i8H�m�"������:���������f�L��#[��Q[Z ���-7<���!I��Z�6yH�fWY����x���S	�qDbAcvP��S�mu��XO������o2��GvY<�[�.�["?�.����?-|��+���8���X���1��b�����������fl!�>I�c����N�,�z�	�d�{��0����x��Us:>S�6kZ"��]��q�!�]K_6cm���4��O�u��2���]%E�w��h�Y�[�E��$L�U&b6lqS�����^�����1z�Qx-}_�Q��4���|@���|���i�<}�4����)L��/���D}V�(���~�]1��Ff��I����I\_�r�L8��"�wa�B��:-�V�Q9�]Q�?,�.E��6L��+�y��Ny�c�V����|�3�z����O��������HNDa�iTN�8���z��14=�:�^�/�NV����	o���P!'�
�vr���J�vj�M^��eG�j�n?PF�\^wp|����x�������ex����"
0Yf�����Z������,�D�X�$�pK�T�Q�����
�5�y���4��

0�-n�4 uc85�m�LbY��2g�a\E����q�^	��>`9�p�:WJ��?�e"�'���:���N�����%����9�!����d�/l:����y�Ra�4���A�n6��f�K�`F�9N�V���Rl���7�u��	5qn#��O��$��(�r�7	=�*�4����:`(k1]��1�
�!0�,Mb��� ����}�PK�Bz�8�jz�C*��<��5���p�V"��iFK 
�U�K����[���-�f�P>b/I*�W�"�_k���������:�v����>�T������pB^dR�H�S�;p�'gi���p��<]L��:�cg<�C�>.p�jz �n�7��21�����!�L�����C�0(�'h�B
`^�E���{�W��P���[?�|aDkkwgo*�����,���1��R��tj�������������Y�,=��4��F��f��wgk�;4+��;���L�����y4�>�_�2��w8n��Q;��q�s|W�8�;�����������HmM���Yp�{�h�`�9`�kd���a�
5
���0��4���y�YW�f%����Z-�^�2������r��2���r�A�7����/RY�=d
^���)�����P�>��3\�tV>�_?��m�Z b[:���D�^��y�')+W.���B>���:}������Y�'�X�����B��.Z��J�A�����]�,�B��A�)��*dH�l�k��D�+@VL<�(!��p(R�,���a�H��`�
���������E�H���A��uF�����R\�	�36�E��P��vD�}z�� �=z
�������E�
��A�Ib���s)���)w�$%1�X�I�R'-DC*�bL,Z��L�G��!��s�j����P�2��9�H�!`��.I�oX��B�*��	]���,��(����9�x��z��::[�~�KS3z���^lQ���yc�#D)6f�L�o�we���)':�z��V�p�[�������u�Z��f��S��/������z�������>�}�k���$"���|k��rG/�L��/�����C�zB*y��n#K��!U��B�"�����������\�z%T3-^�\�-���#�Bo�0^�%����)�x6
_��i��\f�{�<����{��A�{x\� ��
���*|)��<�o���?D/i2��)�l�28��*P�z��[��I'�����h���q��"�x��y�M{��� �|����N~<������m9?��K��wl?�sS�Z���Ana/jS��]��5i���9o�����E`M�k-7VJvbb�2o��-d����Q�HY"� �������e�a������VfH<sK���=�J�P���Z�Y��?�#�����O(�4�5�l���"�7<��izZ��G���dN�yj3|��J�C�QX0:X�mRj#����<�PR �>�<�T7�M��mv%fQ����(~��y�����X|8E�������*!\���Y	��=M�>L �YC:��Tbf�V�cH�)#N���U00����Tn�`�J��`N.�>����^����[�V�W-+����?���B�����?}{��ac�Qv��[M��I6#u���|�BR��.]��;~�z�w?�0l",;R\�1�P-�^R�$�E����a���S�M��|���A�)01!�Q�IRuN���w:���rI��$G�h;��4+�00"u���9��1�!$R|���0�AF�i��G*|D�U[�!=�ej��@��x|����q����(
�kEA����7��Z����}��Hc�x<�>|j�����Z����0�
>a�4��u08�9�������j�vz�\%�)��c�P	:���ED�}@Y�����@pc�Qa��'��,��v\��A����JY�)��7�
:��x7��>�tf�e���^�c�"����l}�aN%ZG=��d�TR�8��J����K'�K����4Ap�|)3��/�����;�=��*;��������N�[,�z��jw���/������������nll�����&6�K��w����*�Gf�k��F����Z�k�\>�u}��v#����*S�q%[dbB�����
2���V��wp���[(o��a�"��r��!�5�A��h8�B^���o4A���������21$���
eV0�Q���v�/�\:�Kl����.^�p��@��.��T�
����1���.��.l����:M�����f����������;�VK�v�]��h��F��u�hh]������O�.���E�=JX������B���}�b���+8.�h�e(�u��e/�g��9���* 7�N�S-w�;�R�h������]�g����I�[���[����!?�Q
�D�$qn]�u�a��d���eg�?���,��0�5��W���#����o�
u"�^�ft8����!��7�Ty�F����B�e�����e���@������~��5�����q�~\;>��3g�s�.p��fK[�j�.��{��_������io(���D��%g"F:B�:��JI�x/���]�����n�X�l����H��^�v���,C�t�L��>	�(�QD��wL<�=��?89��u~P;����5�G�9���f�[�.,qLd��q�H�Jm��<H��"(d����Q�"H���%����5����U��{�V{�Gu������L]�HCY�)D��[��O^`*3�
[J�{��e�xZ�1a����M\��vLH4���2!�������I���x�n��w��vvAL������� 5N���,,0� TK�����8%�`�<�"!u��u&�?�5�|H]z��t����]����(m�u�3���9���*�-�,%&K����{%lm���v��[,�����V*Vg��6��ZM�.g�����I���X$��|-�P8���W��NZ(*0q���=�)��,���`�C;�&�*���~{���6,�N����Vv���R){��ZK�[VI��tr�����8p�����)����>]�����P�g�r@�����#��C�rw�q�P���*���Dx8%�>U�,R�?��5Vn��~��H%�@�j��x-HH@�{��Z]��l8�m����R�W_����-�����oI�����H�����x<��!C�U^�B*�<�5��H�Z7V�F9��J�a
`�{|��G{|�"�'��JKs��T3�p4����L*��R2��'���'?6�!��W���p��4C�Y�V�B���	���{=�<�W��b�d��xD�;�.��`U��2m%���������Ve{��{e�Tfh�&r�z�1�^�
M��F���Z���'������U>VVv�8$������$!��|���!�.?�U9�H����C*�G��������1��vv� D��p��o���_�K�mx� Sh
 ��e�2$��7�)���dF'T�I�h(ocx,��$����*�ro'��V��\b��e����1���JE����?��h�vc"`9*TC&�����������~���e�Z�i5:���c4��b3������M����C��\>XKp��Q���F����c��i^���|���-�K!4���j
g���8��������������dy���`��rfK(rl{*\%Uf~A|���?~B�0p�#�g#6K�c���o�a��5_j�p	��7�B����Fbf�$CBwc�(M������d��!��'��R�$a��N�2�$vK�N����N8Id��<Id�$�}�������G�g����_$9��:�rnp���F�t��r0D�0�3MWA�a�9�L����'�L����]�BZ��l�dRC2`?�_(�n��X�T����S����|B���h���#����v�z�q��v�8l����0S�)6��"���M�3g�(bw<�����I��l��W*���~�X�;��^u��v�����I��vJ�t��Ob��$������
/z�X�����)M�wqZ����_��a������@Y����;�)
�����mk���[��9�/�,�pB�Y����gFd5��~�$�����s��l}t���A�;��N1�����e_,���`;������{OX�~��F�6�F����7K��l{O��y F�/�D�0p���Q
��oD�R�,�F���[�
i�Z��0c-�H�Y������f!���!c�A�G@~�
���kMr�5D�#�pd��#h�x$3����-�O�Q��-����x��f��H{
�{����
�@�5�E_�.�'x5	���b�e��J��Z��gj>��k�sR��N������Q��}�EI.����z3�2�6 �����W������7��O��������'�c,��]qr)��E�����t���_�r��<�s~���
�������e��G2��?.����W���&q�	X��(�|��H!�A�9)�7v�������7&��*��LD�&���B�o�8e4��hU��c�n�.��h�n�Zjcg�\^w����0pw�da������]^w��%���,��p���pw�4w���S:w�����������je~�E+z�?�GO�i�<4k��5:f������*�Z���������#�{<��Y)�G��;��.�#�d9A��0�<i�	{U������e��<��p�=r.�:���~/I'�&k�:�.��-��]8��N���:�����da.�������s���3�������Oj�lp��B���Q�����E��uKp����h��v��g'���CAN����]����\�K�m�/2c��G(
��M���e�^e�g�lm�e\(,FkqN����^��n�fP��q��w��.=���=���g-������Q-��&6�v���b�
�����������J�_�gu��y�R=��*�w���jl��)��~tZ?;���p�m]�����A��5�J���vu�-y:�Xk~j�"�bY����k��?����#���%�n
Y�����6'���������RFd��~i�.�����N��ul;��,���J�+�:����tgl����@��4"�v++Pyp��=�����:F��ldM���m[`6��;��s�
��}�5e�f���jtWE9)�����k�K.�2���_���
�^^Y��
�b�V��������n^����|��B���,��4������k��k���O)�'"J��T���r��)k/������N����[;�}k����5mM�cO�!Qi�n>��B�lp+���
(5����t�o����Wy}
m��TKPx[.��qk�X�uIB`����������M������,��I��r=��+�,X>��kqx�Z5�z�=�����u�~#!I�-�PK���0�5iv����i(�}i� ~�^��������������o��*���������c�E�;>��f��|���������U����7��[�c�%����oS�����[��D����������\-���Q��1*���G"�HA�J�x�@�H{i�Jn]i�R��3��F���hB	�	���U������c�\���i&��Z
���R$����{�-B�K�B�B@!Y���,F�p���<;��5��*�5W9
_�����J>����������h��w��
���<}�����:F��;�^E
�����m3�9���upm����E��>�NNG�����(:��N7���f������x
fO����'`�v�d�Z��PBb��Ne�R-U�������rwo�j�&`yJK)X�R����B��u��Tn�@��]��~���m�A��~��Z�
��[�b����w��@��p���&A�������
���U��j<�y@@�#C-��(R�<�W`�`O����L�Nn 5?{!Z�
���G�_;|��&SgF�z�|�N�?Z�-O�*�z���������e�@�g���tw���F���z�zh�����%c��Q��
�mD�BO�w}�����������.���������,7b��nj����YB���j[[���^�
����ni{����:��	�/,D�Z%|eU�Z�s;��b���3��jg��--���
ao�������H���t���e���O�_A4�!�NE��vR�akB���Jt���S
���q��@q�+�;A`F��kUYA��
����r��u��V��=E��P�`6�����Q����{������H[���2�%�ca#������$�>��0s�j����C�6M������=�moU:�bqo���m�v�j�Mi(�ES
�Xb�1�LSD	XM�K+��A���{�T.9����W�m�|tgS/�)������(#���VK*�8��34x�F��XYF�g�v�h�0}����J�}2�����`O�e02mLu�Y��+�+y�c���'���	�!�~�x���l��'M=C�����6*[d����.�V�?��������H��P�T�3�[�.Fo���NO ����V�\��PB�D
n	)�
G������[H���by�X^]�����������[��m�e�T������S��te�nB�"����,���������	���v��?��Pbw�T)W�T�T*;�?���3��1�L�O��Kws�]n��o��;��e�u����m{$�������cUw�*��}���������=�]Q*����
��*6�J�0��%�,~���|�r��u�����U�p�+l� �UQyP���������=�Q�/�V��$c��<�5>����r���2L����x��������Mi��J����t
�����_����^�����E���^X���mo�>��7l�Wb����c�������������/b��U�(a
��`(���j���g��b��u�b�b;m��3xvrq*~����xG`a��UV����������%	��i:�rwH;��I����Gn[|po��x�,�����	etxp���`!�M�[��!� � �s6�PdfF����H?Q�{$q���+�]t�������lo��u�(�aj4�arb�,R.g�+q��*;������x*a�wX|Zw|�@��wZ0`j]��\�h��$�c��6��fS~	fc��2�
C@S�0���Z�2���8#�8�.3�^9:���XK�U��	���W���@�J��H���~wenW��|����@��0j��zc��9>���O���d���N��������F�����UX�����:�
~K��Yy<U�����F�jd(�IF��7������y����Ha��((�(�O^m�O@d(�S�\��j�kB�9����r�2���W^��)�q�=3������*e��t�p[��������z<%h<��m�L
i,3�y����}gf��P�)�1 �f��"b%:�������	��|����(<���97�*�r�F��V��X$JX��7������=��B������f94yO�����G����|V9+N��
/�.�.�c�S�1����g8����h������0�}�����jvJ�}���|Eh��M�j|4N�1H��(oE��(��*��Y��������4�c{��H�����^�uV��^�#-�����BZdpn�Ej�1�2�����T�b��e�W:��������,�������[@�.��T����#� ��y{���������g�OK��u����+���V�X���wK �o���hiB�6��v�X����A
`�3��t%0&[p�����!@6�qC.��'�fVL����?�F�KO������_M�	A�%4�B�������*@����:q���}'�.~�^h��#8����dE�Er.���������3HW�$�%7y���Uo��i�(�p�|���#M��o���gck�
�2&��
�Ut��: ���;�e=e.�f:����r��O�Z�#��[����z������n�U)����4CyZ�d  !�H�m�*����|<�u$�uv]��V�}����1~s����Q_h�gH1��&���Z�&���1��?V�a��}�~�?��_��~Z<�y#j15�y)<Q�9�ON�x�?��I&��4e�X����fzA�g���+��]^�h65m��J�rR�E�{~���H����>�"�j\��P�H��k6������	#��J�G.B�q���A����2���c��ej��+/�����~��0�8nn�4�4+b��������X&����V��
�����=W�����{�'��pA�g
����iL4��)�+y6}G�
�:�|N9Ee�)�W���x%T�z\�!���P��e��Eo��_���[� jH�{2�l����'F=�]UF6�D����GB�"o�������p���"���ty���<y��BaV�F���%8bx8�[���X�T�������.���?�
���H�M�J�N�s4t��:Sit(.>�����<��Y�NMcu�C���L[)�����oC����(U���Uk���]�&y4�1��s��o�r�
�:�
��e�4��;
cK��#�XI0��6��9���l���>.�:J��~QOhE���;yl����N�f�7��H<Yc����u��C��g��+�9�x04�Vx?����8)���0B&a�]E"c/T6	l����93��ZA��Zk�D[c�������q.|H����*f����	���5#=F�L�,$�MV���YV�����G����	�l
7����C��d���}����$�_
�e�C��"��FA,D���#,I����DE�NS��Yos���T*^�Ojc?�")�\z�����Gt��<�N���>e5��tZ��������a���R�������}WR�%��{J�h���%��.�������p2���_*W\�dp���,cp��!R}�a-�QT���n�����r�R�n��C3��4&����Hc$N��
P�G�}k��V-@��be�p/����i>��2Y�� �+Y2�L �Z�;�&�yS�x���P4�Z�Qr��dx�LJ�L��5���3��rAG���*<}��6�����&R�1N����u�SM�����������?�����U%:�1��tF3�8-
	i���"�5�����M�)�FN�JR�*PO��;[�����()�2�"bI'�UZ.6�=/�!��9�_F���-oV�)�&�Y1!����6�l�<�<���j���N.�����*��+�;����1��^��|���������I-��I����q=1r�I��S�(������Y� �^�&�V��>uj�yb��ybj�<�1�*}��MV�jrrxy�����1.l������K��|��|��>?i�=N��~���a�YO{�l|�|����7�����7kO'�Lo4��j��f�����W?�b���q��V��������������)��L����)�.S�yr��yS�y���LQoR�N�x��)�y����tL�U�1�|��h&�`�4L��Y��20���
�,L���`
<�������I�Vd���k:?�<���������]]@;���v�du�����e�nU��,����&\&��PX�#_&�[�$-����<����+��4A�'iaMj%�8��Q���v�:��������z��sQ_Y����F��4�n�9�1���V���I�DC��:?m��T�Kv��&#fWK:b��0���H��&%&�f��d�f�LaG��K�2�S2�Ct[
����?c\�k�2���k���)�!�A���e����4yi0M�'XRM�C>Fct~�j^Oo;}mNB�&�y��� ���Z���k�P�I�x���a5����a�*���M����5Z�q\
Vr ���y�����f�G�bp��&h�i���b��e)�/�R������WP�E��b�^4D���~=��!����$�E����]��C�6^8U���?��G�Z��>��f���ka����x1�.�q��T�/GL9b��wYx��*H\�T���$�����/hXn����S�3�#���z86?����9_�K�#f�cS�#�+(V��tw1���g�aW2���������v�|MyM����a C��.W�t�����A���JA�<L+H���By�]���=}�{����V�SM]���6<�\�V�+�����3l��d�)%p�%h���<��c�3��������D^����h�,36�����b�H��
�3�7����=�V����(�G�U~�/MX����e2�e"�� �>8�>��*1���.�>B8���d����+:��iN{<�4j���'��)J�,���xRQ/�:��hd���Y8,����r�<��C�,�1�tZ�y[du$��2_PQ|�E��I��M������m;ko����# ���x�xJiX�����0?b�2�.s�=��D%�y�3�{62������EQ���tPj�e^�iZl�?��S�fRws���9�r��(�c�+i${M'5/{r���(n$�e������X3R~
.�)����Yc��Gj�lI�Z�=i����%I=��bh��rFr�q*4�����C"Lx���3J&�����zOg�LB����,~m����.����I�����N��id^�CdV6�6���,,�G��(�
�pe�}����W�0)�q�1t<��H"*r@P��
�T�#�y�<;#,������u
f/��F(������� -i_��Pc�0~������HGJ��
��s<���"_5�w��8!�?��H�7�*]K�A3Eb�P��U��z��1����	�t���"�>4�(�^�3�}%��B���P��;N7QL��7��X�Z�3�D�����Gc��iq�>���p_
�CI�5[���f�X8�t����\������eD���I�����g<����bh�����%�������cS��0sR���om�pMX���2��,�K�`�{�7��O����@�n,P��i�l���2+/z��)���t���Gm���3��'�'l��)<]��_�k�vp!�<�g��&�U�
�����X�nV�Tii?�Y<y���>�4}�?$�EK-������35������t>��j4���_��/���k�����
/"��+	�$fE�x���4�S���a�����1���^\�N.�[2��^��e��,m��Y�[H����
(�4,�u9�fG��H_�5����w��%Yx'���E~��riK^�/�2�O��#c)E���+C���F����w��=Y�T�dr	������R���*��f=C������K��j�+�B.�������2����To�
7����y���r���0����B���WK��jb/c�&S�L8���8(����o��=V2/�*�!���~�q1A��bw����L���&��8~��.?���x��	r��	�X��fJ��#~0t�@��`25��������)�Y��S-�<���9@�X������-�+�%�a��N�2B�A�`.q�=dt$2��|�<aFV�d�:*�E
��N�];3�+��e|��a��J������T~����$e��$��J��1���g'�Y����}�S�<t��
O��
�����V�s(���Jn�B��S�aKm��*�Hs�h�g$��cg ��:.A�5�W:�`+O	�l-��d���G6�&������
����6�I�0i$t<�8���$S(�8�WZ�����70�������CI8Z���Hye}]�j�(D-�Z�'�����Fw��a��Y�L�R��9��l��}u����pE�D#we%*b��(��
G�%v�>�a��K��%�����*��{E�����'���J�A	���k;���\n�
���(I~1K���R>I�2$�T��Q��.!�VV�����BI]-��f�Y�4�#v�i�3T�����Dl��l=�����>XQ�tmg,3�%mOi18�u���W��|z�t��ty����jg�$�����!<�0�xN^#��c���Dm��qd��_����_�M��+^�����
�m/������
2��%&��H�@�x��f���s��C����������iD����xK�9h/O[����+��"�	���JLJ�E([�k�h�U��h�EM�C�bQ[(�}�"�����$�)��6c���"b�AL�(�DG�%H�����y���,�A?e��1�BC�.��!n%������KY��2:q�q?����g���Xn�3��&������$@B�]>^��q
S=%�����9^_��,�[s{�Nh��}�����w�0���c'��E�0�P`�;j\0a;PKI��'wLh!8e����������"�Z�*T9
�C�JDpr����%z���9���JB��&�S�Fe����*hE���2�8�p�)�*{
��MnJ
��J���.HL0�#���7��n<�;;�Dv��
��u��<�)7�.��"���J���Nd� ���+��v���K� .a4��L����J<���������z���;	
$�TL�X8{����o���d���@��px�0����V�4L������8�oFP�<���A)\��������3f:c�v��l��z�=��%��/��C��s�����
���B�^�+���t-z6��,�1��N��d�H�"�(����|Z�4�j�M]�{�i��Y%4Ii�5�������Z~"���n[����m������z��T����t6A0����p����2���^�����.FW�sVqrZ?�5O�r�����_���Hg1�*�����P��?.C�;K�����fl�EM�)<�����8.$9���c~N������dF�,E�PTy<q%�[�a��lR��y����mG4k��Z���H�,���
�����C�1�����J<�iT�(4��QdK��*{g�"�(j'����V:�W�7N�//udF1T����q��o*�g�������C�6��Y?����~|xw��x@��4	"?�3�e���x��
�<ljqT��7Q���K��7���F�?���uYo����5?
�<g2������?6w�`��5!��_��fM�k
��������%�NJ���k�L��^��1z��;\E��w�1��lgu����a^3O~�S�����@����p�B������L�W��[qG-��&w�NL����]�\�����.9u�FZZ�%���R���I�qF@�W�����#[�.�[Y�aQ�uQ ���*1+�d��5�_�b�U�e�;����9��t����8���'���"��!�OkR)�P��
��O�Fm�|3g
h�`lv�m���l���ZC8���Hy�3�k� ���9�7FR�ra���l�1��e0�5��k�M���7��Y����N�@�r��$���%S�y[�g_�I�.3z�BAS� �#8��C�y�!x�sX���I+�%��P�&������D����`~�L��1�Ri��7�H��#U\vw�)��|���&�6�+kh�&/J�v|��7���&�`���1�Fd�Qi����S����j*�s��A6LS���/C��@������l���Y�-�vA��v�
[�Q�)���ctC���E��H����Qf�������ZU*7(��0���B4�*��n��H�C'Tv`
i�<��	���B�8MW��(��&W�w�`*�f��-��QC�z��r`S�h}��V���E�eB7�K1�ed�>7-�����Ha����ku������>1�V�/�_`�1�h������1jM���V@��
D�1GO���1�3T�i�EVK�\�h	9�;��[*��(���a���K�M�Q�pL6n�j�MF%o����������1��]��F�
��l���y��x����B�7Wv�����F��Q���!���Jd��
GJL��]1�j�9���i����v3"��t��f�ND��������bKO�y4�iFf�2P�`,8�|T�
um��uX�@���0]'At=��":S��G��I<��� �byv�C������F�N����7/����Qc��"|��BJyND'p�-&�'�
c��7�^'��.Rm�o3pPKc��M���i��,���4T�@QW�����&*fydu��j�2���#�5CB��M�-J�I��I9L�����~j4?�@��EnmEj��8��5<q��f�vNnV��I��(9������E����uU�02j���)}�0cu)5�[�q��P���4[���
�>d6�Ll�����G����uA~�,��5�� ��������U7��/��G�Db��M�L�������SL���H��#8i������wC���jI����L��`I��3�)���N�'M�Ha�k
v�/NYprb����%�f@R��HfB�0�Ynqq��K=�t�T���XS��J��=o��Bf�H?Kz	�c��)E#�\���%8�_�]?��o'�t<�P��8���6���	ka
xD�I;m���:�'�
�~�F	;�������|����������s��c�a�;9E��TY�G�oO��b�TyNg����O������Bz�FZb�eUF��B�X�'r�g@�zJ�7<��2��<�MSnN�/�6U��rTL:z�w�����	$r����v@������2���������mZL��t�������[?ZO�)��������|�a�]Y
��X�����b�B)��A�*��.�����L��Aqv�����%�+�&St�����W�nRR��J%G1����JHRn"?&%6�[*���"��A�R��U-WhUy�R���M�>���~�d�2WR��� ��v!`^����N4)�D�3g	��=��n���+����} ��(5����3o�U:blB�V�hblq����� �^��f�M��#%���#3t-�f�;
(�.�f~J������LN���B������T/�"����)6@��9y��a�I�M$�C����p���DT��� �[���a����7��'������z�w/���+��7�](��HN��������`SA�\�����e�=2�K��(0�l%=#X�7QT�i����4��C�M �n@�X��!��Dw��M����r8%e��X #��
QH��@^h�!��lhy��
�_x��ms`08�"����?�l�6�j�b���x��c��\�e�i�����H��4
em�,mhb�����Pi1��
��vM#��:�=PY�i�"�kP7TU�����f�����~�|�)�(��� ��W��D&������%yN'��le}CdcaM�)9i��P6
�Rc�hD:3���
���u�����S������@8?�m|��7��d���_�1j;*bJ�N4�upkF���p�TE���j�����p��

��`��Y��mE���{n������7�d���N��{,�1)t�I������V�@���2-eZ��!`���S�������7���X�����,���)������'���*2Y�K�[P���	���!��D�����A
����S����/��d#Q2��=0�:6G�4�>Vo���Q��c��X/RE{��q(&m{�5�u��T�C!!�:D��h��a�:0�40"Z!�Z\"E���S/�WN���msT8WS��f�t��6�����!�����qk#�}3�r!�0lC�g<�jE���UT�����DD��h`��wb$��n�G-��{���@���2�*e8]a0isz�a�����L�p=q-�V�{qk^�l�B�H�2�N
g�[L&�Y�� ��l�w,u�z�J���
����������s;t�cr��1�y��w�='cI�,?�,?Gp���%.���??�[^�i;��(n7���
�Wq\�Kg��J:�g�~z"i������p]�}��Y����������1��y'�O�����*Y�l�b����L�;G�L/6�u [�hA:���qAj1�)M���!3E5�!�D�������*�E_B:'D��S��2��R��E{�w?�Vy��a�����K�c.Y��+(Zo��]|C��G���)o�G���B�u�!�G���-kt{���@�}���wa��n�H&�r:������j�~=mc��P���3�v�Z��fbgSve�)��
��N4t�{&���b9�]i(K ��i�4�����J���M�_�B�Qb�!H�7$us�G���f���n��-�*3o������B��(}����{�y
�Z�(�iV�W>���S�KC�#�:+)0�t�Z�X�pkXg5\��!��V-_����$1-�@"��|�P������(���^lz��2�C�C��#�K��G;3�+�_������]�����`�G�J�/�$�
1�5A	+�<�{�]!2��Q��uV?����ENn������<)C�&�_�	��L��n�/����*�$�W��ke�T��H��r(L>����Y���MIj������^NJ7��+x'C�c�2�3q�e��v�j���	#C���y}���������>�,�8���VB���0<8�����f�h��J!1��PTsh(�Sv����)Q�v�Q�W��_A4�Z-�b����}�����7h]���-�o�$�T
\������Y�~�%1��@_O��P��R&����� d�Y��/�:�\�5�������F��j������iH��Z�4�j�
�u��@�<�y��� >�c�gy��J��S��_��T7��?M���p'&�#%�.�V�L(����aJ�K���f�f����"���#^��aZ�x1�3�$�o���-Q���|xc�c�"�l�.���X�����71T�1u��$�|� ����=]B�vJ�B�"
)��*�#mS,�'�V�����!|��!)���/�=��'-����t��5�d�����r���[�yU�=�������<�"*tC�b!C_��S2~7E1E>SZ|C91���2Y����[��%*Cl�KU)671AKm>����2����+�/72d�L����l}�]�g���
U�2�P2���
�����L!-�L�#%-,�UX�9�nE$�?��?o�uy������=��4C��Z�4�������rZ����e��f������1��9o���F�,�K��'�X��������$X����7��f�A��:*����t�B�
���r�L���[��Hjo?����C���A�D���m{d7�=<(��I^|������n��zk�S]}a3��X(	_�]�������;�Esk?I�Do#24�8n4[������39o�o�,���7-88|����N~<�5S�+��{BY���i6=�������������)��O\�Yg�nLc��6�����M�X���xAS��;W����uO|#���+�v��r��i�t`�.�y�qb��G�:r�/����0z�&
�L�D��r�&��vO�M��Up������kB�U���� >rg������t��a�<	"\�cP l�T�+_J�8\(�������#��;@������;lj�h4�Z��Z���1� ���"����,�f���}m�Q)X�U�p�@�v��M��&B�$\�����I���?����B�C=w�
M��L������	���}��<6��"�	C�N����f������g�{���o�7�\�����6�y���L��4�d����*6�+W�2�����JR����g.�����h.�3L�I�^�n7t�����
�����Wr.0])���.N��m��@n=u�L�	 ���C�rB<��C��R<��C�(�8 v��!g�u��U
sc�v���%L�^a�Es���1>�[MXR
y��p��r)���C2�\!���#�)u;�������7��7���AC�I<�\;�����o5�$��/S�E�����u���A����'��3��F�(��������P����]��!I�����h���1����$����V�0v����F������a�-����+FV�����%�2��zc�w�])eHv)�+�FD���pl>����6 W����}8\2�R?b�l-�����Z$�nn��J��[,����i�a ��;Hqy�zY*�����6aGm��il�#d6��Mg�	��]�����Mu��f���&K;[[�Y��.���$����U(��[���������D�10F�/��n����0�������w(��T��~��U������;;�[;[�;�}{�R�/U�U������GW���(��R��_T`5W��W�i},�������.^���],�_��������^�w�S���P�J�R��W��W�-�Q�/�V��t��J����<� J�������sR?�N��)��x���S0~
����M��M�`�#�_�<��X��m���z6�n�y�!��0�	0����2-,Q�����	�Cx�������8�5k �X�������Ln��]*3����Fx=����*|���m�P6����z�S����������
.�;�q�N+x�P��������5�D%�V���m�Q��m�6�������y	2��r���S��:��������N��]-���N��e"8�z[�����G<�n���t[%���0e��/<}�����<���^"v��Px�����a�������������w ��d�i�n���Qj�k�X�%���Z�n�X7���2�������qv�����n��[����������:�c�hQp9FHL�KJ���^Nkg�tZ�g�WI����S�H����@���N�X�z{���jg7���
1sb1\��.�F���=i�����@��������~�w����h	�a�����"!�����f
z>Z�UM{\�>�e���U�{l��e$�g ���K���H���)Hl���(����)�e��,�m�*����W�V�����#���k�+���Vw��j������0&�U������+���<�i��/#ql]�-�C����Y����W�<��}%Q�����]m$���_�)z8�c	�0����'J��83�dei	�e4#��n��I��o�W���n!a��9�5���z����������Z/v����-���-���2b�����$Mc�/�R�����E�W�v��RH�bj���#�	h�1jaN���tt������'+������
�	
������50#���4I'�q�Kp���r=!�B��G&]B�  ������ZgM����#q@�K5�x��/�O|�	�,-S��|m�-p1�B�n��3�;a�?���q����T��pg
=���IQ���g��g&�[$��MG���E/������(�:������'�2�X��#m$�, ���P,7����GT�b���r�i����db�<{F��^��y���M�S���W�����~��]��\C=p��d;d��"`��
��g�0�C���3W�a��y������o�?�����lAM\A)`���bh��li���H���f�k��-�'_����07���E����3O���V��N�\�����u��8�
1����q���w����!)����h<�Vt������q����8���������:��9��_N�T�1�:�}�c���Mf�����"�Rvf�`�d�6�xF����|�|�V_�)�����,��:T��>��%�K���!����	1���T�^+��ua1�7��`���	�xl���co�+d|I�m�*�;|V_L$�F�	��Z�k5E2F�����]������4!��Jk7����n	L����OS�*������f1�����z��x;���e��u��x��x1"�Y����'`F���c5C5��?9��B�nSy�o����"4�~��t�p#����1L�H�A��)�?����W'��'G��.N���}��������W(2�'���:7i���v��	�i=R4�z���m�k�@-���M������\��.��?=YG�l,�H�.�RZT����h����Q�������^�u��P��������`���-vv[p@��5�;�2PT
�mpmt����������g������
�����a�fi�������������+Y�^]�pry�l������5���\�'�%�E�n/���il����a������,�g��.����
����~-�&�Hnp������o�xa�������<
)�:����Y'�0C+����k�3�<S�Nq)4�����E�Wh��c}3���6��cL*h!+8�8��I^0uyo4L�@-vV���������$d
�����{�h��b7�����������o�7�'�kVS8S�5���GcW]�Ui��wj�d?������JS���Y�sv��yU���p���o��	�p������~��-]bd�z�
<4��|�C����B��:��p�7f����LMG;�����*8f���yq���jV����Q���dCpB�NlU����������T#�iw��m{\o6���-������q��*��}�B��B,�oL���[�rEk��� �8_R�5�I���D�����@b7,#��d����/VYe�:X1���m����2����5{�z��KO������(v����)?{����%�%WR��N�|%Y8��Ng<�8o�����?�;A�lY6` >��S���Ll�3��\>��c���K�!]V�m�� �iJ��\�G��g6�"��C��o��'H���F��8�h�tM���aJ�fx3������$��li�����X�9�97mSV�dw�fj�eX?�9:����+��n5�#��0�!���f�q����?�@�"��I�K��T���%gT���
]||4PC����K�f�XR��U�o]�����N��������)P�L~;
�e4�i3V&�B�ki��N10�I���X��c(O`9A���� rYjY��p=JcK1��n���f�	���"6$Cl)�
��{Z���������h�*mK� M��W#n����q]!��g)��N�b��]����u�z��C��s1@��.�`�_01����n�����h����S!�����[/�7���Ko�m���Z�����I����$���_J#��*������.G�^����1���5��D��N���ba��S���KV��M�yB�pp@��������~�3`H��j���w��
m��*�����"��}m�=�	��NL1�tb+l)i�HR��c�V�V��"8H�-&L�DE����4`����iKx���O ��L����t�&�@J�s�z���-7'T�?�w��h�DR�V�X��}���p�&+�_`]�\*�@� �&�4�y��g+�su�
�6���gn�X����E�c��:����<��T��1����Ynt�d���`�2�%qd����X��e����K��=	���Mz�	��L�TT�)�� C�/��=���IW,���4z�o���(q5�������=�i��x{{�������n�*Z���
�����F
#�K*���&�x0K/.M<�M�q��������U�����xB#�
�8���$���r��P���(�>ct6_�>���x;���o�|C�t��j]b��1-?�>���@w�����4���}p"�l��[���f�]��:�5:��yM�&/8�
%��l��g:'@�JE�FQ���NT��w����h��2J�M����XQ���wd�g�mFG�r�X�I�;�����^w����sw�5I��<�������� ��q!�����
�]���������v��s�F�b�,���]0Qn���-,\��?����o�PX�=�GZ�'��,�vM�H���ww����d�����|	���u7-4�V
&[~��j�b�F�%��x?$ V���N���|3��S�A^uGH���&L.H�a�)<��Z��?0���n���U�"�	w�����8��a'k9��^������\��;�2�b���!�L$R�-�����U�<��x8�#q^�����G��	��V,�����m6�Wsoj�A�����D�����A^J!��O�����0^c�d��u�2qL����ih�h(��JaxYLI���`"3`���"�*@������O���;���#A�>�(��o����r'��BoN� i�������0�_��,+�����R���*{��Kk�{Z0��V�*�K��	��\�<h`�_�`g�_��B�b���183���@���_�jF�UY5��S@���E����*�_�L>wI��%���9��/�����)�ts�mn��kw�nY|���F1b�zw%��;;�W�.��Hx�h2��u���v6�`�c��n�J�����:���a��ss�\�5N�Q���00�3�����N��v���S�����9�	k.f�r?G`���=+s���+nk���> �����n����}���<{|,��CG(���E�5���n���.r����v���;�/"�{��Y%���*�����)O��U�{�e��u�o�f.���),n��t�%�G��~���6��zso����wF���bv��D%>+���h��"�]��{
y����%Q��#�>w��I���,��P�Y��H���e�=�[s�!I��}|�E����J�M���~����m�L��C�F��^6�<�y����A�y���pvt��j�:��Y0������M��~�$c2z�i����g@z�P7���#���tB����e(�W��+����W]p�!��60yMr�dj��V_��V2�����JU�HK)�S��
�
��c�]�@��U�6����c�???-�~����#�>��!3f�zX���/���:����M�[5�ga��b��*��������m��f�������6D�0�r���r��J%*U�CB�e�.�v9�������%����Oi��������������o�.�8�������"���A��
�&?��d���l�� �lL3��,�Y*c���Y�n��]e`��#�yw��E�'��`f�!+�>3+�@x`�����2k&_Br0�L����t�X=�<	g���z�����g�3M��y�*���G{w������RY9�-��2��f� �����Z���8��� ���}R3w�>t�FL���S[�;�l�Z�JI���h�������]��@���5���k(?�M��<���������kp�'��������vm-%=r���Vj-�R��=�"����@Z���ua�����L����&��X��:x��$��������S���&y`qc6G��AF����)sV�t��j����x4������M�lktB�����V��u��c:S�s��>����f�H?D������S�������~J1WL��Uu���'�N����!u�<U�2��eG���z�v�D����U�X�_�bl�?������I�hF���~^���|jX/�N��6����O��,��O2��B�pm���3���F�87G��lf^��X�k�ez���.��v�BFV��������,������L.B��Z=�b7�H5��p���}2��
�@D��BN�-n3�w"d���3��i�y%�ZG�7��F�	�x���Ir�/����"�������G+�X1�"��h�(��o�b��a��:7W�X�s\`�r�f�f��&���I_N���V��05\�y�$��V�h�����zs]���@������`���T�����)B����������]dap�`�_ls00���J4�yQR�A�����g��d�?����)�1��k��Q�I�������R���R
�AX�)Q�tn?";M���?��������g`b�$G���8y���{���1�Q��G�����@��4�7v���v����5|��^����3_����$�Eb�co��
��G�j��NpW(���c<�'=����.�j�k����)�Kh����5��@
0\���������\'�^���\�99}��������hI���c�!���?���e�B��GH�������?���[�"?(�g������?[��#��o�����{�������������N�y����v��z��z_��?d���?�����F�����tT����L1� (]��n�'��3b
�HY��z�����M�����1�D�kDDV��MKsHb�c2Z�I�e�5H�����:��-��,�!�[��$1�9���
������x�a��^6��P��&���'����@�N�,i���ur�V5+��������{�u��k�"3�������7������T��}��]�CA;�:O�a��83���u�|������s84+,��s�p��]��f \�r�[�������`^k3I�����	�����M�z��R�Tz�l4�{)��#�l[R�Pf@u�����4	����t��z���z~���>s�I�B�E��0��� �k\#M��f�����g=q���I=J0�Er���TM���tc\��UC^��p8��@���2 H/9�9nN���<	YT���!��]������g��	��l����$}�l��Q]�l��j��������y0��I����+ �H4&����-:pqp+<�44��V��E��X(��m����
���O�7����F'�9�0s����p*j�3������b:��E�1c��ts64���L��p�\�%q�m��]
w���������������m��N�����m��d�Bp����O-
-�,H���QX��s�1��d�M���Fw�E�3��m4��bs�W��q.��rt�T=��Y��@_[���������k)R�-��w���s�9��(._����B��=�}z7�R�04T����)T�}�uto�/���9#]�7i
��2`TT����O�R�S������(
`��/�����E�	�Es�bW��V���0��B�oEwR�Y��T}�'�Cg�l��!�&R���+S��t�u?�#/B��C�������#��@��n���,��J�YI��e��z��q��^�Eq�]Q1w�����o�v����1w�rI,��w���s�_��#Y�la�-���!�����B�-�?�!'���k�K�G���z43t��v�aw�����
���*le9z�k��4M8���U(���?��2�����q!����S*�������N�yE��RU�j����.M���6e�^r��w���?�9"/A!���'j��NVd
=8�J�p[Q����/�����[�L���`��]�7��Pp�l�������>� ��	��HV��������4�K�j�����9��T��Y
�"�|����_������E{�`6CE����E���'����s����<V�m�����E������`�
 ���e^�����W��`����,�N�a��s�Z�Z�������~)k����"W�Ay�����$q���>�a�k~oq~��D�c�����B+A�l�6���;��N����f9����b��P� @G<O].�+�<��]A3��m-���HV>�6oU��+��hP!m��YhR8��>i�����/#�T�,2��~L��sii4���]��9:x���+CR�B����H�
��G��i�"�B�P��P��/�9����EK�����2(��E���/���g�W���j*,���j�L���K��xb�&Ywz3�N��U�r�T+�Ef*,�{bK��AA�)]O=
�7J&:��L3�%����4! ��� ���r�
�����8��5��B|*�W8�`A#��}��C�a�@���v�y7����@�0y�C��t����?��Gg'?u_��^uO�. 22w�G��]w��O_��������e�NN�\o#���30z��~�N>�	����1�D���s��6J���F����r��|Hz��#\�^u. ���k�O�v�
���HK������#�t�xO��0p�$li@H �m���U���y����`n(�VMd�E_����I��''�r����Y���M���9����F?U�vtb[�D���������f��p�u�O�+g);d�b����~����IY������*��yM�<y��ni+D��1���4C������Mhj�|�(��K�[���G��Es�T6�$1�b���u����)�����i�9��Q����{|����7��fm�K�D��2�� Z)T�4"�D��i2t]�*yk����>f�>���.�_
PK�yW��P�'w�������z���x�`�Up������M�fp�P�x���CI��< ��du����9��Y�{1���N��x�B����?Nl�&S���Pc�ur�(.(�r��m`���z�����N�0���[��i��8�#z=)�d�e$���'7��h6��E�,{�7M�����4��DRa����
'_� �j+]+������'�
$�zQ9�hc;8s8h�b�?�?����������g�s���vM;�Wo�
��[g#����my�1�tc�������B�p��Q��=�\��d�E�r��d&i��nK�F]2ho�����W���J�����j������������a�����A��_�(B�c����f���:���\=2�W���0K�!��,���6&-l����J����?z��������o�tp�d������J�v���@P����6����o�)aK�����Y���\���KCfM�>�Wd����5�:6C��Oh���e��QS�4������aW|j����9B�&s�\�?��w(���~��x�A����;hh9$[��f�\�%�ch��7���K�oZ�
�PJ��A�'�8�S�MJ���c����I��<����<��A};On�G�^@�
�r�">u�wQ���{s+��^���������o��������9�d�5,e��O@T%�7�������J��xjT*���+9.DM�x��9��TH5����������mD��h���V�u�t
�#2#��3�o���������\������������������#������*
��?�����<9E;5M�(�2 jN)`c�����DWe����u����9����@���o$K`�j���<!E���c=E�Q�����3�Ni��@�X�1�(�=�%����~N�����H���2X&���8l�4O(�#`2�����k�9�T�A���
3%���vNU�-��{��E�Z���6{�/����<��>X��nr�����@���������{����U�'u'�����f=q��#��T���B� @�����t{���q���|t���H��o=��[-NDZ� ��L4���0�0M��s�hh<����^�.�5����)�������%5���BY=�E0'qxs������8sY�3Cy�2���1O������\6G3C��Y������A��������/�-���*��bb03�
��f��lK"M�d�
�I�Ww)��,�oI���0�����x��#X�������	�8����UH������[4�� <�'�.�a���:6��2���:�U{���|��d4�EW���/^|
�-:�����9��l�i�o��/����yU[�	5SC�9$1��
�m�%�
Bx��Do��I���wgf�EG���:Rq���gp�|�tlF]u+V�!�k�V����������������Zt�W��T���N'[�p��4o��������
���z���sD.��~���[,�|����������Cy�"��h�����u
��#�e������������U/MGq��H�6�
�2��G�f���x��W���1T���Y.3R�I�0��h�M<o�Osg-�'����>�fD>�QeyRRZ���{��p!*u[���O��������i����[�m�#�/������|`�A�8��z�b�-b�Y��q)�2�)u���8�l�U���. 59=��)���(W�C�x�.Q����b���o��/[�|���di���2����l5��?/���D2��W�\Q���i8��������vi�eBk�<A��c�j���?4��b;f���Tg��2h�� z���6�CK�F�D��J���\V�2L��]T��E3p��W<����Q=�,����:g%�E��2�0W��iz�9�
������	�4��*��}�_�i@Y���?�$4�-p�?���s��@��5)��
x2&HX�����\-M7�#��P���rEr�����{lGvB�1�d����������:����	fn����WX�@�Y��i���x�0�5��_zHn��/nfQ���W�������w�rs�/�	>MJL����#���2o��3�)A���
���-U������j�s�9cr.h:N��IL~��|����+$A(�A���843��r��V���4�}�iM�����b�E���N��e���,��
���J�6%H��z�R=c���U��Tw�-::���
t�����uS;�U��$0��T���]���h�%�[��M�]A��d&�^�}�������)�������*�T�R���ahQ���3�D`�Zgt��K��[��{���)o�Lnj�i����L!�^���^�]��@��&�+5���i���	wN�0�(�m�g��;6 a����YG;� ~(K�?>h�I�3�;]���	l��
�R��bm�u�,w��yKk�(d��:��j!?&q(���$�;�����IGn<J�����#G��xr��m�������2|����t�9p���Z�$zm.���}�b� 
��������U�!���4������P �aZ�z�^BD��VI��4@��k��0�~���Zx�Y������0�r4ji���0����$-#^��4�-+e\����S-�^E|ZXI�L�d��$D��)L\fK�(����r����/s���d��s��,&�#/kO�?��"��G���Gd�+�q�Z����"n���*�A�>�bI!_��a���L�=`��mj�^�����tq
�re|��]�#�����`�yw�����/4j�Ynm�'��
��+&V��/��H�8��\,�G��'S��.�K�E��E p������<�k�&/���:w��Xk�p�����r�S�[<�(�Y��|��f�	��.I�}�2�P�!���
9�S3��hsGRT����3���8��u6�g�N���
��:yB�lr-I���_�c��Y@1wP����y�6opUj�$V�Y	����|�%���
�5��K��'A-!��B������|��g�e���E� � ���=���X}
��b/^�1�K�����B���F�ph
e*�S,�D��%��/�����U��C����%'E��j����
����*�����%jJo���!�U��u-v��N$5�*��%0������h�bm)�Tqf~_DO��g\������������"K�7����3�
A
!
%_���o���Y����h���l?@Z��{d:$�QF����S�B�k8��Q���)�X����� �
�pL3�?�o28d1��������x����
C`��R����?�RV6�vR)@�r�l�Mc���Md�9���s�+����W�;���*`	b�Q^JcB��uK�/C{�z[3�?)���4��0v��h�rH�N�C)3p�t�����"7��-0s�F��WV�2��V���L�h����]�(�����E	B��2����}��x,!Q:����j
��R��*2d��H��U��R�E�uN��\v��XD���.�S���Q���_X�E!Q���s�b$�f���5c>�*���������u�(9Jd�Yo�u�u�R����!#{������<�E�����/<������ �(�|=�Zb��UUvIJT5��\i�O�O����x����To��q�z��(S����5�m�-�&	���
I�UPEk���`7���;Xo�Dg�9�Z�#�_K�<*�I?@�g
R�O��D��>�8��K�m�2����"�6�0����+���k)��#=��h-Lg��*�#.^$�����W��O��~��%x���f��&}��U��Tq����n��W)����x��t	�Q��e�����5�<�����HWFP���N�������hb#AqS����Tp�m���o����u����a�[x���_��G�9�$��s<C��+����X#���i�I�5�9�|�,�#[�M����*����=�5�����Fs����������0�7�?P:��F��w��x���	Mg���q��/8��ZK���i����
+��^�I|a����/k�Ay��#�WBD	9��*�(UTH�k�[��t����������pVX����s�L������T��6&>�P���g�m{]xQ`JGc�N��5@t���=��i���6%e�*J��u���O���	np��c~����b���/$"
���C��|�l@���:%Gg�s�� ��[�*��wH����A%9��|d�_��������!>�"���r��r��x&���HxA-�$�T��'��o]�&Pw!E�$�R�{��Mi�T��A�`�G��jU���Y��Z��`!��p6��E/�O1�i5|a�ShJ��,/���BaqnE�O����(�������6!��X�8=[��0��������
g5�~�!�z���P0C����w���77{����*��!90wO��n�J�6\���=�Z
�Y�[����&����'�Y�3wa*'��3o?4�_��|i������w�����SZ�.�j��
���gH��\�����Y]wT�Fv�w�����)�����D���Y>v]RVG�r���3��h�X�S	���GU�C��Q����S���O����ty>@�����5���~�]����K[Y���R&^Z��&�S��2����m��!B<C1��~x��B>�~{~�}����<����/Y2�^����.}��CV�	P����~��/�D�a��
���<��'��g�my�9�\����{|���������)���d��S�-,G'Ci{?���'.4e��m����k�����V���r������NF?�O��������.-��4R��f�h�x�n[F��(g1�RR!��v���<a$��Dn~�V��N����o�������a�G���k'�3��n�+C]J��!���L�d��IF�(1{@��i������&�*�n���*s���_1�.��0Er���4!MWBt@uC���l�e~=J�� Wn�G�#�������~)�����h>��[�E�b�"�JQj��a�"��j�nr����Zqy.���h}�������|U�F��kE�����+�x����9p�#�	Z}����p����U���IzA�V�u�Q���\��iY�_�z�hP�f�E���<�_s�E��8u�.��&k7��3��}O�DNF�I��������EPO9l'�
��U�����<�
	[�P]_��7(c��)F���>_9G�g	���s��<�k��J�8m�N��$<q��S1�&�Io,(Y��O�mo�����n6�8��!�~hiH���
h����l�j�f�s�\Vi7_�����S��1�@[�C��6�)���)F�Lc�/�w�a[!�;���������vl\�}a�����X����J��O�/�������6�{��]�sz&[Y����I7�X���z��J� �����_z�o����J����2[�T�m��&��
S�� ��������q?�ac�����Q�5?Z��Brx4���F���Go,����vg������%P��e�)z�����qU��g�4���Y���u�Q�=����O����)����j
�U((�&�������_J��2�;b`[=��"x,��RA<���C�}�`��O>��%D*�t�K�Es� &���M�vPvO�#���u�QI�QHO%z�b����K�d=j';���I��Ny�`�9�65T3$��?�S����z��@�tC��\���fr�T*f�K9Rb����zH@.b{	mk?$�P4�h�^�Z��X\�F�KK.p��D���T���!��=�6W&�*�\�� ��1R��:"8C����X_��;��~�~X�*�
��B>)��esp��g^"A�����H:���������	�� ����A����U�d
��|\R����(r�N=�����o���W����5m� u�QT�2b9o�F8�����-�d����6�J��8��H�A�"3{Y����f:BHp�x�a��)�h�vZ��P�s�p���^4��.���c���
�Nz�9�bx��a4�������S������TW�}];K^:b���9�Y"��@�b���2��=�����lU�=WS������<������h�fd����>*0����w�}��:��Xib����-*[/���0}���I�6k�O��<w3"8����*o6���oHfV��X�S�C�3}�$x�Z_:���e��b��A���8]���;���f!�X�a^��y�,N�;�d�
��J��������wE�����B������2[���s��e9�����K�[�,?0��$�M������%����iv��Og�����X�{Ej�_���T���Y88��M�[���=���:g��0������H]��1|��DK?����z���2�Q���5�����YA�)Eg�G����m�����1'�4mE���Pc�v�Su�k���H��D��HV�t�+��R�:��"5�jv�U�/�y^��@u�����]mO�]���u���c!k���Y��|qa��}�1�u���rr{
L�o��������]��u4���t���"�*q�v�b����jCo��Y����r�,���H�V�f��L��U��;�BeJ�_������R�}��M6�})���+�d��)�2�c��nf���kt�e<q�,�y�1H��i+pi���U4
��,`�r\Z�*Y�UT�<��*},��f-�(�<J*�6�J	?���r������'���K@
g�ry���e'�-6�"�����&U���
��z�B=���P���!
��9C����:�lmEk���������y�y�y�y�y�y�y�y�y�y�y�y�y�y�y�y�y�y�y�y�y�y�����.��_�
#120Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo NAGATA (#119)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

Attached is the latest patch (v15) to add support for Incremental Materialized
View Maintenance (IVM). It is possible to apply to current latest master branch.

I have tried to use IVM against TPC-DS (http://www.tpc.org/tpcds/)
queries. TPC-DS models decision support systems and those queries are
modestly complex. So I thought applying IVM to those queries could
show how IVM covers real world queries.

Since IVM does not support queries including ORDER BY and LIMIT, I
removed them from the queries before the test.

Here are some facts so far learned in this attempt.

- Number of TPC-DS query files is 99.
- IVM was successfully applied to 20 queries.
- 33 queries failed because they use WITH clause (CTE) (currenly IVM does not support CTE).
- Error messages from failed queries (except those using WITH) are below:
(the number indicates how many queries failed by the same reason)

11 aggregate functions in nested query are not supported on incrementally maintainable materialized view
8 window functions are not supported on incrementally maintainable materialized view
7 UNION/INTERSECT/EXCEPT statements are not supported on incrementally maintainable materialized view
5 WHERE clause only support subquery with EXISTS clause
3 GROUPING SETS, ROLLUP, or CUBE clauses is not supported on incrementally maintainable materialized view
3 aggregate function and EXISTS condition are not supported at the same time
2 GROUP BY expression not appeared in select list is not supported on incrementally maintainable materialized view
2 aggregate function with DISTINCT arguments is not supported on incrementally maintainable materialized view
2 aggregate is not supported with outer join
1 aggregate function stddev_samp(integer) is not supported on incrementally maintainable materialized view
1 HAVING clause is not supported on incrementally maintainable materialized view
1 subquery is not supported with outer join
1 column "avg" specified more than once

Attached are the queries IVM are successfully applied.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

Attachments:

IVM_sucessfull_queries.tar.gzapplication/octet-streamDownload
#121Andy Fan
zhihui.fan1213@gmail.com
In reply to: Tatsuo Ishii (#120)
Re: Implementing Incremental View Maintenance

On Fri, May 8, 2020 at 9:13 AM Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

Hi,

Attached is the latest patch (v15) to add support for Incremental

Materialized

View Maintenance (IVM). It is possible to apply to current latest

master branch.

I have tried to use IVM against TPC-DS (http://www.tpc.org/tpcds/)
queries. TPC-DS models decision support systems and those queries are
modestly complex. So I thought applying IVM to those queries could
show how IVM covers real world queries.

+1, This is a smart idea. How did you test it? AFAIK, we can test it

with:

1. For any query like SELECT xxx, we create view like CREATE MATERIAL VIEW
mv_name as SELECT xxx; to test if the features in the query are supported.
2. Update the data and then compare the result with SELECT XXX with SELECT
* from mv_name to test if the data is correctly sync.

Best Regards
Andy Fan

#122Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Andy Fan (#121)
Re: Implementing Incremental View Maintenance

+1, This is a smart idea. How did you test it? AFAIK, we can test it

with:

1. For any query like SELECT xxx, we create view like CREATE MATERIAL VIEW
mv_name as SELECT xxx; to test if the features in the query are supported.

No I didn't test the correctness of IVM with TPC-DS data for now.
TPC-DS comes with a data generator and we can test IVM something like:

SELECT * FROM IVM_vew EXCEPT SELECT ... (TPC-DS original query);

If this produces 0 row, then the IVM is correct for the initial data.
(of course actually we need to add appropreate ORDER BY and LIMIT
clause to the SELECT statement for IVM if neccessary).

2. Update the data and then compare the result with SELECT XXX with SELECT
* from mv_name to test if the data is correctly sync.

I wanted to test the data updating but I am still struggling how to
extract correct updating data from TPC-DS data set.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#123Andy Fan
zhihui.fan1213@gmail.com
In reply to: Yugo NAGATA (#118)
Re: Implementing Incremental View Maintenance

Thanks for the patch!

Query checks for following restrictions are added:

Are all known supported cases listed below?

- inheritance parent table
...
- targetlist containing IVM column
- simple subquery is only supported

How to understand 3 items above?

-
Best Regards
Andy Fan

#124Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Andy Fan (#123)
Re: Implementing Incremental View Maintenance

Query checks for following restrictions are added:

Are all known supported cases listed below?

They are "restrictions" and are not supported.

- inheritance parent table
...
- targetlist containing IVM column
- simple subquery is only supported

How to understand 3 items above?

The best way to understand them is looking into regression test.
src/test/regress/expected/incremental_matview.out.

- inheritance parent table

-- inheritance parent is not supported with IVM"
BEGIN;
CREATE TABLE parent (i int, v int);
CREATE TABLE child_a(options text) INHERITS(parent);
CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm21 AS SELECT * FROM parent;
ERROR: inheritance parent is not supported on incrementally maintainable materialized view

- targetlist containing IVM column

-- tartget list cannot contain ivm clumn that start with '__ivm'
CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm28 AS SELECT i AS "__ivm_count__" FROM mv_base_a;
ERROR: column name __ivm_count__ is not supported on incrementally maintainable materialized view

- simple subquery is only supported

-- subquery is not supported with outer join
CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a a LEFT JOIN (SELECT * FROM mv_base_b) b ON a.i=b.i;
ERROR: this query is not allowed on incrementally maintainable materialized view
HINT: subquery is not supported with outer join

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#125Andy Fan
zhihui.fan1213@gmail.com
In reply to: Tatsuo Ishii (#124)
Re: Implementing Incremental View Maintenance

On Tue, Jul 7, 2020 at 3:26 PM Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

Query checks for following restrictions are added:

Are all known supported cases listed below?

They are "restrictions" and are not supported.

Yes, I missed the "not" word:(

- inheritance parent table
...
- targetlist containing IVM column
- simple subquery is only supported

How to understand 3 items above?

The best way to understand them is looking into regression test.

Thanks for sharing, I will look into it.

--
Best Regards
Andy Fan

#126Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#119)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

Attached is the rebased patch (v16) to add support for Incremental
Materialized View Maintenance (IVM). It is able to be applied to
current latest master branch.

This also includes the following small fixes:

- Add a query check for expressions containing aggregates in it
- [doc] Add description about which situations IVM is effective or not in
- Improve hint in log messages
- Reorganize include directives in codes

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v16.tar.gzapplication/gzip; name=IVM_patches_v16.tar.gzDownload
���;_�\{w�H����S��fl��;gBl�p����33w���[��I8�f|?������5��'$uWW��W�����_l>{����N�E�r�%%?��3����I�;�3I�U��ZO��x�<_s������]�n����5��G
��{�}����=O�]_5�V�w�������v~��s�g����}���^��_0)���e�WZ�N�YnK�gxG��g ����;��@[�tt}[Wd&js�uT]�%C���XQ��6jk��:6��)���W�(��
�y����HC�����\�����4.��+�h>���k���3?�m��W��J�aC�����lt�t���cw���������B�@�w@(D
����*p����uqL�k\4*�����1]�y>�?���]��y����~��+�>s��S	�o��5���!����	�}��O&���i���l��9�Z�!uoT��z<W�i�����oZ�ff���6l��
�06-���X�>4m��D��n�U*�9C�~n�l6R-lR1m�}�QG��T}k�5r��2i[E����i*K�V�,3��7P�l�����������0������o��sM�r��~~��������ZVV\�{�����|te�5k����B<��Q#[s�=(h��6x������b��@;1
������MX�P��a��)�
���8W�;����-����fMVHZ�w*$��z����M�����9�(A���+��-���n�
i�C��>��Y8#`���������J�0bv��M�n�����sg|��e��i3x7��?\��t	pY������2>w����Wi��
������������F�IQ\^+%�`dz\�g��%��Ei���\����}�+��j�kY�g��85�����9fuo��ug\�]��L���5-��g�o>B��������?��������Hi)l���t�-��-u��m3�mu�-���-C�������+�U��U����R��@��1�
�@��������g*�c�<7m��nj��0{>�D��t�
z'��<?g�K���g�b�w&z�<��:c�
l�_4������a&��8�pf���_�Jv&4��b)�O.�k&&����� eR!4�|� lh�7�X�/4<S�H#L#����>a�6`N��IG��+@%�3�h��m�:b��VJ�^������t.�(��[����Q
�O�����u}�)`w��Aq�e���qk�h���,5��X*�8������������g��6%8L:���L��;��;}<� 
T���I����
1��W;+�����vC�%hy�]8�	��+�o"%Lt+++�7a�O��.�-q��h#mN�K��h_x��'���p��b�13=7VL�G����3c��yL�H	M�[�f����-v�, ��J0���Z
1�Nw��C'�C�%�P$��Q����X++�� ��Aa�@���gn;�'�����o5�\[�.:���!Y��pl��5L�<^��7��H��i����I
q��n��F@ ��NF��k�R$�`t��� ���x�(�a��C%�+��:��([w���9�U�����5Qh�������7�iXU�-�0~vj��XK��o_;W����b]L��7V3�&s�M�K�P��5���)�7�f>q���v\f���&��t��8�`t���U���l���{;�Z���*k��;���e��Y:��{(�=*n�����R��OQ�|�
a2T$e�5�~�J�t��j�������D����e��`	��������`�W,��E�����Q��?E����Zz
~E�.�`T��K�Z&�5�P����xC�����awW(^���A�Mz~}��:��
x�������"�D���i2�s��&�����Y���j���)	��~6QO��}����a@��x��{���4C����as�����_�������bh��[���g��Ur*�������F�2D�y,A��	��Vgd�D��Kc�"I�������nQ�z��Q� L@�=��\s|#�".����v6�1���sPh����@, !p�N���d���n����x�!���\-�o������d�*\���vvp�pFRu����1
����2a��i���K�u+�l��7{�krdI��]H�>E��/Nuo��l<��,�?K�NiK�F�������mn�;�`*���6��L�J((��O�~
�
]\�eg�����U�sAkY�q(�1Q0Y!h4��U��� �D�{N�����J��%���.�L5��r�-�����XV�|���82+��v�g�VGi�r!0����`����y3�?aV�;�NI
��u�r$�BC��|o&���2�'V�&3t�".��=���$�B�T���Z�p�!)��a�I��4b*^��8�wk���jm��z�<�S��4�cM�c�2F�FC��q��r��Bqy<�����B[d.��q7!����l�t6���0�i�
&E��>M��!�����
��� ���=��Mv��^�	��F��1�7����,��D�+p�?���k�J��?l�9\���:Ze�e����Wy���:�zb'�N;Y�x'����-����������������*���[�-���1k2�l���������b�n<����|%�����X�5�YBn�|v�VkE�b6����&�2E�����N4��6��
ZU�/k�UL8"6�\�`���_0c�9"�����?S1�Zq���Ez�Z���}�'w�`��jv-���Z��T9ZM��T@��w1)� ��YX$�����H����2�����)6�\�.>��hlK���&�<�m�z��Y����KIN�I�!����yS�@����nN&h�i�),��x��G����!��1g\k���<��G���Ztc����`�}{�������TM����>(���N{'�
��>u�z��
��\��������k����55����$"Z8�(�j�����r4�,)�������%51j�����8�'��A�z�������1�'C��JO{�������;9>LH@C:G�����Q�������W�5��?�G���tq���zDHs|��T��rM�5�_�yye�j�i.4�/���a�Lg��W6>�����8�'�w'����V��Q�&���������+��:�ou|.Sx�����
~S����M���M�T��*.��FU��L�	�
s2a���b�~�m�}�h���!�Rk���(���������>�llv����%!���w�C��i���<�<��=�G��F��f�j����Q�='�����������}#��q���D��o7cA;�$�R�. �����{&'*��=���E�NzG������?����!�G~��%�b��):���aT�?9��h�.��mb��X">��DB�I$ |v��������GiR����O��3>!�4�b��o6�:.�K
�����c��f �
3���Q����8-��;C]��q�J��kh�����Sp�=z�}���E_��/����A�/�xx�1����0���bN�H�v���J��H�����l!�=�kU�+#	:J~~������LwR���2Z��P���ej�2C����D^�[�L�_�9�lj����!
�����?&3Y����4=A�_I
H�C��6�����"�C�A��9G�L�5�M���U�qV)!��2dh/�Z.9Po���<�,�v18=;�b��w|v4��C5[��{��;������P�r�C^Fc���FD)g�����#M�,;�N���"o6�w���i������g��6������y�I����R�����Sa�fa;�Yg����v�s�
����%�a�cS�Zn�����H�t��V�^Z@)��:O����P��9�PR����U��.�V�bm,��7o�N�PqV�a�(=IZ��S�����WY^9y5�����sK���[��`~�����BH��
���l����	]���2riL�d�X&TD�k��uU&y.6�v�]n)O;%��6��5����������`,`0�H��t,:�lz������6��hG��$�-
RZi0m��6=���P��?��e�}+K��p4��L��Lt�/�"������W��S�.������l��y���J�S��v���6t�d$��,
/�Ie�[8R1P8����a���\����p�}I�T�2�}w�R-6|J��<���B�������E��g������q�������~���M~?�xw���O%�����S)�����T�6�����������T�C���@�p��Rs��6��y�#b������%�f���r��ain�R�+��j���:��9>�,���vp��x B���%�1whz��p���#���G�I���/��&m�x�E�5�,O
��������AA�l
}:5(��jhJw���	T����B{T8�@p������4����iKG�w��t6��.�(�\�������� �m���o9����xvG�4�sDF4�V M��H������^���u9:o���a:b>y�J$�d�5Y�afbt��b��
}�?Y�3U~�,����q:��U��A"�w65�b�%���m�<�������SXPk��t�H�������1�e(��W>T-1������I���<azR�	�d<0��5���Cq�Q��<�Kx����|#o���.r��t��[��.1�0��M�T0q�P�D,���',����� ��!�R�kn��$�������W��"�j�����j!<����sS7����sJ��z�.�%:6��Y���;j2)'t�Kq!K	�4{+���<��ec�2�ez���2uM9�C�x�?�������X�T
���T��*	��Y�������{���	4�)qx�$���Zk�j3�H)n�,>p����lf���d_#����:���4�F���M�w��ifQ@PG�7QA�4��h���d��.Z�g���]9������N��]�I�V����0�#Zt�}�������?�V��W��/��c	��e�F���7"�`���T��jxRU��n�H�����`xq�MSX;�[,�\�����!-_S���r��A�������q���`3w�C��Au�h�Hi?����B/�M��z��X��%��V�$�"X������QR����np�����A��Vml��n���\�����-5���Z��aPA�4�n-8��+� �^�n[��5�
��M���\�X<��Tk�{5�%C��P��}%�g���v��(Y��[����v!>|�������������0��l>%<������z������������SA������k�����������	���*��FP����ShU�S��V���J-m�d@���{Q�f-�>���Q*�?(�*K�Xs��\!�yb|@�:��c����.���d�~*�>@5�%v,��BT���P�n��;��	X� ]�kU�-��������Bx�#�������I��
(2�('�u'�-���	��%��"Y�r(��3�������U�"����Pa����s�U53���U[x�O��Kl4����r
������m������,Z
QSC���� /�����R1k���_����~*GO3LC�H.���m����F'�����=%�C��WE���yx������6�cq���S�yA7�RLB~�H�e�$����	��3RL����z_������a!�E��fz�������.B*�-9�!<�g�-O�p�
\M��d�UI�ic�/�R����V�ogC�� G��a��2�����u��������pXA6����V��^�L�2��)PM2�]�#fOS-�S0M�
�Rs���hu$��N���tY-�.�wA��,��_k���[����*�����5������L���}�UFx�[�]Y���#�����ks�������{��^�����}�.�!5�`���������L7�����I����L���X?�PA>�l�^��(`:��?�s��~�X��J�5�Eh���?o�<h�~���>bo"t���n-|��~���u�8�"��G������IW7VbQu(��y�q����{��}\e��gSG�%�'V!��!6��|�30���C�a����c)����c\u���h|��d0����o_��A^z������?��H�\��4�Ao|4��`+o,���fC\�A=�1es|�j�=�}��O���D�Gk�G_��r�	�r�B�,�|B�����vD��j�����z�j�xZb\�IbBK����bP���� }�/'�!�$f%��b�����[)wr�"���TK�R�D����\���^���)y�;�o9����6[Y�����I��KX��0��'
��I��z�,`�K��A�u<�-�w:&�S�D�[N�mYB��6@� ��j�+��3�����'q������3��V��	�K��S�8]�z;(�
�J��$�>u(N����sC�^0o��6����A�#O���WT��3�|;P�=u(�?OnC+������Sx��������{B��������Y���;V���M����}�em�����2��G�t�V|+��OU��X��������T��������P2%;�7�K����v`�/���(E��n{
�du�K\�I�)�7�p��]�{te���B���pY�6��O������B�����P��x�W��n��[�������Q��-_��m`���e�y�x�zj&�s�)�{z|�]�yTFL�g��Eyov6����������X,5��Y��Y��q������u��h�A�~�z�G�vyt���i"���1x<��)j��&{�Z�[������')��u��j�������}�sq����������q�A���1)��q��|�4�!U��an7�%IN����(I����S�e�b�$
<u��"��{������Q����R>�"����'����x4�����oH�5MK��f�z�'1���R�n�Kr�`}�S��}h���x��>��h��.��=�6'w_w����8���=-}�]���S����S�-�M�J�E��=����g����Y�������}�����/�x�~s��?�F�f�H��e���i�	Z��C��O����w�{������|��S6:E=Fx�����o��1}��Q3�[��QK��o�����o�����b��Ha��7���t���f��oX���@9�I�>]��e��(��{Z�jyIf����y�������@�/w�o���	��	{9=.������|	��Q����a]����+���=Bt�e��[� ������}��xV��>EzV�=+��k�z���,���X�&�l���<3�&���(���k�z~����5=G�y 
��F�A4B	g���1"�,eP���5�������1��r�y��]s��<�����=1
����C�o���an����\�7�ImyZ�����%[��4��h���>[��&��cOp���a�u���aJ����af�#:�R��:������>X���fT���_����������W��o�H�p�W�(~_! ���}o��m��z�}�Q?�:y�u�gF��~�`'��'�d)�z�3��k�g��K�c��>���1,���o9����<>���1��)�s*�9T
K�*���c�|N����{��?fR�''�};J��!TI�����o*9�\0{���k������L`�����4��F$�����bN����_w������.G���1Yf�!(�R��Qt�H�����7����'��<�Y���z�O�U���*�[����2��i������7��{8;�oBA�����gf����l��T�<��K���hf��#�rF������x|�V��������S�E{H�|s����s����{;�<+onI�������1-����7����yZ����9��V��n��p��'8�gK�{��s��S�<|���������Q=�J��%�������<UU�{X�g5�������mT�(�����y$������!g����������?�g���Eq���c�(��W�D����[r����zv��H��;���3�<����<'T�G�1 �H	����>c'?')��f>�C&)zP��R�,>�6}|N�l�2<�H�C���m6f��e��]w��rm9���J3[��Qi�2�oC���|rA�ro��g��s4�gm�����+M�sc�W��Z������|��1��[��@�����f/O������]�����x�"�������^}|�D�<9�T��!����<���7�P���t��'����
D�
D�'��nx�K���f?�q��,����b�����g��a5n�f0�Np)'�X�^L��~�O?��<d�'w~{+J���{�n!��^�p���Q1>��;����s�n4�
k�0���dZ�;���f�������gm���:1-x_lR�N�Iz�/�f<�y$}&i#��>v&�9x�L�F��O2����cN�)�I�^s�O!�A�H��p����!�- ������X^eN��������b�=�2��F�D.
��R:�����uE��uk��-Y���������m�I.�-W~f�x��'��[�m�����������/��u�������3�>�'��&����w��A`�lw����l���F�/�����5	���X�r�����A��f����ss�8q�h��5���R�������p���/!FZ��[�-g�1���[�	�n=N;��q��GH��W� wo��E�
Rg���p�"���o
mIv���c������Tf�f�1Ys����D�Z��7�S����R���{�<�fSUy�5?~�$��(���3�vX��;#�'������c�}�(D��/'
1��w��w�G��& 1�h;�\�<h�+��L��e]wFA����������{��0�#��g��&�v���x��n��[���+!<;�x#�j�AQdp#,�_��A���z���9&9L=((���,�b2�G�\�2(r�U�o
�F`����������������S��F>���r���r�uz���sO7��A0o_��A��Ww8���a_�Dg�;������qZo��qu�M�W������GPl��]�7��p��_�)��p�VAZ�����3�c�4���[��6Q��	e'�l���nD�+��
)/������jyVV�++�%�+d���
���D�Y���H�p%IW��.{tw�����_�
;�������02�T�����r��u<(yv0p��#	���<��D*/zN��#n v�![�P�x��&�WO;����~�!��qzz|
d+�����n�X��@X��{
��-g��l$j��Fa�8��C��c
�CQ����5�������]���/���v@��?yH����5����G�r�n ��Q�.hU���"�
�Y`��-�Y�?]|���5�~���������$Z��,���$G0HEqQ�����;C<���N��gV��O��b�{{��;N�#�-��o�W�����4 `�}^�DP����O
"���4}JC��d0����6`%��c��9]��dm9(F���~�3qg�{���.B�4�g|�/��f�(@M����/������U���'���[�R�����U��{�"P�;�N����pQ��%�yd��x����M���y�:��*��%:Y�*~��S�r��X�,�	��F�o�����d8/0q����T6�����Ch0
Nc��������j%:����B������ri��7DI���p\=X<�^z���.8��<�f=�*�c���cLU<��#Pa!Y��QTW�Z��[�g+��_�Y�f�v��d>O4���o5�6�
o4���9q�Ac�&�ER�Z!Y�0���85ouc1����C�E�����)	�J��1E�B��R����	�_l��w����>�B����o��1}��>f���fs�w�M�Y�q:��&��i�?�f��I*���=m�P��dO��w��������<32���?>?j�^���2�,��3���	�j���e�oy�C�\�y9[\N�W8�Z���u���S�����q� �Q���zYAUO.�p��� ��K���b]Y�C��`MNbO�y�.�w8a��Eu�
VsI�8+�����^��������I�	� �Mt���J��P��a�c�������F+� �4f���]5po���c���PF�����W3�g4���t��M��&E�wV�xr����s�3�Ji���_�Z������o�!�j��l�k�d��n��Tk�
�;���|s@�?��%c���^�K�������}+h$9,�|w���d�wCS�%��!��)�����O�S�%7�<�XhZ��i�m	���dtme��=�("�y�u����>9q�8���Bq��N�Rd���sB��R�B'�@����y_h�q~R@ij��GE���p�7��ZN`����v);1nhG�2�t�A�������EM����VN�������#�0wncn#mS���tn��[`Q���#4�(7L�B��!�qo���)�ac�T�%���A�����'�p��"���t7�����P�&�:g��b�6��3��V�����F*�i�c�)"U�O�)�dR����6'4�;?9���<�P?M����tD�3�������ll�:�|���MH�� v	
��=0DS/+@�����m����0�(6��5����Q�n�e�����/E��r�`{�����v��;� �912����<e�f������Kk���P+g��01����vw����=	���M�]_H�]5�"4,p�/J��)�+�}��`����"�('��?���V���H��N�R\�'<���=��N@����g %7�)5�:�7tFg�Z���D��y��W@����8�1�)�M��Ho�LkQ�
�i�.�\�������IG��������i~Je�J���:���c��g�� d_c`m�H�����$���3)W.��]w�����NB�f�cy�b��������G�ad����X��\�[wd}���U�_�����p�����������O{���
�y�"g��89���K(��"5MzV��x�rza�t���������0;�p�B��I<���qD?O���O
�����p��Z?j���JU���(H��#�����V�1$9�����1J*l�R��>'��B�<�z�7F����!L�m�a4�E�:5��x
)X_E��w
���:�����;��hcs5b�,�x����[[�B�����?md"Q������Y�Mb=�	��@B������B���	�7fj �R�7�7��y�5����d���-��z��h�|�g,g�}K$�]��,q�(������"r=����w�0������p;h��c<�V�y�V4��|V7�/[��T-\8���$'�P19����j�9W�2g����I��s�%�9��7��3�����~_ln^�e������/A�B�����@r�m�{i�& vt�(���z�WQ�l���n��+�Ze����k{�\*m�j�����������z����;���A�'Vx%:���~Lc��r6���2h�o_�w���qU0�K�?�����>�����������k?Va��")4�&)�=p������/l��NW�r�Hah�����uE�����vx����8:�������1R�/:�Iq�wMy���������y����`�}u�&t�2�@Z�Y��<�yV�
����V�u<��T%����������bE�6�����U����bqkk�����Z�3�A���M/�[�\�*l�
�06A����I��S�>��Z�
�J���������{��j�����z�D��f@��������<��WG��}O|n�V`�J�+���U�l��J�&/{��K�/���.qJ�,B��S��U`��G�E>`�F%0-<w�������;4��i�,��8=]w���l�h!N����gu�`��a����\���JI��%-�����_�v�����y'�yY�^.Ut�h���/�(I�B��v��&�t�:: ��\����4w�xr��)C[�1�f��c9���~�3���4(���K�A��:�Bp'��[�d�,23�G� 7����'�a�I�V!`�@�)p����R�:��5-�H1
y��j��{V[�p�������b�\X��_�N�b���99�N��!�����'+�o�J�t���9������r������v�zbC���tXr]K2��{.���k�RW���(�T�
_F�Xf�N�y�F�P&r�iQ+�Z!�/y�G�`s#��Pa��@����5��>�`��+/:���AN7k\DAnv2p�k8��u\���l�^�<{g��r����5���p�)Fc5	GB��
���4���Fv�E?���t4��T�h��B����~�~�G�L���9O�W��{��4h���CCQ�3�k)����8�����*6en����F�*�-f��R)����`nY������m����u����n}���S�f����x��s�6�b�I�(�>gm���qs�?��mg�)��T�\�~
?rWx'������������6�Cz��
/9A�s���#d�`i\��w]����eS��E�3���Y+S��e���<��g��L�����Es�����m����>6��3}���Ln4tF���5����g����A�+�Z������O��P�@��3�T�|VBe=��J���G����s�Pg/1:"K-I8J����v��H���0f�q��.vD}�s����a�f�\�85z�VgW�Z8�����/�vz�aLCI�	��x� Z�J��RTK���p�+h�*�@"(�
"���0�����HNf
PuC�L(�J���9uI��:% {SCI!U�C	�
�\�(k}Uj�����dg�������Ij�e5&��������;����9��$�D�� ��@7K����V������S�^��*?
��V�2���m���-��HG��ja�E�S�V�p'���$�F'"FQ�#������m*�z39�����g�GV^I�s/��^�M}s%�����bu!l����g;#����g��Dxt�YN�|&�(��Daqr��X?�E���K�1�v���L�h�

Q��#i��.�����|��Wp$�Ys��*���e+��s`'8����)/�SQQ�p8���)�-nN�����
P|i��&~x�BS�A�������&q�'/�&>�JW����12�������Z���1���w�{��������.D|�u�f�Y�|�:�>J����8:�8���"�@�.�7��JmC���O2UG2���+�l��;a���ap�W9�0�.U"�+���������x���]��/acW���x��C~�f��a����?�N��]���Ju�j���V�;�������T��Z��y)|&1�������f��'����������!hqb3������d5�)+5JRN$dD^���H3�g*�@�����C����L�a�i@��,���9�)@��.L1�B��yy�/�V2�Y�J���c�;��n�i�Lbj�������]�&��DF������4�/���.V���<�.S�Hb{�@�.�w����S������2���F��k)�x�yM2@X�w���o�����k�Z���4+���V�A���
����y��
��KO	X��~��_�n
��3�����0��_en.P}z����[C������+��;U��9�`G��t\�JzbR�����JA�m�VY�a���N!���~R�u�9mv����]�3���a��������������aw�!b����94`A�_J���~���u����~����(�������J�0*F����
�0-�,P����5��0k|�38�O���������e����������uc�+��'���^�G~�4�HA

��I�|[�|����|���7�l��Ff��S60��:�5s)I�c=H�kfl�cV��$R=�����)������3� �RK�R�P�\�>&u������}�;�=#��r����T��MeJ������>�����e���U;��\\��z���yep�,��?��g����Yp�c��u�
��=�S�c�q������>��g��b�Y���z�����������gP�\���>s�g.��e��l��:���=-.k�=.;�����3�}���\���>sY�eQO�FQ1���0�Z���Y�3�}f��,���>��(�}�����nd�1�g��b�Y�3�}f��,6�b�Fv&?{������>s�g.��e���3��r��������Fv)�z���\���>s�g.��e%��C�c\��O�O�Nv)�z���\���>s�g.��ec\��oeg3�G��]�����3�}���\���>s��}�����1nf�3�gF��h��3�}f���6�h�nv6K{��������>3�gF��h��3�����c��������]�����3�}���\���>s��}�����ng�2�g.��e���3�}���\6�e�vv6G{��������>3�gF��h��)�5X,����u���c��l���y���(�r�{�n!��Dvxol����=�������<������o��\�"����)�2'����CiE��^��S/��?��(�j�h����JD�ut{2r�Vg��E���������3�kd�_Vg`���5��}��n������]���H�S����l-7�������Q���(�P���e��jjlK$�j&=�����@F�����D{Dvg��\�}\�������g����w@{�����L#Ys�Bc"W-��}�,�v��������D��3rFs���}gd�Dp3�������#*/����ICo�m�����[MXR~�vF��x�&LYB�L�D��;�������a���y�="6�����u������[�������W����J-��1I�����5�����7�N�[�[���"O�����	����_��"9�����_�
�A0o_�����;O���/l�
��{��sw�;=>i��[��p\!�U��
��DA���v�����u���N���[�������4��}�}�xW??l�w��D��3$���m�����,?(�����7��%ZY)��$�I�Rd�#�E�V�������z���B�;�nLb��)��^^�*�8N4�V���PY]9��:
���?�$�W��Z�������;���+����&�r|�,a��iGt�	B�7��.�S�s����B���\�Z���h���n ��Q�.�)���}oE�|O|,�,�j<���Y�����$Z��,���$G�O��x����
rP���8�IuV��O��bC|���������~�f����T�������b9
G��`��?��������s�V`����%���E4�����v�c��D{�3~�[��������a)��;������hq���d��r�},��������
��_'�w���/
�\���$���FK_�������g96N����V�3�N��F���?���s0�M������\�%}B�����d%�d2<���X�.��_�l�zQj���`��-1l��U�U���(������[�R#��]w�%��z�Z|'�E_Gpa��Xp0�8<�Cg���t�u�����qc��T���Jq�;1���P��L���~tP����bC����4��<
qv�_�[��,w����X��@V�aT��iSAsx�E
Q79�N{�@���m'F;�1����@u�;_g����7Oo�����G���||�������@L�=�+��DIZR�Ew�[����a�(���:4�.�>�G�V�.zLRs���a��5���\��+g�Q6?6[/���;k�2��#����+:�qo�����)wB���,4@����HwqV�xr������������/g����V(�_;�R�!F���4����h)� ����������	�F.�W$x��9=�'5l��}whw���#TM2���!��)<�AT����Tb7��F%���k��n���gC>���;��R��8���	!NS|�B���6K!�)gt	�>�F][�GA�2*�D(K����~���������r
E�k��I�r6a0����n�$���-�N*���P������-��z����H7	��L,$J#1�}<����d�RMB)'��)����(��8��)A')	Ak0p���������3ih:�F
�������C����n{����-��;����O��@C9���~O�����g/`���q��9r���k�L��NFA����&���g�>�elw��(����t��xl[����S\p�{	b�<����1J���4.4��
�B���I�Q���_��[�����r�������j��K\_:�Kq�VY Y�+{ |�;�~#/~���5�)}���:�����8�0�����(��5�����>�6��N�f���~;�rN��I�
,b�^����q�.YI^��A�\X���za]
h�.��^t�cy�b�������U��02���*��,���F_���~U���g��5��3�?9>l�������{C6r�H3$f�N�����������CH���t%���^�6�n�?4���3��o�_hu���k(G����'q���@V���(���~�"������P�6�B0������HRp98����h�0�J��Z�+*��l<�":�;O;~�|������*�`�Z
�^��y��\�.�+)���f�&m�7���o��*�@�l|h��A��lB
��]���f7�ih�'����Q�������#/�`b/�d1�0$�FK�b!�������V+��N��������7FC���R��Y��6{.�A������q'�fs���\a
6?9���G-ZFp�*R�}���v��������	�v�j��?��;���ry����Re�\*�I��2�������e�������s�b��V�w;����nm�����j�N���/U��Ve����S���G�Wg�X�wD����/*�����+��HYH�~�������~��?����T�J����v>�{�\zU�zU����^��z6��m�+���zk��(W^�+���"���T*AE���*���U���������e6p/�"�)�~����X,r����N`�m��������D��-6���Xu�X�ev}�U@��h����*����H�[5�w��4�����
�5�/��/@��=���f~u����bs�����)`�Ly���z�Wa��u�w���;�b�_��;{[�UXm$�.S{X�)L���WlV�[��}��<;�?�����fek��?x�x`um<C��~�v��>����_�xiz�����^�-�z��K�4����z
��A����~m�wT�+���{�N3mz���lR�{�6Y��-��nH�����It�{��p���K���
���e�/��\���-o�D?y'&`u}c������}h�l���P�bP-8j�����p(�S��:=D�jm���S�m���@�6�*�����/�,�)2�=��0�����a��=h�4�G��������E���v�v����<Lk���ad���
bl���%�,������nRcT��}~�����
��
�f���-�\���
��P��7�3)_�nOu�>Y�z#h���4�(����8� �h4=��aV�`���)d�b��2�u#mY�3�_ ����1O����u�z0�
�����{���O��g�����`I�{�^������-w*vok�gW����
������v���_�y�����F��/l��3r�����+���������������TF�����
�6Q����M�����{�|*�d���G����������f}�:��7k��C���>.�^�1��Pr�����.��~��Y-\Y�IlF��S<'D���r�[�B,}U��:|=Qc������?Dw�F�\�dGBB��	2���~��.��e�90>�b�&����U��wY���8��Z��;I��)g8�{���&�5t}i�U�/[P��7$+�
�{�s.@- �t�������#��&UscC�0��.9��xoc��J�U[�K����W�v�������#�	
s����zClq���x$�X3X����G�m2Eb{k"�|;_�<$�Q"�"���d�N� ������/�W��p�����X�]0������`��=�J3>DG��D�PVM�	�0��V���!���I�/��|��jp$ ����{��g}B�
��2�MNU�oR�L�6��,�+�qt�����WL��b^�����{�F7�x;�%�<N���������-�O�>}LZ:G&�21���8���e��7����,P�c���m<9RVn����j��lS�R��<s���n������8e�x"
�u�,��eu���/K���P��H�b�;��B?�'�{~>j	���SZ
r.wb)�C�1�;��2P�>�3�2�p_�)I���Z�Y_�2���+�%z�e9����QI��.`j%fx�M_�G���V7R<�fO�s3c�"��wBD�i�YE���S�D`�����+�Sx��r��w��nJsX�bQ���	0�2'�����f��E���@������N���0��S�X�@��p;`��q�P�	���&`��~7QFv{����9=o��(�n�6�8@�@��W��'L__�]w�Z�0������u�x��%j\Z#:�Nf���'#>j�D��;0���k�$]C�n]I�,��� ���X_���Y9�����V�WRP��j&3_I���`_�����K=������
c���HO�:f��];L�j�bu.���ha���������a
�%�LQ.�
V���!�%��G���FA��<:8�� �q��Aqf��,7_PG���.�N���O>��;|[�Ci�/2no�to��zr�F�J5nB����������E�Ox'�:=?�G��u�Ci�`�����f�*�P/Q~v��	^��zF{���[�x<px>��a�3#��qUcF�H�^�����.�1���e���v�ry���Mg�p��G����Z�6�9i>j�-�WG�{x��II_�����j�E����r������2@e"^�.���T������J�*%,L����B h�Q����Nuw��{_V���R���1.6+_�U ��m|=4�m���d��mAh�8C"�}l����4P-��_l%7��V���@��a����Fg�3?Hf������q�au��J@����$�	�*��3OYX�[@�O�
��!�T;�&�O���~����������
k���c�����0�N��^C��Z�ve{�l���~�����j��a�6��K���u���d�P.�&�K��K�����OO����$)�S�a��P�e�Sd[O6�FX���@[�G�����������zH
�:O{��\
�kM���F8�/�@�
[3e"�h�?\9=6���%9���I�pP��"��h����<�y�F�|t<u�#{�=�l,u�8{��i����:l�C�"�)����a
�������^T�����%!g5x�vYD��d��5e�	z5�"�Sv�Qb�CHV���
aL�<C���4
�_�w�~#)J����.u�w;������������Q���4��%>Z�%O���T�V���k������7Iz���M����}�7v������F��^�v7(���U8�7k4�M��A�>N����� ��@@A�@�����Pzu@N�y;���E.�7u����O`������������n?�{���]�M����c�����"B�Ts��V�sG7��pZ����A�1����@���i��<���x3p7�sj�N�8��U��d�p<	�q%�fu�uG�M�MZ���\AI'�������z��r���cB�T�����A�����"�ck���]����;]�s����Kbn����rn�}dGh8b\��K���.��s���R��q�c��3$�C?�rR���}	�=�������wF����a��_�lp�����6Z�:�Y����z�t�-&���!E�#<��:A�6��G��j�
��a����.���8��������Ng�|��B�FD�'���G�1 ���f��������z,����'B<��&�������s"�S.0��� w��u��������5
��e{#Q��B3j���������t)C��gD���X���vl�������kc��+^���{8����$b����]���b�!���
��u�M�O�T	L��K��-��p�_�w��j�b��nn� ��	�zn�M�H����e=�d�.r�������*&����]�$�1(�A��c���4����/e���A�\��1��5����%#aK�����;�
����+�1Q����h23�>1�K���N�g����)�<W>2����N^}���`Q�����)v,�rj��sa�R=5���#c�Eq6!�"OO�D���s�
'�Q��6���W�����n]�D����A'j�(:�"1��XU���=�����m>{����?{���X��~�?���c���!K�2����X��P�S�d]���L��]�Lw 	v����
'MYGU�.��v�}���#.3�H��l)�O����YW����:�pa�|�x=H#�(M���T�e���OL����R}���}:���X6:���{��l�f��[�����Hs$�r��������������3�r�9�Q�I!����K�M����6s5�9�;=Z|%�,t�Q��+5Q�l�9����G*ghg�E�52pQ^x��+��:���`���!��ia?�T��+������U�!�al��b��ju�Z-�mU�y�+�^���j-����8f��v�� �$e����Q��� H�?cA��v�`eP�o���NX�A��"K/�����`\�K�����`��
��f���"�g�t���������n��sC�0�q��l�	��/�y�����Bt?S8�OOZ��Q�e#"������,
*�!�TKjQ����R�'��b����b����My�O��M�f@%�~]+�l������X��qL4N��^����7�{����cGP\3�����]��:�����h�����'�iZ!y}�Z��� @h��\A�j(�i�Pf+��\Ri�9�M�%3
O�%:z%�t]L6�W��PK�B$I�B����"�}�}'���T@� E����I�!a~������^���)w�����7�B�E�vmd�L�P�����b<;=�����#�	���Pt%���'�c�:�T�s�c>��
�A>0����S�x,K
��4Xz��:(��O|�C�$����E~�R��y(���P?�*#�d����F�C�������	aS
a��p0���9���kH?>	����u��.Oc�>L��%ryi�BZ��R��P�a��#J����<��HP��t�����f]f�>B��h~���6�Q���z��8G���Z�YV�����~\
xSpm5�����$�a5�p�3M���{nu��)!b���Z$�.P��E�_�>��<)�}�x��`�2������%G)F%|�2����Ma�6eV���V�tS�5�=���[�?�f���T�]�
&^6�A_\D�� ��og4�~Qj��Fx9^?k$�5���G��1R��h��	������u~"��v	(E��14��l���S��}Nq
]��{��F�R(t�FQ1�VMe�!|�9i(���x ��A�|K��^����?�o����"��y����P���W�m1!��M��`��BhC���9���A"ab��FJ?<���nC�S.�?j���]i-��X ���&t���*F��\7�o��a����gLq�6���Yk#��"+�!2����Ao�&S��f���;���0������N���4t�b�@��Dt���kA�[�"g�6���
�	��Ik����px�����7YZ�W�Ea,��k�e�n4�J���RF�9~��VP���Rkc����N��gt���I5��60E���]����l����J}��S��s&h�f����r���}1
�z��b���C8_iI��aZJ�l���r�N{.44���t���I]m	?���U>��G�4B���Zwh���I�ZPNv	A���'&)}NilF*�*����2T�sh#�~cTn����OT����q�]�2��E%G~��)���.3���K/���O���u(tb�S�(��-	����V$mZi������ 3���������R���y�u�w��;��lP(�Cx����Ga�E���Ou�b��7:A��~�E�b���������$�zS5/����1s��l<.�����b+	>�����C����\r�-=���������(d\�;�����Z�����x�3��X��~q�:���g�{6Z���H7�v�>�l'-�p���!�>����J�~�1l5�44��7��
%2�5��p�@	
�a��.`^���(p���$�%���F	xc�p����Q�����V�����z��y�qY��[<3_�c ��<\��>�R�B3><�JDu*�u�#g
O�"'�?h����V�.�uh>�0�����"��)�y��[�)BAz��a_���QS�/����Ayg��b��L�@O:~�s:v��cC!�V%�Ft��HE�1���~Rf����'�:d�Y'�_])"�N1��r��2����M�j�b���e��c���r��z�.�����H9)���<��&�{�������MjE�7�%����4�V���p�e�m�3CN��A���������v���=����l7����.j8�9���a���}|����B���z (�&nH'�7>?g����n��v�Q�%;=�LF|
��P����j[@��6���w�V���y��CaA�M�������l���n���=�\0Q�$6��JL�\�V��6����C3�o}<�����N���!4G	/]f��_N9�����A��;������@�C�:"�k[:��D�������(��R���������U�Czkv]�w7`O�����JN��<2u$�n4w�a;��<�3t�44�+I�N�>���������w��C��%����=�M��7�:��9��]B|�}�6�b��#X[
C���m2(3{E(s��lc���^sZ9?HD��B��X	��=��!�4���G��S>���X86���{���w���9Mk�=��aOg�X`u��+�H�d#�in8#�a�*8���C��T��i��J���@��y�?/�:�>�q �@<�C�M:���fxnR#�����5������=UY<UR�3��af�L\�e|$�g%KX�/��;���c�����~#Ua�_���q�`J�DX�
���s�h��S�Pi�:*R��5��%Sv��a�O���j�LJ��~IE�`�d�	���;��L��'�m���"�l��D$�BL��Pi2��?�,��M_~�����+:\������������A����(���T�������}z�������/�*�r,�{��]{����(��Ng�������^�����U:��]��*��n���[*Y��n�^���_me�/���ScI� r����X��zNq���������I�R�9[<���8����K���}(v�$��(WD4�z�8�����xU�x-,��I�W�y�+�xZ��ii�����z#�����^�������=�T�t��X���8@Y�[��W�e�I	XU����B�}�w~��g��~������QaU�������m���]�u�i���)|���H1���
f@_Y��X����0�>����{��Y�i\�
�_��	m�yB[���'���X��1T��t���n�R��G�(�����3���p�h���U����}��{#J����E�J/_�_�e~uxz�x�_�8:�vD-� ����sX��8�����bA�
��/��7���b�J���(�u��'�t��[�C���{C���[����f�e
a�aC��
NH5@����M����[(�n��9~�g7�W8so2������*4����Tb�I>��
���5��A�tl+ #����+\$�mo��rYjW#�IK5���0��-�8k�Z^�L�����V�Y9�{w`[6BC�k{5<�?�j�22W����j��c8����7�����!�}g�/�vn�NAS'�~�g
&�
N�B}Wx����2-z�����%G)te����	��������X���:�����N������5���-�X)G��5�m�)#�)�Jm76� Od�w�}������1�t�P��d3�'�L{�b�vv�[��m��[)U���jg�<���ZIa2�	�R�H��It���`,_@�6���G�l���������?����;>m4��}�Z�����{�������G�Gs�6�m����~[�%��t���V�s��C�
(55pI����M�xb�������������	��w�
��($��V���tX?z^�H�����ua>}��?���~+��8e����#�fp�+�e�#6�S�_D��D�r�j C�\��}������������h��tn�������>�fM-��H��}�q�py��G����7�X���!�����T�W�D�#�l?'[�.t#���fbTz$I�=yx8��<������6�x
K�9��a����������Vp?���LB_6�E�a���~����h�w��cTa�w�����rc�V� +��s3�g�r�Up����F�^��4�.������nF�4sb?�-�2H���S����>�����cf�w���PL�D���l��2;?�|����)<�3x�	�c!��ley���{�5>A��u�xJ`��j���J�W*�����o@���:>�6#�67�x��c.��_�V�����k������G���Ju�Z��������*����n�_�Y{v��lm��J���m�������T�����Z�g	FP3�Wf@��dDXu����s�OV�iV;7�=���t��l(����/��^�r�Y��^��WJ����8�X������_@��k��^bH����ZV��eD#�)������L�(V=}+^B����=}�&�K��KU:�J\�Y.�Tr�N���N�C{�Nm�W�����mt���94�
���%�Ze���a�@��Z�^j���$��#���4����Y���-_^zY>*b�.�9&������!�����=�b����hs�v���-/av�����H>^�U�WYu��5��z]c��A�v�����
X�v��{8tG������@��
A�6��%{�6�Bi����YbX/��J�4��r��-�&���{_p_�P?:8	E&o���4N�'�����9���Vi��o�~�W�w�������j4)��J�N����xr|J_������AD���i�"�����|~j�R���Z���_��a�����h'$R��	=:"4������9O��qt�?5���g-4�hq�V$=j@����O4��1��t�7�t:>#�h�����H�B��+��j	6Z��S����Q�+��/���%��>�;�B"��Wt^g"
��=t/��{l��&�F�#�}�E3�['��&���:E�
���kq���%��y�a����q�F�y'��,����ky+�}��o��C��n����`����FK9~����e3Zm���Gn��K���a����_Zp�1A�86��>SQ:���?om��A��F��Q����P�y#���y7�f���i��2��{���|�sU�����9&]y�S�o~��&f�*�,�+vQ�M�1{����a3�?JC��t.���y����Z��w�@��8:��!>����{�*�@h�x-���6�����B��� �E�ts��1��PQ^�D*|�J���u�k+(H���h<o|��9��TU����Q��sq���Aw�{"Y5v�NP�n�����	�A��_��
�&^�h|<i����Q���2g\o�W+J�b?m�w�=�*��*�
�d��;�Z`���ku�^������OF�
����k��#�d?J2aw���AS��jt	j���3������"�����^�3v��b�j�p������Ni+y��n'<kd�!P�w��#R��Rw�
�}S�6�
�uql�K�2�%""��Pw U��C"w���<�2q����Bn�����qvySHS��������O�������({!������rK��J�kW��b�����]���)���V�K/A�GF��<y��[����Ork�h�@��+��?5���j�$�@�i����\u�����j�zL�����z����Y�~8W��"T����m_�h��U�<?����:��i�����W�l���L=���&{roj�9�?g�+�JL�[��.=���k�r������N���v��^��k�Z�����v+������W����J5K�[��_����
M=�a�
5���JY@|�:.U�\}&��F�'9"���b�Yz`�O� ���?>7sT%��r|//mk,mJ��6T����3
�������K��`A���RKU���)MlT���FU�jl
�����U�F�47��2e&��0]J��a��u��ZT�<E�g�K)T�v����^���U���;��n�29�HR�+���^�r��IM����>B��N��S����hL�7X���d��/�V���-6EM��iW5{����d��zmu��EI���\�s�m��:=���{����1���3�~D?�������t��j�I�l�G��i
��o��t�;8�R��(��vVXXv�����L�S�9)�[ePgw+������6v��{i��V��O� ��"CW� ��w�Z.�;DW���J����H�&R#Y���6�"��;?�y�B���kjDy���?���8�GSV�4���A��@��x�^�#8i��{7���E�;������v��ru��W,�vK���nmw��VVkI��*�'��]��Jv�v��jG�p�f�+�]AmQ7��H}4>��VVN��/}��2�RK���f9�����'G�]<T"�_���`�m�l?na]	���
>a��(v�����(�N����B
�����[7��-�����s��z�(�	���<�����Y��|���
W�=�A�Lv��Y���m���nH��n	����wt�6�Z ��#������ ��-,$q������~�_+��.�J���8n46��������]��]Dpt���F���5��(zpG�LA�[���J4������
w��� ���2�2��*�u�S��bWC��FV�	��ZN;y���.�m}�ytL����*��X�A��&Ev��( �cg�S�v*�NH�V���������Ii(��I)�:7V���"�~�U?<~�S�
���^�4D�E����o��������'������/'�6��n�+1����d�������B-���������a+�'[��dK*�4KX���*7���6xP��I�d���b)��}����9���
x��%V��W3�/�H3q"yB�L������@.P<�uJ��Z�ZM���I"D��C�B�$&�_�X8B:���$�4�
�:�$��6���rDm�n$�q1g3cX���Q��1TM�g�����.�*@*�F{����`6V��N���5�|���h��1m9����v���v:V�R,���5���Jo�pY��o�H�\FD���e=j��g������g��4�?Q�?��t^���Y�"�� �Z�����aG$�����
�(4G2D���uQ��%WVZ��w�f�� ����H�mxCAX�W����j}����wk��?Y<nR�"�b
.����V���;�����N�Y���H�k�K��Zm�dmU�P����*����N�.U�j{�J
�������������[����I�B�U�
R��puU<%�����^�1�)�~24��*xJ2�����PH!�Zfe
c���H���UVDd8(J����I
N$G:(h)���	)�yC�Dc�E=�'��We�V(�^�B��xm�W4h��*AE^�������4�7� .���|��j�9���_�����S��\�-�_U���g~C���y���9��
rD�V��2��L.:M���	G�{�x�s'2}�jl�� ����F�sv"A���Q.H�|�E�E"��fM�L���Pip^��{TD�����������_���_uu`�m�6�i����&��:qMS�Op0����-Z�w��)������<2h��2��	n�h4x��F]��D��V�U#<R!|�2gt�~�	�8&W��6TtU ��$U����prA���
�c��W#���Ych9����@�����
[\5��b�%�J%q���(r n������}�]?�?a����S�V���r�W�3�d��-��h-�;F~��
^��OE��k������:L�
�N(J_O���5�r��F�`��8���������
_~��������i+q�^�Kf��C��ZyW�^��:t��{��P+e��0��n��[{;��w��E�M<�����U.k�����X�l��u�7H}}}I���\���F�O+
�5������yF"�q�����]*����T]�OJam��x�i��h�&���4osy�+S�_��C7J �W���x�J������k��]�i���(�����t��8�^�����6lqeP��UR_"Re7�4��P����.�,�j��neo��S,����d����j����Z��re�b�l�.iU����X3��M��������;�V-������r#��^���!��* �E�5�v�����W�a�4��)o��H�������z@�� �8�������1^��&��5����Ry��0h_����U����;�.���!��u^�w�('���7�	����2�1^��������Str����}�^�n��s�U��+�gR'��8�M���{>�@�������}2`��AT�/��C?�t�L	�
�������F�����N��z1��S�7YN �����n���K��N���Y%�.������J�T,VK��H���r�ZJ��N)E;�R@����c{Pn[��Q,Wo�.}gjp|���Z� ��%�-�Q�f����-E����!�O'=���;s�
��}.���y:m��S�:~�����5�����{)�dd���b-3C��3����w����)u���|xvX�E���n�|%��O������ �Q�
}�F�8��dB/��Ev�/��"�=��rJs
?�-��C�$/��q1�����i����#k,������XGT��*�C�d�<�J���~�t���8|����+;Gv��t6�����8f��)�� hpXG:+������r���Qn�*=�0��d�X�0���T�j�����q����A����	�;���@6c��~!����}m
�iK��F*`�=H�tB�(������E�r��kxE
���ox����B� ��.��#.����>=xm
��F�K�EcF�?Db��t��X����i�V������,�+�6J�1j1��=�W�*�	l
��"^r��g���$D��:xS`>����#XP��F�����1�$E�f���~�D6u'������������|[��
����g�iS��X
H6�����iA�X�l��� <�?>h��5�����-���O���q�W�1F��������%��������%�c�"}\B��Z�BO%�Z ��B_��<�a���F{�)�u���%�&��w��:��+H�w������@��_*��"�q)1��&��-��uw�]��+JZvTW���-5eZ�yia���h�|rXo�f�[:9m��O�<�~I�ZaP�<`!x�2lr��`���D)�.�=�n��t'��aLvm�)P[���/Qk+Pk��"��l�e�W����e@M��U��B�y��B��F�k =���
5{�Q����9��\� ��o����*�ed�����f>���V��I������b�vT�=%:�{���\��a�y]�?������4&DH�a�H���0t�A��[:��o����Y(��l��3L���I��p����%���f�XQ�Y�3P�x�#�Di9}�#m�-��Q,$�����lg��A4�s����@�\?:r�aHPi�'W-f��B?������8��+Y������d�Zh/�����5Mb�LYD�_�}�||hi+^��Y���J�&��&a����"(��-/���`$hV�F7�������^���)�+��vc`��M�/��n��a&.�,���S��w2�����d"�r2l�Q���Y�1�����5� �_���T�P���S7���)����'���$�Ps��s��,�[��u|p�	�����}h�������<�J�F��b���>]�����Z9�������U7�x�p�@�\���{S���$��"����;/6D��0���c��y��^����M��|���%��A�����/.|�^�wI���o������S��|)�����ma�Ir�����1�*��������.��� ���-R<&��\�+�n���Ev���T���cnu�v?����������9�b�D���TSl]-�H�q��v����S�"1� 
�h� ��?: t��>mp���gA*���=����5q�
���&R�����&;T��{�7�����a��r
s��{h8t�y�XB�����r�/����l^��|<Bwe��G�M�����~�8���Z��U�x�PQ����>��s�7�n�x1�N5T�PxRO��'SN���zq�L��
�D�YC=����Ez��y���@��)���@��g���E�sL�(���8r�|�d2����y�'8��8�����=���o����<_��6L�{�^���X��!]�bD'l"���B���V�S
���u!�A'i���=�b.���;���7
R��B]_�:hG!��d�#��V?#6���bcg�\����qn��m�B��I�W��i�8�����J�k(��,�z<f�,���\�������0��"4A�|xEAH�_+�ne�Bg��<�IZ��@��B��o���&-��-��FE����k��:�BA	����I�
��xN.%��;��%��Q�)�������6��US�0H����/
V���������&]D�v����H��E*?g,�_�����vF~�c#Q\���(7�q���}y�>2f��Y�j�����#c����M��������� �<���
Y������[*��2%��8��5�^3��AR�Vh��bd���{9c8�H��!�G�5Q�5�����k�$$�.WdH��/
b��`}p��^����A�{�&�#�x��f������?9�q�!U!�zA��aM��#�������whl����hk��3�������e.@�P��j�Z�A�ZO��f��<�I
�Z����\'rg��������;��7��)�@�p'A���������Rt2@>�A)���E��+t��W�������"v1�\l�r`#:�����(��"�����A��qt�8���M�]���#�'�yw7}w�oy:�.)���2a�L�=���S�g(�M��!It��_H���v����8������8i���W��%[}b�
J�fq:*4������E�zdx�W^��b���|���}��wO�Rg�)�UE7iX��MB�w���UR/�4.a$K�������u���hA����1�Z�����:f����K����e����3��)e6�sG���'��#��|x�4N1��=d�/8����N�/J�;�����������w���������������F�p��,~rF=�����m���-T���N�n�����p�g"II�����������	E�?��!���c�ct�"H��~:?j����_6~�o���XvH��e��y�D6��0W������Xsdn�����^����(�8�^�}��6�L
���z����vST��L�Fk���k$~+%��;�\�i^�������(����//�i�`�8�C������9���
�k�����
-���NY^�i���tpE�]��D#��r���1F#8@��A��Sf��Y�d��{��+��K���{��H_x�RwGz'�iyg�;E�@��A@���nP��f����V�<�mt�����J+	��+��j&��(�I��%�|�t0F�'9�Qe��Q�4�:SV��#����j�3\ml.v�f\����>L��-�Q�y�e���c�2��E�hq�����6�+���v48}����-*^�*Xw�����'�t�:Uo�����F��@8�9������%���s�,5W^\Y�XC��di�$C�R%6��7+F����������2���[8���";K���PJ(����Za]i�t:�Xf$(I����cl������`����1]=[�8��^E��{���,I~D�s)��7�q���'�}�HLQ=/��B��i�L#�Io����3-�.F�U����
�N���G���&-���@�E10^P<9Q~�w�u[�E
k3�T%�`0F����7��7�h��{��"g���3��*��d`��G�����O�Ke
*������|��5"�n�~�	���[��>���N0O"=�\��z.R�K�U�`�3��0�������fd��Y�����@�\�n��E������������5����k#
�L]�Nz�ko�����3:���,%��m�vzu/�������Ly��K2uR�Z+�����CQPke�#-X���w�mE����=��8Db2r~����6���9�����"UTF���y<G�'��[n/����s�(F8 �P?�Q}.�jC����zx>���0n��!����
K1ob
P�����_��q�\��|��(3�L��/���]/l��m�8�a�

��)�y������E
���q�����.m�+�&`h[���~7�xL�"����@|�u��	���-�M����~����A��U�,+�j�K[I������� ��}��>A__: .�r��`��H�LX�*Ae�y��Ml���]�7�!�!<t���1��F4p:��f�z��:u$�H�0Z�)�4�2��CG���&"��q���$*2WC�d�,n��<��gs�0:R��Tl����g�m[{�b�*��v��Vgn64�W����v�P�H'[�ZVDTJ`����a�avD����2����VA%��;�����]���~�2���)���,6�3.FZ\�eF����W�k*S�9X���H��
?��"#��\�{Z��y�Wd�������^bIb%��
��DCG�Z,L�����;#��{�\w3�k��5���F���;3�F�%,I�[J�
$�[����W�*%Cm��q2�����+���}�k<Ou��9f�<�]�r|r#-���fP�����$�MN0�g����X)o�^PDZ�}��K���'��
������g�k��c�L�L)��b��S�B�K��vz2��q���U�>�b?�wlzIG"v_$`q,X�I�CO@9��>q���,������R+_q�hK>p�Q#� ����T� b���=�4�Y������Qc"K3�N�����f��
k����f���[��*�e�"/�n����*���xE=���c5OC���8�R�"�q��#F������q�D%�8#eO
��z*2*�!��m#������P���a��^�r� s�y�qt��cLt>�nlpq�����\��
���`k�i0����8$���f���0����P���ZD����L��~h�/0�v���f������8�$�H���"�G�����UoC�����>5*�;><���Q�tX$$P�.��(���#�1�LV@���`)�]�:%\e�T��HL	A������q�\-q�@3TT������|��Q�z�#G�j��v
���*�O	
�S�U�&"B���C�f�%z���Zc����+�sG������l<���8�a������E��(�h"p{��w��# ���T@�� \�����(�$�?���O��+����"%\K��\+j�^���}���5N�#�U#�:ft��.���f~{�0X����m�-P�����N�*�JxYh������Z��d�j�I��\3
i���QR�&�D��R�U{�����QD������Z#K�h�W�LD��s��i���e�
���<���xL�������������=��#i�}���R�A���X���J����(n���(�
Q�h^PN���,vG���se�!d<�l�l�����o*K���A_�g
�������*N��I���l���s`�\����%1��Iy���
J�~���o�FG�-0 �)������n����5n���l+�xcdJ���v��A��@t|B�4I�_p���;Pj����`!r��
�T�X&�/�l/��7��.��v�G����e����5�]}�e
����m�_�����~!�z6��>�hY���%/��4������/a���T)�
 g��3�3}u�'��H��hR����&�8w��|pi���2�DL8`"�A���1F�nc`�6�er)'i$��
��R��?��nf��+zKFQ
	u���Y����$���R)K�.�t(�(m�����&@����	����y)���rn�|T%����
�Z�pt�j�B~4� ��v&�E��0�9*d��HV_�rL��7{���P�V����$�������M�,���(���sF�B"CtQ�}o���=����S�+f��@�c���gz�4<#=�~��U�%��bh��u�A)�O�B���%$�)7%c�����h0�dd��8u�_����$�i��������N�
�	�a�<<��^a6�0��w�|���oL#�
h��:;���d�^�/�5IQ�t�k���������U(������H�#��Ty�-R
�p[\�Nc����H<G�N�Z�W�]�#������<;t��0�{���5���"�,gE�0��)3���52�+���������9`W���@�v1u���m9�iDH�:�8�'�
�1�P�ICx |���"#��NPj�&H!6���U#{�a}������W��0@t�L����^��D �RWw	���q]�����Fg��F8"�@�5�Q���,81D�H����H"�@��ah�w����f�������������X�q
)NV)���|�,� 
>�q���
P&��+��	/#����5�������{O'���)����$��.������0�b�Z+�kKu�f����V��6���,������6�Z��P����� 3��z��� �uv��tt�8�.1�
m�D
���k-I���6���$%f�0�1K)����sa1��c�����������
���+��`����w��%-R���w���|�Q��72��^m����c�������?�r�,�T�b-)��s�/�?����)����k��[����Pg�7QR7mR�Q�]|�JV?��,jo��m�hb�+���+�pk5��@d�X0"0�T�����&V*�3�qE�"�a���1�����}��8
��e��G�_��Z�o����GsC|'_;~v�m��C26v�~
��<KE7o���q��*/�L�m"lh���n&���+���B�g��b���o�B8��a����%A�h+z�)��z|���v
e��;�K�u�9 ��P<��i �j5P�T�,�CJ�ike\�P������9J��Z����)NJ�%$L������Ng��F����G��
�;p@
Z�6�I�
� f�%V�8��"f������W
3A���0�^r[0f
`���(p��L��]��,u����$�,�(��0�����B��pcS����8jkphqI_�K�vZ��v���Y|��7��Z�bs�X8W�c�d" �a��NO"R+^��D�
�/��#�����`�mK��)d�/���(%�<��t����"{���_�E�SbT��o@�o����3� [ob��`m�/�(<��|�[{L�u�����=9����a�����z�x[���p	o�~l5Pi�;������]����R�3~(k�k\B�o|��pF�0����Lh*��l�qJ���%�`_h>�[ae4Xav����]@?�|$��G�<0u�cVz���)�G���W1�6b���j���X��G3���{#PiX��92��2����h��T�����?��� ��dw�M��~Dd�$�Ek`��
���-!&���t6�H�����'��@���*��������N�y|���+Gu*�T�(�I�U��r%���S�=b�.9&[>IsiY����]h����lHU8n-�/,I8'hB*��/+�0Y+>�������rPDZ_!�t����l�v��,���{9���r�\[���8s�Db�����i�v�,��k����qgj#A��[z�`p#�K��*)��\���f����s*dQ�c#d�G���l����'e��/��;Sg���iv*�����%R�@��\o�����9�f���p���]�]�z�D��V�b��,�&1�����Z�i$���%2L���1q���h��S��B�=�/���;	��� w��V\�T������'��������J��:	�g#k�_�A.����G����$#���,�Z��������#	��	����Mq�25�>��a���g���5\IZy��L��jl=x�E�@����m��e�#�o��u&���G�0�z��C9�Xp��Z�$�K��8�wp��/��D�����y�P��T04�\B]e��ym�BUo�@ts�rL�&N1#%w�X`���Jl�3�^6	W�p� ��O�H�Q�F}����3pS���+
�_hM�y��$����
U�	y���|
��"_�A��r������T�Bu�Q�*T��z����!S��,=��3��Cn��C.cq=���W�s)J����ljg��E3�kalp��?�91h��ke�b�g���n�����J���8�(]V�'V:0I�����t%*��QD���j����F�)��(Z�@�������	�mCl-�5���Xv�3H'���6��������}:d�O��IV!z:6��9�s��3_�E���o��^�`�y�-�@�oS>�7B��������l$e�r�~~c%�K�M��n�7GX:T���~*+���a���
?�}?YN �&�R���<����}������H���?~l�E����s"�}[���vc7b�^����<^x���atm@���I����oW�q&m3�
��D��4��;���b\BH�������I����}����$Q��]�w������3�:���"#Tt� r�����dX��+�Dk0���+���h��"����k�.��hw�XT�����j #H����4#���=�����,�Hb�����Q(���z�,��������`$6mc��y5�H�3�m4��o���m�&��!�3�f^k�m{<2\>��&Y�Fo�T��2�5��F��3�0b_����+O}�9Jh.��Vu�t&�:��+�n��jH�{����wd�2�
��&K�����dqD�Cp�j���*|���H9v+���o�
�C1SRyU$�|,m�q������8e��r�i$�7�v�����x��h�'�N��K��H~��(�z��)R&������*��h>�$��)���3�����G���tOE12�T$�c�5��jy���F�LP�;���&��d}�(���A/{�<p�0���,�Gf�h#Is�[	@����W/�'��T3u�KqC���N'��g8�%�=�d�B�3dT������V�L72L7fJ�1�����IC*�i+��)nI��T[m����G{�z7�<z����$�!4yZ�k������]l��|��N�^g{��������D��h��S�Kr�����_;|���������/_hE^�#C��.C<x+hJR�)o����Ge������a����SOD# �.�8R.�F��@����4��FX��C!9����yJI�8\�
E��CBR�$@
"��UI�S"��P/B�(�V�����!��k���Y�c��n)���x	�-1-�y�zs24dY �"Q@�H���FG�H�PZ�R�#�"�D=����/�"����2��+�����t-C8����20@#�M�Y���OY�{1�@A�(]8�(<���Uy5�7�$���wt�J��n���~Q���{-���{��]%�|�<m��E&@�B�(2�#T�+��<��H4��D�i�K�h�S�������V"T{OO'K�C;s��:����+���~��[�sW��v�T_n(����x!�[��N 0��e'��|M{�e��.�.Nz�	��K��N�$���h�4vf�
��������y�����1����S/�.d�?���F��6�[f�c','[wN�K�h.��H--��)M-��Ce�mIE�gY�%-�"�c��j�p�q ����	�$�oyo��C��v�{g[��[g|�/�~|��2��H��E�������VqcuoL&��:94�E���~B������������G������A����5�v�s$y�Zd�EX3l:V�����t�M�.����~�d� ���V)
Q@��Q��|�/t��D�1�c�I��R��%KD��uH	c)���M�[�Ml�#&v/PUk���� �t\k��^�W���������=2Rm GH���0G��I$���Yc�����E��G;>i�Rv/j?�$@q��x��C|��-���g�C9����o�dM�7(������>���7�$I��x`��$',���V$�ax��}!Gs�8Gi5�H�	H�!�2�D���E)SfKE��A Dc]���B^;�����5�ec;�X�$�������d�S����F�KC�G��F�,�Gs!����d�:�gt�f�q��cG��1�2T0����i���K��%��q��9�L������y���5��:$S�F�DJ;�9B��6��]N�	�b��-���5G|��� �]�Fwg0��<#S�Hfm���k�����)����������|�:?���x@����E�)G��,FBq{�����2#QC�����XD�@�P���WJ�Y:)3�PeO"Y'��s�1'���M4���a��#1�,�+�
O����4n�T~e� ��c��;����Q�;0m��$5_��S�`�M�~�u[%�A-�f[5���pg��*�kf��0�Ya��Gbh}oL:4�5Gt&5��-��\?HaL+�XS<��1����XN��[����M#���bWdR���L�?3r�y|�L��g#pL��`D�uq�VT.'��~���!6�l��7�5��&N������r��p�S���6��C�a�JH����<
K��K�������+e*�%�W��adq<�J������^�Ws�����������T���G�^���KN?���{@���C
|4hN��F"����
��ts���%J�w��c�:���M��X�4����
o���R���h>C�
��bB�����cT��v�Z]��`����
�h�?u6b��s�k�4�Q@Q��J�k�SJTCP-�],��!/0���0#��5�<!4��*����#��24���2b���m�x"f�z�ad��"���"O�mN+I?�^[^����F`Z*t�eWf�t"]�g���
��I�:�R��40F�KT��UU�m�S�tw\�]��^WjB�'���$W�E>���}�dk���z�_���l��Uet�W���_'fw��&��j]�Y>R���r�j3
\E�qh���D����&0�bbz��Y��!�U�|\S��;���
P'�I�H"�<*ETCDhm�����p�B�+!��(��|����b��o�tR��N�i����0���Pnb)����-�����14m1�'i���rE��d��>��%���G���C?P:� �dJ��Q5�
�����w#r�3�R�K
ee��Y����T�u�:��������LS
s(Q@-D�Y��������h���y3� 8���)�����M.!�6���DI�z�B�#�~�8ow��������xI"�|K25�L;��.�
��f���x�Buc����n��/@�����&��Dzyr��-����kM�r�����4��v�+�3kZ�tL8�V���B�h�(T��$c��	*��Q��O+�����p�9?9l���O����$���2�p���k�8�u�z�Z�c����s�I�@�~P����:a0P��vSE�����	���,������x�������2�7���tzx���Q�v<��{����6�t�;1�W��h���
gJb����++���<����n��&�H���{Z!,�(�����mc��������-��[��Q����Uo��������m\����]�����MA5q�N	����?�HgQ�M��q��L��*��4"���6��rF��Y���R���?~��H��2��Z2�g��[�w8zk���I������%�f���8��FNp<�cx6Vg�s�$��,�$+�K��Z�~�W���)S����n"�Q#���3���^r�N\��X�Y(K�_��Tm ���L�
�i�(Qy��"���S�DM���P���x�-��U�o���zM��E8��E�Fod���ua�����3��M���2y�
v�^V�)��8>��dY�_&�~tMw�������K?8	Yy~U���>7D����0C~�9�IH��X�G��#Q?<kL�K�)W�P�����$�������~�mYSB��E^����o7/C(�� [��G��`\���T�OB��2�c:��
���J�����R�TAb��E�,Z?QF<���B������u���������@:S'�."%�[�u��^�"�N{��� ��{���c'K�a�x����t@�d�����4[�(&W$�T�h�V���P��/�m��:��ppm��(t�.�E�c�����X�.�t��E�pll
[��H�����L=�mj�t��&�BE���)��k�Z�����.2�v��<�l��m��}��A�m�$���$H|#(��8hS�f�.w��o�|�b�G��>����BI���]�1�:Sc|�FP�{q�{'�7��Gja��1���I��Jn����p��7�IE�>^��O�����;��3���i��-o�+`
�u��>����5����%���:��w"����	�G2B�
E�u_��t�,i|��)��F&�.8�&��7�<�E1&gR���;��c��
v&���������B����m�����~�2�����o�e���#Bu�����2f��D/�dO���Y���"c�0����s��Glm6H��I=(P��� 	iylw�@�f�x��i�����c�!�O���1�I(\�0����kf��3B�F1�.
HF%�7�D�-J10�c���g���\��w�e)�r��k����;I���<bkBZ�&Id�0'TV�	~�8:#��]���(1s����ZA��}�)��;���g���{��*�q&����p:�n�R��� I�vt$I���/��I9��&Rp�Q����9iR_S�����e!7��Y��.<w2��\�W>���_\�:?��A������~/��n�h�w=Y6j�0b:B���{��KBR����������G������Ahn�p2V/�k��������w
�	������P�"zv+����`Rz�fe�"�2��)��=�q��g?��7bYO�&���|E��D��\�VNl.�L
R/��(����OL��������)��4b�0�1���k�Z���~#�KIX����S��&��'�"��.���ilQ%B�Q��"&1�(O�{B���|����q��8�����}Td��D�{�#m�7
����V���%�Ay@��A�Jr@��I@�[�3�=cv�sX{R����h��Sl?�eSh%��m�I�c�xR!�5e��K��������:e��LQ�9M5��[���fU���4��`Y�>p&S��B�2�:����������]��1
�
_���N=�85������v�6��n���>���a�H��t$I+w6����,+�=�~������W����(��<�wp��+��Z���4��:w��\�8#'P��
��M�5����(Q'o)bd�����Q�a���R��� �Y>��5���������q��a����tK���O)�1�/2��t�Bc0�H
][R��j��~g��|e�io����1�����2+��caL�.Tt��]1:-�sV�7�j���VC����#�~<�5�8;k�X�?RN���0B��Et"�����2K�����B���Sy�T�yc�I��#z���6���������-���fe�wo���1�T��BNF��4�����57��tW��yq�[q�wT|6�$��?OF_d�SF��__�g�����L��?�����r�M���9�g'M��T�yx�nL#�������o������~+��� ����<����<{��x(.`��������b���X������g�����0�Q��N���<R)Hbv�J�q|H��Zu<��
��f�U��:�}�0w\�H9j���GL��2JH�A"vh#H�&'����5���q��o���28w�B�G������n���I��^�Bv�CJ0{���E vm��<�J��� ������o��|�z��<�(��{|��v=�����wY��>w���-v��i�[]��q��!���bei��
��m>Fl��-�X0c��I��7��wVg���L�'{���9����s$�Z���Z���Y�����G����Z��k�<���>}:m��V�q�D��*)��cUE�������x�(:�&a��2�a��>L�,�h�"�VeM�3��s����h��@��9td�2�E���HC��;�)��TNa�XD��|A�`$��zF��������y�52�tqL*�>��L��j���`�a���!�:x �i���|�����xP�b��)���a��\���t��>{�*��v�K�a�kE^�~j�>�@��DnmEr"
�O�rz�0�\�#Tf���q����Au�jmB�Nxg�
9YTpH���W��|�S�o�bx/�82T���2������������3�^�7��(��
��i��2�^�
�X��\��Tf%r�B�&��[��z�����ox���������H�UP��a�o�p��������ocf�Rf�9++"��g���"H�~����c��V" ��H-�?i4����u�v2�Oe�/FG��
Y���%�}1`��V�����z������p�)F6_Z3��qb���4^�b[�x�)��#EM���s9�|B�%!5C��K�I��P��������CY����nw��nX�Aei����������Z"���0�U��R��|�����d�H����9TL3ZQ������%,�n�s�`0n!4����{IR�?�1�\�e�<l^h>�W!���W��A����']%��"���G�����<:�H;�.�c���b�P��;��z(tr�[T�A��s��u����mF���d��T�����@a<A��R�:/������+��E��=�������#Z[�={S���Eg�Wf������S�]�b�/��GK����Q_��6J?5G]�;[���Y�N��LUd��e����a����
H�yv���p#����'��s�X�������� 'O�M.o�Djk��0����G�;�C^#cu^
�T�QH�\@���!��a�vf����4+�����j����:������F�S��Q\������,�|��� S����L��$�<x�����������i�<hk�������f'���
��>IY�rA�����(W��=eGe���2=���*��m�FDz����V�b��u����e�2�"OI�W!C�dK]3�%"]�b*@(�F	���C�Zd)��+t@B��V��L���d����(G�(�|
:
�3�j�����"M�&��,o���7c���#����>�3h�0��F���-"�9RNrs�o�KA'\O�$)��@�ZNJ�� �8�h!RA�cb���fF?��I�h��4P#���
����,�E��4uI�BDT�L����`�EyfO,�q�{��c�������]�������b�b�.�
��#�H�	0�d����(c����X9�A�}�������2/�U�����d��' 7C���X�~9i���?4�����ON���;_���&9���[���;�x��JE~t�x�<j�d�SR���wYh��v�y�N��<���W^���+��i�R��l� Yz��J.��gN���i���n���2c���!��@�_�C�r����!mm�'�V�K���}��s�!zI���N�������P���s>����O:i�@M6���6���,���g�s�e�)������xx��Y����l����^"��c�a���������0fr{Q��W��:g�I���Y��:?��+kj^l��R�#�y�m#+�}��rG��QF.�v��(�c�|��\�2G���[RF�$�YV��Z(>'�"�r�����4��~B����id�]�9���)M����>�/�&s"�s��+�T�-������ro�2X9�eU������i�������:&l�+1�j��D��%^����,H���)�M~���lV	���OO[H(t�ib�a���!��3���CBOqj����9�h4/�r#sV�szY�!�}�/��XEE6���B��hY�7���	7bO� (���#|����g�j2�O�����4�����t�2���WC`��)0�aa	����)Gj����&A.
n��
���F@��(�Q%��|��|[�u8�#~>��R���a�U��c��.�8�,�l<�a�"z��
��c�IQszx�36�Opp��<up�Fx��y�2,�
,u��J�|�3+DoF�9_dd�c�����c���Z�V$������y~6�:�/�L5�B&&��g5%������4=���OO
,�]�HQ7u$#3�Xj]2�C�!e�P=�����G�}@s�����@pc.Saz��[o�,��v]��A���=L��)m�7��;O�xk����t��e��m���������y}�aN%ZG=�k�1T�8��JZ���'�K����Bp�|)s��/�������3��*��Vv:[{�]�S,v�;�jg�V��r��]��nnn�������<=������Ra[l������*�G��k��F����Z�k��?�u����v#����*SRs%udbB��$��
� ���VK�=�q�s.�`��s��X9	��y��p��U!/tB�7��KOHYRn��W��J�9
+����E;�T����cl�/t�]b R��@�����X���arO]XK6���]-����q�}v~rr��A�\P+��?;������	#��LA�.���|N���"S�z���%,P��VB5"�A��>��fC��%$f��2������u�R��7��lr���[\����)���^�T,�vz�?��2��
M���m���l�
�	��xZ�g�3��n��5$��%/;���]jI����I��r�O\�h~[�]X����h�����O���&�K4j;g"�#�a.�.�
zG�������f��h�7�Z����~#�9`��;p���4�X�2�W��v��O�"G��<X�H{C���U$�,9�0��
�VJ���{�^�{�Zi�goo�[;�{{[�N���^�v���,��U���w�'!%@���R$��������'����������f�� '���L}�"��%���{#�IY�-8@����Ul����Q�2�a�����������f��v�_��
wl��^���m�Z�HCY�)D��S��O^`*3�
[J�{��e�xR�1e����M]��vLH4���2!�����N�i��VxP��wv��[�b��gU;;{��L<0�����K{�,.�'���b�q'A*��������t�J��!u��k ���JLYwU������U�	�����:����>�����Fa�Z��HPa��d����n�T��f.��N�j�ep9���.'}�rb��z��B��Q?�^)z;m����u�wX�x��G�����X��Rr�:�;[�,p��N�K�-��W,����e����[q;qV��{;�s�������W��yh�?�q$;�(V��1�uaCE#���$�t�r�EJ���w�����A��������1@M������		h2���S�����
��MY��PJ%���U��"����T�m����BlY�zM�eh����VHEw�g��=�#\��|��':�i��"L,x��*���k�>IVZD<������u��Xym����=��?��Y�F��J���=�z���:���N�����������$���;�Q��#���D���bm�i+��n�L�7�*[{��+��2C�79�������j�Ph��o>�m�$�p�Y�$��N�����H�!a�$�(F�<�p�'	�\�C=/�w��������@*$uR�pM�����������&��n�E��p��o���_�X�mx��Sh$ ��e�2$��7�)���d�'T�I�h(ocx,��$����*�rw;��V�\b��g���f1���9JE����B_A
y������!�r_K��I>�
{!".`8�y�k�����b�#������tK@�~75�F�o���r�d�-�]�F_��_��)��y��.����S(�Bh����4=Iq�5a�'�#���;���Cx��)���P���:��J�����]�7?~���a�^E��F��(��-����&��
�k��8�!o9���'����hIH&����Q�F9EG'#����b'N��Iboow��/�K;���v�[���N�I"���I"�$������]D>��8�l��"a���a��p�{T��6��+g��!����i�
���	d�@�t4��=�g�l�|�����>f�%�����QfC)tk������^$�����7�uZv|DkP���H\����#��O���A�����9\���)M����k
�9��@{��X}����N�T�����nU���������n
N��a`R�{����^�}+$���u�5�Ux�w'���M�-i����v|����x�;l_>����$���x���M	�.m�	�~X�?p�����j~�gQ���>k�"��j�=2I�[{)����
/�������w$k�b�{M����X�{�vN�$��	�����������+�9&�fo�:0�.���d[�@��_x�|a��'���0o��r��X��c��������Ea.[��B�]ii9�+=�B�Q�C��5�*��<4�i%����8�k�l�v?��������X�<�G��@�<\�[hs��d+���]����Z����$kf�������js��
������J���|�5!"$��g2�:���
���������\�����fJe��0�@�]!�%���y ��&���w��w-����GX�!'z��(R(G�*������S��%���+����;0>4�-�S'N36��6e���7��)�N��-�*-���aZ��c�BJ�<��=����[c�uLA%U ���M�'��$F%�Fq�h�a��Z�p�[�g�.�{$�n�J[��]6pk�m��3YX>p%�n����t�n�nm?w���+p��i����]�e�n�^�npw�jm{������=����/�Q��5���
mFJq�Nf-GJI~���R���=T����n��Q�����m�X�4���
Y�l�����_���^I�Z�{���%�d;d-��_E��-����������#�b��;���,�e������v�7�|�7�W�B�t�I��
N|�Z�9���Vo�����n	��C�9����.������q �}4�=�'[rV�K{h����ECfl��E�AT��@������.���l���n�\���b�g�\|����
%{gdy7�����z�#h�}�F}K�k���
nb�m��|.v���+�x���9�(����4�`���0Lp�W��3K��zG������P�B���'��3�9G�����������@?\���e�lW���������f�'�+����j����c��U?���~�Qr��`F�)PJks��\J��)
�K���Y{;��j�S.�v��z�j��v����t��%���XE�����m����6�F�����ne*o��g9'4~[������|��l�c��7vG�N��������l��R���(!e70Z1u�t���	ZB��+U��++���[�����B�Z��1���K��Up�[�����(Q�f���l����Pu��"��)��TD	^�J��Z�� e-�E�!i��]���x���Uj{e{����6����'Q����M7�Y!Z6�	l���w�dD��7o���U^�Bm,�����w�Z�n]�����f�����ik����:5K:u�r�]����
����_�Z�V����A�h�%r]wP�I�f@�2��MaM�����{�^�_�8�_���j����p�g�lp+�����
L��-���e2�-Fq����A�U-_��:,�/:-&~���O��7��C�/�|��B�=��6'*Q���(zl z ?Wyr|���E��(6����!R�R0'#�^��[W����q��=���g�>�RBn��n�*��������[��v�i��Z
���R$����j�!��\!T!����o�P#J�^�w�p>x�t����O}z�����s�>f��7������h��w��
��A�>Pzgw{c��]�G����X������������9��\�y��q���]%�#��MF&���DR;!�4���=|r$�9zC,��)�=	�^�����U�v��kyfJ	��;���J�T���[{�row����`yJK)X�R����B��u��Tn�@�z=��~���m�A���~��Z�
.�[�b����]w��C��p����GW���-���F��*�iu�E��' ����nu)q��?0��oOp�h&B���7�����N�]t
����]>�Q��3�y5T>u��-��'V����J�s�v��\�d���_cO���~d#�f�~�s=4wJ�t������(�@��6"p�����u����>�{���|V��`�O����YZN�1_e75{js��
���W��Z��[���v������/lh���A�m��{�l!Ob�snHc���q���M{��i�4�	i�w\�tt�:�����l;q���6����>���jd_�nK�}�P��	�1��O9�l5����0�F�#�g_��~`��O��l���D�%Li��BCDG|P�;
C�� 0�9���� }`��H�����:�\�~����M�e0�iUii�x`rH�=�V��F�m^V�����G@8�b�iW�7<�f���f�m�dIR�[�N���������+���nu
IIi(IRR
��Zb7�LSZD	XM�V+�/CF��{�TD9���rZ����j`�4���T
�8��(y���Tu�g~HC����r�|��7���a�J��)�
`e(1�C��"�rT��j���49�T ��W���+����T(�fE��@��j<^�S&�=��I�A�P�9��8��������K�G���z,��<������9(<U�������&(J����tx�����*�P�3U�FnJF���n��5;��R���j�X�)�WW���c�X}Y*�v6��o��>y���tF�@d�T�v}�y�P�,�l��O�V����*Ww��;��vi�O�J��S��(-k���M0$���\����r�����a�^eg����[�J��$���W���;���V���]�Vwz{e�h��=�Q*����
��*6�J�0G��]~�����������������W�LA���>��z�$��W��W[�b��W*��MH�x%�qRo������� h��A�jO����,��(K����8
61F�+��`?�&6����������9/��fc�u{��_������O<
n
�
�����(�!���1�kY#�WW�>�p���]����c��0��cH��������/��������V������cw����cAH�����H�V������^x�d�������WP�0�Y���PF�
w���H�����*���ar9%E���Wa�:�.O����G����C���1���X^��������VM�}
f�@S�'�"����k,��W�KA�|;J�������u�'�
�z�����-�_��KH0��j3
k�G�`6��*c�34�>�K[��*3�L�3�8o���@#���&�\��1w^5�SQ��\\aqi�z	� VE�<!�x��wOF�a39�y
(�D�
������M��D�/��=	P&^j������o$��{SX����Y���>�H�7�Z�t�C�%�,���Y.h��Fv
�dd��}����<l�7��9�F���B�������DF2<��]�&�&�A�Cn-_ia�>�(���;t�=��2��1�5�9�(�R"
IG�e���1<��S���k������F��23�����zq0�~fB)��Y*�W��H
���������g�G��G������i,���3"4���=��i�����vP5��1���|hO�g4����~���
{L	l9�ya�����`�
����b�R&5>%W����~F��@�|��z�5�'"�~�f�T�W[��W�� =������A�D��.��V���)��".�t-/�l�k^O���������i��\�E^'-b����8���O�L.�E�Fi[��I�z���M�J�*6�_Vy���
(_.��d���NE���$��nY�ND����R��A6��s�3��.,��x�n�dmmUz�bi�\�W+��B�-M�9g�b�0���R����Pi�lT��!J����d#���n��jfEF��=�#k,U�8#�u��&����o�Z�sorQ`v�5Tf��[y��j�k=5�^��]A~w�2��"�Hr��PJMAu�j��i���;�|�iP��?�Z6T`8w�����2	J��2'm����)#k.�F_E�~��:����^���\��.��mi������-�Z�V�����o�^k?S���[��R*���4��a	��2�t����R���{Gbwg��1��`��o���8�x��n�����jR_���
or>Z�c	�|U�y�i0 n��d�E�H�
�h\P#2�>0�G*:h���93>8�Da����l��5��L�HQ9�3�qi��������XYI_N�)=�"�r��T�GVW���vvI�c�F?�b���<a�^	��E���g0)��]��t,�LM`r�`�Eox�������G��q��H��"6����i!����e"X�j%����)�s��K�m>���y�<���$|��p/O���D�����r�g�w$������W�SDP��j�Q�WB������
����YV�j�&n�u����
����/�)��V�!	~�`����Ue|�H��9z$4(���w���|k0�/^![X:���x��0,f}��kD:�s�3�G���uN.�U�$II�}q����?����09��������t>Gs�Q\��3q�J����l��{�C/V��4Q�:�������2��+K�0 ��R�He�v+���u�`��@C�I9���.�����[1\��ASfK��0��o"����Y�h����C;�g ������X���Vd$�,���=y���#���5�/�J^��`�<|�>!����@ss���3�����b�p�#��
V$2�Be���X\�}�3�(�t>��6�A��03�����K��������bF0��~��Np~]3�����B��d+)�e%J@�]�,�d��y�����iL/��<��H6��
[)	.�Or��@Zf>/�x�`�B�x=������pZ�5�����6G�(��������6��
�rN0��m�]��zDW�a�S���}v�S���J&���(i�
��.5@H?
-�/�+�w%U�Q����t�����]���2z?]�h���	~���Bp9����@6��IJ�2���"�'��+E��	����Q.W*����4���
f�x��Tfp@��$���&|t2�F�h�bap[mF�W��2�JI����G+���5��%����e�{o��G����zAE��e%>K����4���]���8U�9�t�L����G��A���<�o!�����{�/��?�d�O�qY���K��C��/KXU��	QqIg4������&
h(RX��O���^���o���$��D*�]+��.FEIA��	��H29e��r����)�
i����2�6�l���M9����	���L��f��������]���|~d���W�/^���x�M�>;��q�w����Z��J��w�����&����G���e'��O���wG����pjx;��Z��������������������Y�7Y������eb�G�����zF�.mv�yb@�ybv���y��8}f�Mj��V#�y��1�y�����b��r��<��2��,�����[i�3�_�x����
RX���>�Fj�'���gG�X_S0��s6���TL�����IL��i��30E�I�:S�y*���)�
o�1EVI��ez����U�0�ga
���G*H�0����)�\c���`�.\���#�U�R��<�B9������T���[�)[[�b����tv:�~m�����)��a!v)L2��V�v����D�2������u���5��P�X�F����Q���_?�`�����6 �yce�b��m��������_�m'�6qc
�R���)� R�.��xrh�l�FY��Y�0�~P~ ]�J�t���a3�iP�e2O�VdFL�tOu�4��6���?���5����ws�M��:g�������������5�`I5-��tX�!D��!�q�Qx=	����9	]�� ��vJp|���<�����v�|,7�1G
����d�n�������������������c����P?��?���6�0A;L�/d��K�~Q�z�<p���F6���0�\{4��/L��.����������x,�,G���;5��4����
x���}�8l��P����!�0o\���antY�=�(c9�?�Hx��7?����J��IM��Z�)^���F����<0p?�;#������c��>Z0�������Se�86�8��q`e�Jw��H��v%3k��N�<m��g���4����@����������A��6�JA�<L+H��Z��ax�v�]}�{����V�SM]���6<�"�F�+��Jq���3[�@���)%p�%h��
<��c�3��������D^����h�,�N�����"
I��
_4�7��u&}�V����(�G�U~{ MXf����c�g"�� n?8����*1���.�=B8���d��Z|�+:A	�iN�A�5j���'��)J�,���xRQ/�:��hd���Y8,4��vs�>��C�,�1�tZ�y[du$��2_PQ|�E��I���������m;oo����# ���x�xJ�d�����0?b�2�.s�=��D%�y���{62������]��x�Y:(���(�4-6�_�)J3��W�����X��Q�1w��4������=9D7G7�2�����gh�)?���Veu���1M�#�]��$B-��6KBY������s14O`9#9�8�x���B�!��R��)��S�	Ofd=��3c&	!hZGX����dv�������X��XS����4�/�2+�3&�)rI����v���w��2������Kx���8�9�Ye<O 	(���
r"��<g���q��J���:��vJ#��P�Ji�X��4��VD��H�lX���2��
�M��\�9OM[������L����������=�I�-���"�L�R���X/�[���D����'�Np�c�6���YD��*h��(_���W����uz�b����y��B�J�� ��pL�><�
O�s��fE��jhJz��:�6�����S�0�L������/�<�EN%�<��nuC�]8#�K�p����+��x1e��z�0�������)}e��y
F�0\�"v�Ro@��V7���X�X����#f�n�eV$^�)qUxAMI�1���$���gZ�O3�O��KSx�6x7�f�>��B����M��0
K���������Y��f��bK>�,���L-�l<�Tld���|���\��G�T��g��g��g�������^(����5�JB:�Y�'��-�)��ymX,k��t ����^�$#����3s_ei�L�����1EP�iXj�r���Jm���kB��5���K��N~���a�����_�e�B%���R����W�N�M%6�+Z�����{�j�0���(����r[	Un���z�b���]!�����WT�\�5#��
ue�[Cs��
��Wm�����By�~mL�0�}������^�M&��r�W�qP����H�{�d^�U�Cl���J�b��}��t��+���Mj_q�L+�]~0����(S?��i�����^G�`������<dVmx��&'*��Q�JF����9@�X�f�r��-�+�%�a��N�2b�A�`Ft�3	dx$2��|�<a�V�d�:,�E
��Ng�];3�+��e|��a��J������R~����$e��$��J��1���g��Y�]��}�S�<t��
O��
�����VE�s(��� Jn�B�S�aK��*�Hs�h�g$��og(��:.A�5�W:�`+O	�l-��d���G6�&������
����6wI�0m$t<�:��X|$S(�8�W������70�������CI8Z���Hye}]�j�(p-�Z�'�����F���a��Y��R��9�#l��}u����pE�D#we%*b��(��
G�%v�>�a���%�&���*��{E9����'���J�Ii�.�+;��\��
����(I~1K���R>I�2$�T��Q��6!�VVf����BI]-��f���4�#v�i�3T�����Dl��l=�����>XQ�tmg,��%mOi18�u�#^W���^��%�����1�������TO���#������9y��^�-R��H��6~����~!7e>F�x]�_cn6��� ~��8:-�����#����JT�0O,����S���,��N#�$��[
^�Q{y�2:&g�\�b=�,HX,TzU�/B�J�X�D���r��G�G(j�J��B��!�G
�$QM���e~�
b�I%::t.@Z�7����,�e��)���A2v�h58r+QT�_��
����M�.,=#����r����6��L<�'�������G�k��)�^�d������gI��[���R��e{����z�"��;v��[��c���Sv����4�zr���3��h���]��N�P��E����'X��e�HN@��x��/E�r6'4��}I�``��D�
���y��j�	�[\�g�%�eW!u��MI��W�6z��)�bt���F#���G�cg���VV�bC�Q�n:��A��WE�cS�;�)�����d���|�����:q���%�G����=�P��\<�b�]�tY<�\�Q�eu)A�$��i
g��9�
������^���/�71P����#�2@��"���
�G��6(lZ���]<x{�l�a���5u�m^o�������r��yj��p��aa��T����"�����e�&���g>���)���-�Sd%U����K�[M9��z�9���4���&)����P�[Q�y�O���m��b>�MP��~T�8��cF���6�:�����8[F�@��+9v�����j~A�*�O����i��q�����=1	�,�Ve�������2���e��g��]����a���{#�G=�� ���$G�s{,�	^T�s��	�gQ%UN\�2��yX7:��c��z0�G�����Zu�V�?��"�E|:r����r���ox+�R�q�$
-4�2I��3��_�9��/���d:����%���3�[�S5�����]���Z����~��!~��8��-��A6�1<i��sdDP�7M�����kz�?��qKAC"�Z6�g-*h�'�M�e����O�VC��+.f}��B����F9 �����M��2X-6@MH��W�F�Y�����AE�nv��:���?%�D��5g&p�:��]��%������Cd����D��0/�Y�',��)��GZq hpe@�)K�F;���Uf��D����6��C�;F/&IBa��o���]�X��:n$-����Y��@B
���8����Wd^eF���|��,��0���(�y�w����2`�|�/[12+�2�^hP����{��]�e����a^S\�����5�|(��G�'i�6k��34u08;e7UT]6sMf�!t�Y�<���5g��_���#�B�0|�F6����2���5��y��[�����Q�\'d�y�Xz������������m��K��M����N��1B�#�<�<��	�
}n������ro(E��kJ�@�	�x<p0�j&M~��X�4W��F��K��*0�;�����J>�Qu�X�����z�%Q?:���{�l�x�OT��Q#����4Y	c����_�T5�{��D� �)k����{m��Zr�IF�a��,���|��H���%�V# U#�H�l�^���*@5r}�2��	��V&hld(sh���p6O���Z����-�f ��kf���	� ���}:�� �h��iLM9#�V#��T�P�:M�H4~n���0g;��A�<�����#(j'���k�nX��
A�\T�l�����5F�x���F�R�t���M��*�y�l��;��ZGd&�g��'
����1e�pL)�+;9qezo�ZS����P�c�`��S��e���Ts���uY*E7�O������6���ad�p��s��E�ks�[��0o9�c��.6����CSM�J<x��5�U�D
D�+�H��&��|�:-���d���5JkN"$�����U��d���7c1�#w���U$��%�������5�u"�����T��n��[�����:���7d��a"X����g����J��"&�o��s	"�i#����<
|��>�p6�����%��taW9�Apz�v0jy�u~z�<z���9�;e$RJw�;��m15�m����=����8��w�j+=���Z�K,ojh�LSxe�-�a��������=�5Q��#���\k�n���(���>��^Q�nO�hN�o:�����S��A�~&rk+R[6��������F0C���qr7�b
�O���$�q�����,�%?������!P{�k���9�Ki;��x��*A���2�Wx�!�Qgic��N�?�(m�
����=���QxE��h��=�����|W=�&�x^a���=�-��`�=F�5H���I��f^����d�PW+�e��EHz� ��L��u::n�E
�_S���q��c��s�$(�5�r�@2�����r��3F�X���+��&����3�W:<��y��0�D�YL8���Ly(����2��,���*������x�����j����~��[Xk��#�NZ�j�w���>�T���8J��E�����F=5G&%�F� C
��9E��4Z�G�o���f�T	R�����O�����cC��FZb�eU����C�X���r�/B�zJ�7�;��R��<�KS�NsL�6U�AvT�Lr�)�T��pg{;���q��x�K:]�?��zf0
}|�<s=�����bx��j8~�J�>��{J�8�U����z��e:�-
�������-(�_4���-�����t���XTT�*9�D���PB�r�?)���YA�g(��V���r�W>�gUy�R�����>���~�d�2WR��� ��vw!`Q����N4)�D�3g	����n�Z����K[F[{�����e��7�
16!m�61�8�����R/ut��{���KA��S9�E"C_g3v?%��hT�F�gn�R!d������I�J������ @���<����C��&�"�RSf�\�M"H���-�������[�J��gVP����@�;�Z���������S$����MRoNM��!k�R������� �%so�}����,��(��4�{�m�����&�7 T,���@��F��J=l�j9���2`i,�T�(�a /����X6�X{����?�Y��U0��eI����f���]5H�{�O<z
0ji.���5kN��T�\���6[�64��d�QW�T���HE�H����C\��,��c�q9������[�gb�$�I�v�R"�V�P�R��t�+�WU"�g��yV��<���i6���!���&����\{(��O�AQ4"��^Bu�:<~a`j�)T_Q'�M ��6�������H2���	����1%t'�:�5��gD8Q���5|T�gi8����ab8������"���}w0p�����W���cg��=&�:=�V��z�|+K �R�t��R4I�0����EJY�h2EG�O,J��U�O�Y�����X����

�hI�,~����%(�s�W�X.��D�����A
����s�a��/��t�R2��=0��6��4�>Ro������c��X/RE{�a�q(�&m{�5�u���CC!!�:D�h��a�:0�40"Z!�Z\"E��p/�WN����~TXS��f�t��6�����!�����qk#��}3�r!�0lJ��g<r�jE��UT�����D(��h`��wb$��^�G-��C{�**3X���JvN�GL[���z�-�{4u%S*\M]�d�U�VB��W6���$����S�����}�"/�?8���������}�#�2i�5|/q��]��\��0�y����@d�� �4��<��\��p��-������@c:&F��Ml����D�>�����{����G���H��7��>Z�%���"B�H��+0��]�{
h��e�#�(%�J�,�pGpp$%���@���i�,Z��i5��F\�Z�xnE3X�����iW"�L���i�����/!�L"��)f�^@�f��=��l�<G�0R��m�%2����
���.�!���b{��7�#S�H���T���|t���5��bll��>�����0��SC$�Q9�Z��`EI5U���1�h�d���a�H-�[3�3G)�2�������'���=��JQ���4�%�M�4�����Y�$G��&�/��(���${�����#
�p�smt^�Q����������T!�H�>������}LY����-A��5���+�u�LF@�%��������:ac�r�\�1B)5\�C"��V-_����$1-�@"1@�|�P�������(���^lz��2�C�C��#����G;3�+%n���0��]�����`�����/�$�1�5A	+�u�{�]!2��a��}�������ENn������<)C��&�_�	�����n�/����*�$�W��ke�T��H��r(�Z����Y���MIj����\�^NJ7�+x'c�c��2�3q�e��v������&C���y}����������,�:�]w�VB��0<8�����f�h�?K!1��PTsh(�Sv���)Q�v�Q�W��_A4�Z-�b����}�����7h]���-�o�$�T
\������Y�~�%1��@_E)O��P��R&����� d�Y��/�:�\�5�������F��j������iH��Z�4�j�
�u��@�<�y��� >�c�Gy�_J��S��_��T7��?M���r'f�#%�.�V�1L(����aJ�K���f�f���K?���#���aV�x1�3�+�O���-Q���|xc�c�"�l�.���������71T�1u��$�|� ��I�=]B�vJ�B�"
)��*�#mS,�'�v�����!|��!)���/�=��'-U���t��5�d�����r�L{�R�!�tn��,���
����e��W:�����C@Q�������EN����L�����Vxn����RU��ML�R��d,5�����$��H���*S�:k�������9%��BU�L%�L��dn�B�����-SHK2��FIh�3E������gx��Z^��>=1�?o�Q�H3t��EN���|��<=�)��:Z�Z&mj��K����3����Mm�B���x�����p
0x�BHO��(z�3�h>��������~V��XZ�lW
�*Z�AoM�"�#���^dJ�����C��{�
�!������ J0'y��,"����7��AOu!t��(�c��I|�v�&�hJO���}�$i���T�\���j���q�����
�O�����Q���<<����H���H��1��S������3'*�g��gg���7uy��E��1�ed3�Tc�P7c�zJ�M}
�\%w�c�=��|o���I|��M��U����(Z�����*���u�L��+��u�4Xd2)�3���h.�q<�6-V�
{G���ytPWq.�o������j�������$�p9h���H���R
�|)��h=��?������#�5��j�fT�����!��k_�Ch��t'����n���~��H�%���FE�`�V]����1y79��rH�Em�p�^JH�&I���
G
'�����*-4f3)n�gv7c��v� �����&0|!;�J���[���W�U�}�}�Z
��\�^py#3W����!s[r3��[��^B ���T�\��6��+I������������0�0&e{��];�!g���� 
��`�_���t��:r��8}��I����-3'������������btK�TL9�����Q���M�1OW)�1�]���0�z�i���+DdL���Ho5aI5�I��q�W0�����E��r����"�����7��p_^{��'��s��p������Vt�La1lx�8j5@d��i���`��fQ��r�����C=:tx 8V�Nt�O�$u:���a���,����gx�x\�c[����6"�,���R�i��/�G�[�.{����(������v��Z��@�,�����^n���p�x\�?^���p�`J��	������h� ��)V+��N�������#�zY*����!6a3l�l�c���Mg�	'f;�����Mu��3���K��~�w�J�'��V��?��;���ry����R���U��(�ifs�� �O7�ws�]n��o��;\�v�c�{��Z���o����8�|����hs�@���X�4I���H-I����@�h��q
�����/�����(�;��B������<~��������A�6��^��S
�v��Z����pT��J���5��<s��]�����n��K��~��������a�;����Yt��
JX�_`/�Uj���{X�9��[��J%� �a����7'?���j��ssL?"��	5x1z�Z\K��j ������6�g��E"%�x9%�d�	�r4�����ua�j�����$p4}���������|������H~��G\\��7��q�	�����7%�����*��mo6�NQ������Y�e�
����R:���
��<�oA-�����qw���i	� aB�58kQ%��k�oQT�����\��f��]����n{�8����y0I~�>�F�����~���;�����AP��{�Dm����ej�[fc���L�C>t}h��$���2�E�47�(��)bG=�;={{vqzvq�����������w�Y_f�i��|svq��7+�bi��87
\���M��F��S���A���;����w�h�AjE�4�������#����"�q�#�@_Rj0y�����M�+��W��$e��:&�l�������F�\��������~%I���(��d8w�=��
�S�h��P?�����E��I�`\���F��E�Oa)����>b��/�I]W����0�i�K�s�f|1z�>��~$x
O���?��X�o��?X����-!���C}F/�s?�����*;��5��{|���v��J������6v�a����V��F����U{����C���`����-���4������(�������*��t�|_�v��;�id�}���s�"������d6YK�R���`�KgC�*�QOX,��
���d	��!�R���x:>���d���Y��y�2��(f��&;��2��H����=~� �%c��
z=�ya�����Q�1)N��s���'+$�y�����Q�QG���p/�g��j.w^��7�m�
���0>�>�������b\�<l�
��S�7��RjO������h~�0�$a��������0���Z�b~����f���e	1�QH�����C#�D^L�M�Gl#	C�L4-9�8�������<z�:�vD�y����dfA�B����gK|L9����s����C�z��������z�����JW;����������n�=`���{����R���T�Z�W�Wk��bi�C�-��6i�)^yg@(�G��9�U�(�h�/���Bg�	l<���I�'�v�����3��C f���2n��Q�����78bQP�����'E���~��(����m�^D����<0��@V���P�������M��	��M�#*�,m<����5fD���l��E	��Vt���&��S�+8��,A���j�����7������VH��#QE�
B�'���@��]"�}0b4�m]b�$~h�x/��N<"[�~"	6����>�r��!"�/�l7��lw�G]�W������^=@-�V���3�,��#��qTx��E!���)��R������y�_"(Z��h[d�U���~-A�.����W�*\.�%N��3������l����,�z)�pi!Y��B1t�r!������\���^����:�K�M������W7g��'������!U�%j�7�����)i9RB_���q��n�I��=C���n��;?� �j��H�.3,z6�Kg(���c�`������0��t��d��BA��D�C>�����-v5��wv+�R��C?�M)��[������{��7���`�c�~5�j:k�: �����^����8�������g7GJ���k�pT{z\S�4X���8���J�������m�_A����F	;6k�v���Mg(�$ 2c���l< C6f�mt���W������^2�B
��&�k��NCf���V<&\x=���dd?��=�E��l$&��M�q�Rz1��L�T�	����9	2���0H���
]�P���3�;*����k�������x!-s4�]��Bu�������N��k�0�	x����
MD�������(m�k�!��Q��c{V`�)q�V������l����*�������b�>M���t�b2?�q��n���F�P@����U���M
!{h�[{6/�����������a��SK5h
0p��<�f���U�9c�h\|ig#(o��vlt8�����1���O��q*2��h��*��q�Z��	X�/�����'�_��p}��!�@�b�2����e����31Bh�G��7���3��#s������,���~`\t
��"b�3�-r�-0�(F�Em�
��Tw�1n+2#�y}y%��/�8�:S�/�J�������NC��y�����-xT���'�@Z�����S���Hl�G����	�m.P��+�9��G
�=������6z���E���r��?R��V������q�J��c��'tP4�=�O%������fKsT��
���d�����Z9��5e�E��e��t�
2Q������r_�t�����������"+_C^�2b'����F��&X�����R@[,��`�PY�yl������X%��	�a�������V���w�D��w�M�������,�
�f�L|�w�/�I�%����/'�RLP��D��
D���e�e�|�	�(M4�<�
o�Z��<#�a���FV�=-bZMB��#;��J��x�M�nW#n?o���u���e3n������c�5Kv�Q�P#R��7+gb@7-Tf�4
1������G�)/�D3qCL��!��^cRo��o�Y%��*�.�]�z����I��<�$b���J���Y���-����-Z����}FvNx�������,,1{j3x0a�����'<<{�/���9@
�Z����T�x��+���g����K����^x���N&���2��M*����J�*���S�:���p�&\A���L!
#�CoR�5~��#%H�2cl���%0)�1U�t���.��GdMG;���U�z����]n���!��E�5�
����$�4o9�d��R\n�H�a.��[.��,����I�b�\^t��}�k����(�������&nib?��Q����2_��Y��g�d���h��4!0�v��S��&�c������_v3;134�v��?�A�}���gd��b����w:{�z�\�t:���N��[���t���KD�����/s��{0���}ij�q{�
o�o�~wqBv��W�_�vM )���^�02.�)j���V+k����`��K��:�dng������B���,���q����e��y�����y�({���7��R��#���Z��K����=��#������
j(��Y3:���1�s9�(%���@q
_�%�C��<:(�����n~�wd�D!�_��^^�����c5t|���3j�{���A�y��l��4����h%������'%����z�S�����)��1��y��h��tm��>/��0#�,I��+8g��6��7�q�`�T�=���=�^X1���tR���5e�����&#OZ9K���t�?��*I�,7^<�Vb��]y�k&��F�� �>ZH�60E�0;�p����a���

�(� ��R�=��j��lO��z��Pv���iVno��(nu� �8G�K\��jt
L�}|f��`6W�N�,[���-�Id��"X�\��n�v@�?���1q2������m n ���m�����B�M�����^�O�������oU3t+������l�CB\�)h�R���Cf�Z0�#�0�5%K�'$__��c4o9���
Q:�r�i�-89��������A�E�h�3$y���&4sg��F�?R������������*7*��$�jJZyb%���>8��Dx�	���a��`=e������UDY�#�����a�'.Zcl�L�D"y+�4���9$DRE�`���9'u"���+���}n�7��b1���B��fmu>�vU�R�;��j�;��?G�]��z�b�V��J���<+i���1�[$D��k,S�E�������`��'V�u�=uG��L3���v�+u��[���m���dg?��t�{�Q.�W���S,�9W3�3<�Q�f[C"�k��,�:�����w5��l��1�C�tR�oV�b�g���l��\J����N���tY�iC�T��];��m�\�gs����"*����5W������e0�vZPz��`nPyE8.��8�����W��'����JV��x�������O�w��f|	���� g���V��b��c�HK1����H��i�<\�|���+Omv�����A�I���}K�.$�������T������g
g
O����6�&�`����SF1��t���h���ft\H'"9�K�YK�U7��M&CV��a5IN/�5�F`m
��lI��W(,�L�:3�������{NZs|q�B��C�i5�W���
)Il�iM��=<����r��y��4�R��d�	��w�4�S�f����\�!w��w������@3R �R��gy5b�l�������Yk���+o��*�Y�s:N�Vr��42��Pk<��G+{.��&��[GR��K���L�Mj|W��$~u�o�Z��[!�-nGx����������]��H�p�|Z���:F'O4y����^\�}|<�����`��)���}2m�tU6<`�}6�,$�bf�����Y[n���-����c2����L�b����n#� +�9��E!�0�v���s)�����p�&�Iea�l�{rs�����;�
��s7V�DJT.�W����).eZ\�\ne���d
v�����LH��}��pIfq,+��d� ��������^�w�M�p�E�m���L�P��&J����{'��.n��D�m���_r�R�"��%�
��X���o�#�������T4G����9��Z�*)�f{g�t�w�������2�m��	�X�������o�u�R��cTDv�Fj�'$��O�,CKG�&y�yq}��	��0E{n��!o�Q�'�l���n$����q�qbz=>g1'M��������R�i���E�4���".�E/����%y��9��[O3
��C�oAqz��Q��~��1Pnm:9�����cS�"�����������9�3��lk�9Vb���"G�������x��0D����eL���)��"������P�����&_Nkb�����^����Q��2i��a���v�)/��s����a�F������=��DP��RL�]���T������#tgl�s��$r"mc\�,,��&{#�_p�WwZ��R���31���^�o/�s�F����V1<pBt�m���G"%B��9�*��H�o��b��a���:�7��w���q�m���y>)�W}9���s�(
��������F������N�U�m��]�����ZL}�E(�d��A@����q�������V/�h�!�#zQ�a�'��3���{�	w��m(D���Dt):��a�c�i	kn}a5�4@�}��+����NK�(!a�����6����d>wGS��c���AU�?����/J�^k5�4UI�������<��`g�v������~��8�tv���z
��=� g&��k<�Ox�G5b��7�9��*��7� �����qLR.Jf��c�|�����;�7����&%+m�K�����UMXB
)�ti[�35?jI�F>���fKT����p1�L+�xJ���A_�R����om�2CY��>�c�?�s��)�F&�����V��F���X��.��l4:���N��[����p������sP�����j-�T:O���� �u��Y���z%����:M�6��|��.UT��������(�p7�����b�E�c���R>��A�f@D�[j���G�Y]�{� *�1�9��c�dm�	(�#g!rQ�v�`ob�"�0�we�G#f���s��t���K]A$O�=����q7�F�������d�/����W�Q���5���N%h��������',9�
L�W���Y{z��	mwJ2�����k��i�1����A��~f��C����rp�Y�#R�2�����:�C���(:5�����5�3�\/�wy~�-RAF��h0l��\aDe���� �k)�5�P%f�w���k��G�x1Um��JD��>1a2%#A���D������c��lmc�g� �9L<%I��JS%q�=tIQ@�@^�Y�?�P&�8,$H/8�%-N���8	QP��������it��� D�!�m��'����I�d_/wj��nc'�?Y�f�`��B}���U�:�=��4���6�49�^D���D����fNP,�:���`~�FM��	�����a�!�a�����mNi�y*�d}�>f���h�����PH�����=�i�D��3"�R8��������88H��������\��a�����,���8p�:��
BA�6�x��M�BK3��W��� XK7��c��|m��
	bK�g�U������_��
���f�=�e�����
�S��~"��Z��P�����|�D*}��������gAq�R�X��aL���V�_����Nl����C_`M�+l�]9����&M�{�'tnch�c����G7�Y�\4�\�s���2	���3��*"�!X�A�`<��#}6�0�V[IZE�;���B�;H��E|e�H	��o
���0�������}ur���u�=�dl�$1,�7���[�PD�RZus�V�1�1���`
V������`�x��4��$�����{8���<��7��4��k���3�b�GR(>��Q��SQ!���t<X�I�����4\B�Xo�_���W(���=�1������3�B���dl�RZ|��6�--�����Sk�b��=��_�</S�#����L��y(��4���%���6�{�)H1oN����S�h�����gQ9SkeW��d4h���4��j���+*t�b��U
g�~,;5IDB��n���o���f~�>b^�&�OO�JQ���!���s1�����v�>{K�����9�,y�
���-k3�R6���u�n��M�'D��w�&p��B�9� ~R���9����K7�"}ZA�t�6��w�K�)�4��)���~��hM��f\hx�z�ln�^��FZ���X���������_.��I�xbL���y.�P�_���`���������N���c1��*����R���t`^z�l��t��UX�R;01��v�q�~|��gDy�_����f�Qyb����q�2�Lk�
�[}g��S&z�-�[r�U1�v�\�feL�]��i�C\����5��|d�|��z�
�����K�x�dgahqh�W����}|�'Q.Gi����cN������b�<^aGhF(Yi )�c��g����m�na�4iQu���&�y$�U�/�$l�����v����Ei]a��u�D!��Bu_1���`KW%�&t�PJ5F�0/a�8��(j���(u��T���XTq���tKl*3LH4��)�����M�P
�dHw�?��-���.���nl:4�n��AQ��kb�a�9�����J����������!K��������rX�vw�{�p�=�ec��<����t��|��F����Z�g�7����+�f
�����.���i<�d����~w�Fgd�a���#4p�9~��|�@,KH*��\j�4d`A�qp^1��;~8�.�6F6I������i�
���^��?%y��H}|q)0�� Q�������C�9����qj��=���D*�B�}]��Td��a�h5T��n�THMF�������f-�z���\�s��9M6;��rpfk���������U�D��%���a^�� ���H��;6_�����K���u�iM]"�K��e!Y��(����*eFZ��8,4NP>�	M
��#e��=�`��x�l���r��a-���d���cYf��\�8�����a�10��m�������<$�&^N�D��9��-�"�k�*G�G�,U�y��|�"3�
��e�S��=��V��d7�v�-Qn���o�0���ZeQH]��^LSl$����5��m�^i{��&pou�0�j�*F�u�1�oZ���F�v�bp�&��3t.d�]`��K@����7L\��<!�S����6��L=QU��+�D�����V��ad������2�
����p>�?��(�!;�M��U�R7��U�D*����\�@�+&�Ti��b/���BJ��Nu��aL�F��,�md�p����8��s��^=t���cMyN���2��`����T����l�v�<;(����b���J�8�U��HO<�>�x���q��J���I���))���)b�����g����@3oQu����+b���=@{��B���0_����9���_
���	�h�Y���V{�=W������,��h1Z�nV��~�����q�,����z����E��6i��2YL�d�S�r��	����o[
������#��wj2gK��+6����7FQ����&�
��x5<L���*V��(�J1�n�f�hc�2��B��n��>��&8�RD�rSJ+[��h3�4o [n��
Z��������"K�h��w2��/�>��+�K=
Q<B��=�@�^Y��c��+��V��g)�(G��#|F�C�$1��V�t0����HM��+�ER8�n�Tno��������Qh���?0�����|L��d�5�d����D������T��|���]#�����)$��Lp!j�52���J����2�����qy�H`�8Z�.��Q�+�-CVDw�c�V|�[p}v���y�������U�����u���� �.eB�;i��{�����\��s�S���t$�);�VP��P��`�9(A)p6Bi��h���?���
AJ��Y�5��q��JUz���4��`�����p�����(�G��k�<�(�hw�]"����#�H��cnX&'��8lj�(�/A����X<�;%
+P��S{��I�FM5�^ ���~�4�<���,V�l�f{��$�DXk�p�I�vX�����8����b����kU�I�]R0clVS�����!W�D�E��a	���O���TP������z {�y��f+���W|���� �na�!���Ka<��$�'
���b����c���n\T�7����PHZ%���i^�����
�n�%���nJ�.�DY���P���$�������0B'��[���{��F���
f��,�&[���kkr��~7f��^������+Mw8��!
�'�_����_�(\�&Aw> ����+AN�M>�����v���aT� �w���
����M���bG]�)Q����t�d��
q,�o����q4��_/8����A�-���+�%ia�z���PO~��_�	^7x@ahx�)��	�`e��:����
Cs|���R����Xb���y�����r#���LD������B�z���o�/Z/����������f����i���-66g7bYh�P��cX@��F)iC"X.L���!5	S�e��*~����x��k��_Ilo����Iy[
��
�D�'�\���Z��l6g�?�~�8�� ��3:��,�=2���F,@��\1���'�4QH\G&�G�������N=Z��]Maj���%��3`�� �<)#)-���9���0��%��k@����2�#�Zm�����F�Q�I���g,x-���
���h��
��B}�="���qcPY*S���}�4�����d�-�~bjrr�i�P�zVshj��U ���D�@\�n>�����r���Y�s�m
j}���V��G���b/��|�(9����t�Kz
�����?�!����;�UW�E�����\��=����z�l�h��Rh:� ~�[���<6^�a
	���������P��o�t!`1���\� ��)�����!���-7�ZR���)�MP]I+���E��I�+��$�� �e^���d[����������PB�Z��?���3���ji�$Mw��q(��X7��A�9'<�����y����mA:|O�
~��r�����8������`}h��Q��L����	���r��**�`�Y��q�*C�gmSh2��G�i!��Z�����6v�@
(���������
��d��E�\��t��G�-Y�5&1&���V���o��v7��F�N�C6.�:�	��)���@E�����*�/B��pm�Q��`pm�<���7h���B�`$}�_����J$qO/a.hX=+��ggA�8�m�g��)��H�e5�2�H�ji1lu��F�"�h�����\3&�|\���e�h&��=6|b�Ng98F�-�{�������������]Y��������i�k.KF%�Jj8B85bm��F��k"���������u-xz����0e����5{j{R0|�&8���EvM�1M���R��3��-G&�r����R��>h�������V��8�����b�������4W%�3�9-��uX���P��|m�e�X�:����o���Ymj�ha;&cP����&s$A��'o��`f
��<!��a��lCU�4X��.#�����;N,�M�~VL�D���lMo[g�� ��y�������=L�WA�PJnd(�rV�r�_�G��VGQ�� �c��	�/����;�5�N�I�%f'[�F�t7���T������-!Mh�Jf��i���#��4>-^H"M��>�`b�)0caK?+�=��r�O�>-s�/�"6	�x�.��������2\���tH���h}���������u�H�G�Hq
���x�%x�u�����c-8Ma�
Z&����������-{.�f6o��y��C)�����[]��9�1��F�J��������C!NLg��5�;5���
:q"p?v��s��b%�m��^K3e�����#9&P[���(ps�L�T�6�Al�c���|��f�	����X	�`R������c��P�a�Z���-�� �����]L(x?<��SM'V�]
�.&��e@����c�x�����XJj���T�#�g���A�R�;��!,�q���>{����e����K�O7>�������u�h���^&��e���.R��0$�H>L�c���/
O���2�y���_�S���9�����B�����/�f��e��h������u~��FB���j�i��^X5��t����%J�=\�k �U��e56/.�L�?���'�>���`c�������8���k���7k�����be����vL,���t��a8�K��D�X4��4��9��Uc�M��O6��G�e��Y"�x�Q7>��W�����([�J��{=��l��L6Ed8P
EZ�'n��C(A��n������]�����<�=��6�tR	@�p�m���!a�Mb������H�23<���<���a	}���4���m����lp;`c�����89���D��FJ�@}uR�uB��
�����{�������0�h�~E��Hm��64A�a�/�b~�/AKL�K�������-�������Di����9�y����  �p@Dj�X���^�\7�L��e7�Q���(�T8����&�!��,]�\�������Z�7I�������lJ����:J4�e�`����6B0�Q���4�l2f-�9H�Z���H��������%z��m1�a�/�Q��LV���NI������f������-��8x+�qO�����L����
5�}),�_��PMFVZK%�ZJ�eh���]x�"�w��_���0���8�����Q����=��f'U���$�������u{����iJ�	3K��"iM����8��h������3I����z�y;�Ry��%��o	�qv�g���"�/�x�*��J/S��������EJd�fo�NJ���(���2i��~��^Dd����F��}�D" Wkd�PS4����(hE�FjbXZ�_����IZ������f_�@��<('.$����#��Q���'(��2�3�f-'B"*{�w��)3�����fF���<m���[���
��#�������0�6�?:�~
n�t��>'''�:+��|����t�RZ�gDN+��)GX�R�~��1�}hc����c51�5 L���
�rA��O�8>0l�c���)}-�>���c��;7j�aC[�i��7T���cP-��{�=�v;���P�N�x��(k���5�;z���=��Z�b,-F��9��:�������rI�|
&X��_���"��.������Kn=���i���$��r��|06�D4�/�j��� '�4��#+������CJ�?���\�����H�{A1!$�T�7��{.2��rM��b8�Q8G��������5��}�@��yJ�f�G���������a�{1x%��+����:9�U	V��
��)g����d5
n��� -@�LM��������8
����:/����4OS��V���E�,k�j,�����/�K&N��M����������=�:}s1����0���p1	,,��a�m~�gh����X�\������������7v�O�^�;�i�\����U��5hX��>C���,&6�����<�9�a�r�������E";a��tIiY>(����`*���`K��\����5'��.���,����m�]S�z�H���P�����x�f����b�k�+�r;3�u��
�y��:��A����t�Ia���\_^|L���i�s��h2nu�a���g��#�������3���1���	�B���d�����	�8-��'�ytf�N��ol=�(�����N�"=���t�3����������w�1�q;�&�����Ex�xl�Z���8�� ���b��^�.c�f�R��u�t!�LL��C*R�t6�E'�e�J�� 9S��W.��H�ML8����}~��
i�N%�_�M�=]W����8���$X#���@�H���4!$�����K��Sc&+��D,�2jl�0���w�9f��G����=YY�V�k����m@����o��U��sqt@uB�kO��������P�l�<��Q�+�=V5���iC������s�s�����[�(�@V71����np����jqe,����h|���G���x�JJ��K%���f����-�;>{��S���9������.��}kU�����8��g�P=��v@Q���\��i������}Z4(��(��S�yl�k����] �����9��q����@/=�����`<j��Ql*�#o�S�	���wUl)``&k��-69`�/���78:��r�����U���|f�-�9�QJ���<��@�&���6�������XFS~����,�O�m�5jUti�W����������%G�ob4Y
����l��
m���.+���,]�WN���OA�V���h�u7%
�)|A�<��"��;��P6���)��4�K�����Tdr�`yq,���`o��O��n������6�{s[`si���ueI|
�ZB$����'P$�K�V���V�����+���Ej&��E�������d�68t�j
z�x�_g��D��i.��3�,.��*Q����2/t���EC����Q�X]��B�
���[q�F+���.�M�[Qe��Y���.&Q�)�BTml��j��v��vBV�U�.(�]$���@L�b	� ����������K������Q�����<oj)'�z���2��p6F�7J�E*V�D�,�&�!L@��	M�6�p1O������Q��Q��2��+�)C!<��y����Y���Ki�M5�K:���������#9�)Eo5�C�2�+uBt4m���
��5�D����i|����������*k;$mP4o�v�j5%n�1M�H�l�s�O���a��c����O1}�����}�rI���-��:����3�bi�Z��d��a��,�*L��I!V.�����?��*��I���N,��&�O�k
]�<���:(�s�#���Q�]�*�.����t)��S
��aK����t#U}��� tRG%A� ��ao�����7��q.�K�����(a�=���cq�`���������`�#���A9�a����D�)0���-;^44�]l!hN��@������s���	
G� �S�<&�_���^om������%���:�i"Y����T�2��=f9�_��lx����g"���������ME�7�m���A��]��c��2���$B�?����l�",��C��.�f0e��}^hn�����^^TzX�z �.��|�����,N����dB;����&M^������l��������4�1�Y���8k�����Xf����L���K�7�O7�q^��P�&�n��K�!X"?b�%g��?�vr.�n	��|����<1,��7����6S�:��������.�|�`Ex�!�]q���
�s��F�v�b`s�6��gW7A����Y�&���kZE��r�����@�.�Q����5/��%_��YA���3��|�e��w8����z�������P�v�u�c��%I�)o��=����B���NuJCI����1���x�1<A%����]�2OQ�������1��������>�����6���3�R_��������xjyyz�����p��	�.��2f#Z��.����Ti���9+������`�2�l��	��2���P����J���p_,g3���k^���c���	��e6�rk[LI���4h'�x�pY��	5f��a�V���g�V�4xsr�����E��[Ed�K�������i6�2�������osS��'V��<�o�kxs�0{����Xno�����a��/1����f����H�gva�b?=~�$�G,bE��;���������lo�Z��W����g�Y���g�Y���g�Y���g�Y���g�Y���g�Y���g�Y���g�Y���g�Y���g�Y���g�Y���g��8�
#127Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo NAGATA (#126)
Re: Implementing Incremental View Maintenance

I have looked into this.

Hi,

Attached is the rebased patch (v16) to add support for Incremental
Materialized View Maintenance (IVM). It is able to be applied to
current latest master branch.

This also includes the following small fixes:

- Add a query check for expressions containing aggregates in it
- [doc] Add description about which situations IVM is effective or not in
- Improve hint in log messages
- Reorganize include directives in codes

- make check passed.
- make check-world passed.

- 0004-Allow-to-prolong-life-span-of-transition-tables-unti.patch:
This one needs a comment to describe what the function does etc.

  +void
  +SetTransitionTablePreserved(Oid relid, CmdType cmdType)
  +{

- 0007-Add-aggregates-support-in-IVM.patch
"Check if the given aggregate function is supporting" shouldn't be
"Check if the given aggregate function is supporting IVM"?

+ * check_aggregate_supports_ivm
+ *
+ * Check if the given aggregate function is supporting

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#128Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Tatsuo Ishii (#127)
1 attachment(s)
Re: Implementing Incremental View Maintenance

On Wed, 19 Aug 2020 10:02:42 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

I have looked into this.

Thank you for your reviewing!

- 0004-Allow-to-prolong-life-span-of-transition-tables-unti.patch:
This one needs a comment to describe what the function does etc.

+void
+SetTransitionTablePreserved(Oid relid, CmdType cmdType)
+{

I added a comment for this function and related places.

+/*
+ * SetTransitionTablePreserved
+ *
+ * Prolong lifespan of transition tables corresponding specified relid and
+ * command type to the end of the outmost query instead of each nested query.
+ * This enables to use nested AFTER trigger's transition tables from outer
+ * query's triggers.  Currently, only immediate incremental view maintenance
+ * uses this.
+ */
+void
+SetTransitionTablePreserved(Oid relid, CmdType cmdType)

Also, I removed releted unnecessary code which was left accidentally.

- 0007-Add-aggregates-support-in-IVM.patch
"Check if the given aggregate function is supporting" shouldn't be
"Check if the given aggregate function is supporting IVM"?

Yes, you are right. I fixed this, too.

+ * check_aggregate_supports_ivm
+ *
+ * Check if the given aggregate function is supporting

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v17.tar.gzapplication/gzip; name=IVM_patches_v17.tar.gzDownload
���?_�\{w�H����S���LpXB��8b��;6��83sw�p�����D$A���~�[��B��Iv�����������|8�����`~*7��<�%��l4�]n6��{t=��M������<���\o<��c��&~�{O�'�n�)�[���:��-��������[����s/���]
��;	������{�\Xu8q��r��_�\r� k�Zd�Qo
�kMM��'��54���g(�?��o<wZ��
�Cm���4�E�4����ij�p[RvS�$8r8ec�� I/�?P��%b�~GAWG���'����=������.�/K�4/��gU�0������B�aS�����dp�����}���$mKV�	�-a�-!pA�%�n	�[>�Kp�9��������.j�R����
��O|����DG_&Z]0:�+�9k���S��o�5�rP :���s���G������5��!5����j	|����s��I`��Vf��lFW	dZ6C�H-fZ��<���77J%��Z=�P7[K����,�d�a�4�������i*�vT�������Bo%�*=�z��NE�M|���a�q�kx������<5��l��o����y���
�l�`m�c���{	OWV���A
���BD�����C�������]����'f	��i}�r����
������q�nvKpS�*yG"]�;�����zo��HU?��9V��i��P�]/��^}���~�F�n�Y�C�/?��Y4"`a]_��g�|-z�{��l��v��0��;����6��r�������C
&D�.��z�A���$�?�u����q������S1�/qc2��Z�#������7�������fM.���������j�e��'��8H�u���5dU�;UwX
<��-^�1Hm�W'N`����uIQ��_S������x�o2�P���4��f��&�5���l����d�i�c�i���(��*���-�D�n	��@n	����@ni��]�i�3���g�[�n��&X��s�OLZoz�la��3��������{=A�J?N�w
&5x�wy��p�)�3��m��W��	
������'����#6����J$��.\B��'#+����b���i��#��" �����(t���Tb������VH\3�lK	()�B>�c�����@>�&o��?A5�~�����m&�W��qKB��5��a��A�&i
3��2��!N�y��CD5�^o�T���,Q��
�U������z�Q{��th�A	�9�s�66���)����c6:�������5y�i�����qj=l���D�Q����>��h��#_�������A���6�]����'~k��`��3���3�����$�ih�Q"���V�::F�t����\���'��G><���C��#V���p8"n��h=���3��x_&��*We�9����X��8����fU���U�^i.����QG�q��}tk������2>���m=��Q��?�}��oa�\��yS���h�*�',�[���]c�a������?#|��gY�kb�����Q�y��=r��aS@r�$H7f^��jcG6�1?B��4��]�xW������P������fT:���Lp*�)�e�,�#W
�2p]{m���U���5no��8.�Qyp�y�nF����F��O��7]GX��LJ������&'E��O�\������t�0hel�b��C�X�/p1���|+%L���-|��*SX}�����z�����v�Y8�u��>'V��r�8���������5rUQ�
������q��������x����AisK�E��^�9���a������OU�bc����uL������B�b�G�cA��������f*g_ a�� \���R�4$P�=2/I��P8%��}�{	���(k4b�E��R�&SZ6%�&�JB�������S�y�6��|l�����������W���><������������������F����)9���{�����$�w����s�����&B���i��k�R�H4�^��v^n���T`}�*6xC+�y|����3\�~�q?������e��h./�m�q^,\"^ `�E*�{hy~�6��)���������?B� �&=�>��#5^�x�������T�=��y��%�;7���0���Y���;.VJZ#�d���;���0��\�D�9$��{*��@.oYPT�B����g����l�����L�B+b"��u����c��
�/$�� 8�Se��;Hy,�~�U�47K�����9�
1>0�^�f/�S�����a�NQ>��1Q`r@�1�t���-����PNCCT-�v4Y�(;Y�����rq��e��$
�&S����'4���CN��YU��b�>�h����f���D_/���0�2�EBD����>E�4.znSq/�!�O�<k+����c�
�8��#���qC���l�"�+�&k� Hh��w�y��B\�es��,
���}��y(�3q2Y#���u4��2�f3Z�����S��V��X�������
6H�)Ry��U��{[asU��Z�����5�K9�%���J�9l��F������4L9gi�d�e���������?3�����+�y:�jI���A����
�->S/'3w�������Tc�4��=����A�Lp*�(m�;�#�%�JX�M6��<�r�����~=�5��#���iV=v���]L~sX5��]�=�j��}����RWs�?4��}�������ln���*;LV5�i���*���
�3e�
u��>���R!���e��cR����[�U�EG-Jb�V�m�m���1rg�Vj��?�Zn�a��MU�����q�l�{4���>��9�)_���^��?fb����faC�:�?����}j'7���H	��^JSm4��)r��B�)��	a�\���x^�e
\��������J��Nh��5)�C��T��v$LNCI�e�Q�L6�r&v�D���%|"U$��+��^�*m"�P�z�g"��t�X��%<���|a9r����XV��r?�=����h`9��o���[�6�Z��0��i��C��&A.���C�{�>�A��;N�}h��OK�e�"K�Q�(��z�}T+j��QiH�4��A���������wYR��e��/Kj��N?����!���N�������i�J�X�Q_����a{�V��roN��R��O�}�s���d��i���lm����;��A����Cf�cF�������w�+J��t|%����F�p��|�i��cw<�u���U�m���58i�9i���f��k��b���[��K�������_�T��L�I�/2~U��"�*������k���I_��fY��D��r�E)&�<�����]�;������J
*6�k��H�y���\6q��v��y%���}�F��i��<�8P<�L
�!�����p�(wL��N�_���J�%�g	�!?nb��d�J�+e��E��:�=l��p@Z���~��:iw[Gm�?><;�B�?�+��>`D~��9����)Z����aV;89~���]���c}��Ul��w�H�h5�����������op��eU�Q�Fj�32��O�vVx���?�]����:����t����b7L���Lb���F��9)��o������q���l���{J�[��g��mx�P��~�?<B~���3�an	.���k��HrKv��v��|K���-��G|,��$�G�_9f���'���3���2b�c"����/+B?��[�����!�%�cN��e�� �P���J�'l�/�}5�� ��2I�Eh���.�A��I-�:tV^s$�4�K�^Tzi�KYK�J~�PV� �{	�(p����pC-��1G9��a����2N�����=L84gk}xK��~����������wy�i�'���1������4\�H��z�T������|/�-��O���	
_���+�m�%�=5)s��I���N��#4%�TDW��#�%pVD����@7�k����aa`������N����������p�H�|�y<3�n�|C,��2c	9�p�+�bec,�����[��o$�W%Q��U�G)B����=�����7��q��0��GL��[*9?����&���n��.*@��|��s'�����e�)��`7��VL���Xs���.������U�bOy�Q(�i4���Q����N�
���`��3��M!]{�~�O������TQ���������V,����o����,T����	l��3K]0Y��\��Jv�/�"��bNY\a�+��J>)�����Fh�V�\�m��t�V�f���a�� #Yewex�U��"����Q�K�G��r�cd9���y6�RW�a_h�����k��Sz��s��5��1��1P=c5���r�*��2����d7����d[��o������[��$����+���P�R2�����Q��Q�r�t��B9<����+��*�4V�vc�[�= F�����D����2� PN"c5,��Y�pCsX���J�8��&���������p)�)�P�vP��pa)��\�g�.]:sD;6�H"ik��"������-b~�i���� .y����
i��)�A��H"��.�f�P�"��B{T8�Pq�\�V PjS���k�������6D����&����*�'S>!�R��*�	�������|��Ww4I=�FdT��`eRH2{�$\.
��Z��L���y�=������TL@Z��fF�(�:~?����C�G����N�-��a����-Osm'cS�\���F���v6\�wJj��1�<V��e�&�,���t�r&*�k�{Pb����$J]x�p=���L:X������8�(��<�KE�7��D�
��[%5���g[%��v��)����l��$��K��n��q���+���� ���!�����KZ��9sC�+��-�z~[?�ib��J~�zsa�e�4}pN�
Y??d�T��#k�����.�)���R|�����/4�G+�@�:w�����/��������0��I��u�;���}.b=K�!������>��Y�����[����6�	-�)x�$���J�2��<��O�*>�����le���b_!�X��9I����F���-nw��ieQ@8��Ob��(����� ����S>��O��@D���t��"I�RV�V��@��C����E���i���q���l�&��o2-��<z-{�"�����S�N���
j3��@x�1�^z������-Qk.g��0�!-^S������������B��8jE:�,����pv�h��I�*~(
����:^��HfW�

��+��VH2�"�GRn.6� mPy�O78rXN%~���JC*�����j$K�e~����o��������������y��4iw�p���K�^�or�x,)3����<+�y�5�1�J;���CZ�� =!�7�����Z.>|����Y��fs����0�=��?&<�������������_�����cA�����9��1�����~���>�WS�=&:���cjt��8�z�����[u�U!��m��Y>�^FT������� 3��(�*+Q,����9Whk���t������#�s~a������0M}�K:�����g����F��4�JO����H���h����H��_dI�(��Ir��$(�&	�V2�}�]���Z�
��E�O�dD��������!/E�?q��Q����	���H)�����b�,��(#�1��lM��H�����^n��b�9�X2(Y��`��e�
��g�_�x
kU��C��a��u���~��^��R���eS�|l�g\4��q;��v��2i���kV�F7u�i��l�**���nv���>�O1�5_|��gY�,�����D0
v��?�5��hm������)a�I�����e/�EH�@�.pO��s�S�<����0�|URz���K���@�m��������'���a�����p+�e��i���
H��w8�� X�Za+bi/S&{�[��f0���1����LA�{CE���l:F8Z�I���r�k���t���A��Y�Z��nE_�o�Jo�T@���]�����^,�mg������3�[�����}W���e��5�	/e� �k��^��Zm�>f��`�1��1#�;���?�M�*����z��A~�.��.�O*TP�1;WF� �������<8���W7��cE
`�?�.��F:���q����]�1�[�����m}]�������[�~|(e����DT���>��6E��(��`s_WY�8���P|I��IT�nk���4�L2$g��w4�����XJ���2���W���Y�4��\o����~�����A^z�������?��H�\��6�A�4��`+o,���f#\�A=�1es|�jcd{��>��0)�+�������6�?������Y&�����?Q��X�U�����z�}��x�bT�Ib"K����bP���� }w�/���$f%%�bO�����X)wr���U�J�R�D����\���^���)y�;���iYw���`W�>�G�e���#Ly�Q2z����,���}�q�`�j�3�m����I�epo��w��i=���+0���wj��N�c�m0�L�{l�����(N����sC�b������:��Aqn�	������3Hp��aCw��sCq��o�[�������J���>7����->�ax���3��=n�����!gV������h�9��G_�nY��?F%������.���o����j�
��r�v�[�
vY�����JZ�S|����iNo��b���R�+�m�B�.sI�K<��C
�M(o��D�\�xg��{��7�(\��M���B����{�z<��9�w���jU��s����Q5�<J�;��KQ���^��L8��o����s�1%tO���4���)�d��( ������r��z�\9�����{2�|2���� ��[�Q��.��-�7�oS����.�w�3M���1�{S�<F-���d�Q�x+����1����Z���fj��N��h�7�.����������3�3�;V4��9���O��f���p�#��&�$��!�x�%�5�G�|sj��S�������ZD�ug���Z=25�\�vYj��Pd}����d�.�$L��4a��mM�R=������Q����T������Y��X�z���<�y��'�g�Kbe������]�r�)�p���>����x�)����)���t%�"c���������I�����p[�T��l����<o��X�K�p3{ m��f��4�-��!���q���;���I|Y�x:e�)��"�}���Q��_���m����'��%��7���F��nOa�o�D�0I�uQ���nO3��7�\yd���d�.]��e��(��{Z�jyIf���y�������@�+w�o�����{9=,������|��Q����a]����+���=@t�e��[� ������]��xR��>EzR�=)��k�zw��,���X�&�l���<3�&����(���k�z~�{��#�5=E��'
��F�^4B�	g���!"�,eP��5�������1��r�y��]s��<�����=2
����C�o���~n����\�7�������G�qK��
i8nw�~�s}��#M�!��\�����������������<�?Dt����u���Z}��m������<������i��P������2���Q��B@��)��b'<�< ���{�~�u���>��t!��N�7��"��Ruog�%8�N��5�
�0�}�#�CX�/��r��]�y|�s��c��S��T�s���?T*�;;�'@��T�����'��:�N��v�>�C�b�����o*9�\0���wk�q����L`�����4��F$�����bN����[w�{�����.G�y�1Yf� (�R��Qt�H�����7����'��<�I���z�O�T���*�[����2��i��}��oJ�wv������c3�����������yR=������V5=D�������/z�����li�v��+����v�@���I���D��'wv�yR����=��=��=`Z�E��oH
4M���f�@���s��������p;�p�O�Rw:��p;��R��x;��pg9�zx���K�)u��Eiy������<Oj�'5�#;v?��<:P~��{���@J�o3�}���)�*��=���~O6O1����M���Q�<�>�EK����3C����3��\w��ug�x
-t�yJ�<��c@��*��9}�N~JRt;��|��LRt���7�:Y|��l�����:�<dxJ�����C�l���<�W������rT[��f����,eP��j�q��<�f���QO*��h2O��'m��W�<�������41�����6
c� i�t��Rs�Q�^�i�IWq���!2[/�!D�3���w�_�����lyr����C�c����k�oN=��#O���O��Y?�<�<�O����������~2��/Y�'�?�\Z�1�T��j�Z�`(>{���p��Lw1��o?�'<d�'w~{-JV���v��R@�x��R����������s�n4�
k�0���hZ�;�-9��;w���n��yhX������q|�I�;�G���t��`��Ll#�$>�w&�9�}&�����Q�Z�q��<�<	��a��1�=���qw�����\� ���5��)c��@i%da��wG��/��h��@C����`�.~c]~c���oK�����o�4��o�����������h�6~�F(=>%�,x/�����lw�5�9������������h��Cx�I?t��?	D�����\9�K7��v�n���;WU,G9a������S����p���X�8�7/���oE)_��AF��a8VM���d�Vg�Yg�E�#wmBA�K��F�����c�'�����4���1jz�T�i�"���\8W��'r�P����f�Uf�f�1]s����D�Z��kj%��3�	�&��w�+x������k��At�a�xCox)&������nW�7#wu�����q���_N�bp����o.��
@b~����N��E�\�s��u��[�p^���^���V���>>o�500��f���:ts|�l�_1v��xH#S�y(��o�#�+���P�5�$����3���a8���v��[]��9�}����	�5$���E3�F���������jA�����������������W~�f����t}��.���v�=B�E�<9����'gl\tS����/�1p��������h��_��V��V���������F�Qu�ojGM�^�(wq����]���nD�+��
��U��J�[�<++�����2h�Z�-��_D��,���D$b����v�=�;�p���/^D'�B����d����.t�=��:���n����#	��v�`)�6T^t������@��Cv0��4�F�M�/���E�(��M�E�����Vx4���;�Ab5�C����g�� ly���E��n���S Q8�;����o(�S^�x���8���B�������i^X��y�G�Y��q�8T���@n�	Eo2�|
ZD��>��d���`�o�������p��sP;��,z,>�.�&~)��<,����Rq\��!)���n��o���Sh�-�]t��t'�9b�P��x�(|*|��[���H$j�z�^}|l��2������I��9v?��� ���n�����'k�A1,��h��c8��)�%`��W��R!DL�x�G�r�lv��t./��%�V�Z������`2�}�/o�N.NQe���S�.�3h��_��\J�g@����>@L������s
�rX�]���G����Ow��5�J��0Mm��&���O�'�]Je�x|�2���4f�)��O{��V�CM.:/���X�)���p�}C���q�����K��\��]�g��V��x���p���{�a�q*,$+�9��
R��+�l'PX��Sj;����n��������������AP�����=/�5H`L�$�H
�P+� k�������n,�����whbQ���6MItUB��)�r=�:�w���b�����j!���(���{|�Zl�W/�b��	l6��y�[T��u�#�jl��`���m����b�8<�i���{�g�7���������A&����q3�,�$��f�O�yt���LxTs�,S}g��'��9������4���C�M�w��9C|:>iw	�v
L��t�PuE��w���Xy��Q�#��	��)�I��>o}
�L��dQ]���\R;�j��r�/`l���X��$�D[��&�Xv�	J��P��Q�}�������z�r[3dy����7s����LN{��o���+��eL�h% �����6�[M�����������?f���f����7��s�1��C��T?{�q>��b �t�:S�Q*��d���p����Rd.��I?{9)q�ss_�������|�
���F��~*��]$4��|N�D5y��*�(�����B��KMKoKX��'�k+��4��xF1�n<vnZ0��G� '���X(n�A��R��O<�}L)2�@J_��V(q!b�m:.N(M\���2��7Y��a�#�������vd,#K�<�n*�k�HY�t��~����.�/a`
;�s�6�6�Q�6�=�M�� �1��7��8F� �r;�����{�M9�������J?��|P?mF�]q����8��f��J5����8������&��;�[��������N��T5>�_���I3;��9�����!������YZu��\v�#z��1�����fc���X���f�7!�F��%4���M��)�8d�\o�$��IL������V���Vk-_���})bu���h�1�`�v�����!��\g�L���S\�)a-}�i��F#f�A#�1n��,sWC_�XB���w�Sn��xWM�
���Rq{
��f�9 ��2���4��i<�����03�����/��+�s%��r}����t& 	�H��s��o�w��x��3�m��Wk���<R� `A������&�k���7�Z�kCm��K>�=����~~N��E���Y��jd�o��R����v?�uYv�:�C�)��1�6R�|�Z[V������+�{��^XW�Y'!q��v��b�!�x������Al����D��\�[�}��}��
����~��C����OO�����F~�������!r&��0�������b���Q(R��g5]I�-��M������
�
�/4���ck(������'qT�PG����@��g��&����T�m����Y`:rI��I`CZ�����.���F*���sr�P)T�3�+~c�M�����f@�[��S����`��*"��-X����K�rR�\�M������U2W�om
��.�����D����gqP7���$$~	��D���
�3$���M��`HY^����uN�p��b��adF�:�x��m���X1�
�X�V%��H^�_Y�LqbqP;?��<��cr�q���B_T���O�o��f�������h�+��n�_��Z�h��5HN��brF;�E�<s�~��YLj�-��k�K*s4%o��3q��t�^Oln^��<���!���`�B���3����������������7��_D���:{{�N�Xt�*�Ne���/�������&j��ilccc�^�����^aWl�_��^����%�0�������.���2l��@�w���qU}0�K�?�����6��������QQ�A��&�DRh�ERf��}r����k]����������	�4��Yp3��n;��'��f!�	pt���'B ���P���u&�I��3��VR����R��<Br���xB�)���5�3�!��q�gE��hKlEPG�6HU1h���
���!QDn�����~e�\�/���{�����3c$����b��������cDH�*��q]=���~��P�X��_���'�}���N��BQ<.���O�	j��?�tV��V8d_���<<��]W�]R(��X���N����.��<������ �S��e(J���niV����	d�Tp�t��	���'�4�H�t2��v������6e�FI2�n>�S���D��>
UJ�(T-i�tK}�.����������������r���G3M��DI"�<�����0�������fs)�nR��Y�
����m����i0p���_Bo�!^�D7K��KV.Y��eu(��N(��@�0nYdf���An�+|��O���4n���B���S�����u~�3jZ6a1
y��j��{V[gp_'G���\X�/_�L�b���9z�N��!�����'+�o�J�t����X����r�������v�zbC���tXr]K2��{.��B�K�RW���(�T�
_E�Xf�N�y�F�P&r���s���>ZG#Y���Og�����
�����O��h0�����Eu����CQ��=��C���Z���oO���k���d��1���	�3@p��bj���A�L4|���BM�Q����!-MO���d�Y�3{DG3��A��������D�+w;�$B��hd�W��y�j����A�������]KA_n��)�.^�T��8s+l�Zk����b�(P)��y��F��[�Zxm��^�;Y��i���w��;@6�~���CW�[���M"x���1kSd������o'L�����@}?���x'����������6�Cz��
/9A������#d�`i\��w������M9����d*g�Lmt��/���t����Jt0���:�����O�F��{�8F�����2�������cWe_S�s��j�6+[�c?}V"�=��HP�G�Y���>+�R���5	����^btD�Z�h�4��{���/��Qa:���<�]*�����na7{�D���2�qj�������p�B�a-^2��0�6�&��b�	��x� Z�J��RTK���p�kh�:�@",�"���0�����JNf
PuC�L�(�J���9uI����=L����*���z
o��������6y��
�V�7 �B� ������q���Ru'�.�3�1'�^���������h�f�*l���5`d��y���2��O���+xOy����-��HG��jQ�E��u+�q'��$�F�"FQ�#'�u<3�3�[+��grNW��
f�GV^���8���$����������bu!l��{���.� �����N�����,��4������Y�}�����/)d����b0�70��*4D��������'�I�^��X���U���e+c/��N��;�+S^"����ph`�S�Z4����
�'�+���8�U�����r�X���IoM��O2^�M4L�����Icd�	[I���L�uc�!�d��/�Z1�'96#\��8�������u"}����q��q�g�E��]�o(g���R;K��UG2���+^����Q���ap�W9�0�.Ub�+���������x���]��/acW���x��C~�f��(�wI��:J�WL�������5�[I��������{��K/[�^������+�b6��U�ZH�(Rc[
��c�n���a��s�*��.H��X2�,k���H������H3�g*�@�m���F�!EF�^&��Iq%*<a�x
��������+$�����������%��t��?��C�����$�����G�t�"�,�%22Y6�TD���*�@X�.�?�(�L�"������M[hp�SO�^���R�����L�,���yM2@X�w���o�����k�Z���4+���V�A���1
����y����K�	X��}���n�vo��O��W�0S|�a��@��9PgV�^l

n����LN�o��cv!���tR�JzbR�����JA�m�VY�a���N!���~,f�:��6;DwX��?�����vV_��B�_JK��\?�����1�B����/%BY_?���:gCY_?~��UJ�����=�C��2���E�d�.C:LK*T��)m
�1���N������0h���?Gv����2���������1���z�~_|/�#�X�J�� ��l���?�.�d�_�ah�4!�������M���Le�\J�D����2+��R�g\M���L�����3�0�RK�R�P���]L����gww�$�_������Sp�S	�7�)mo��������}'+>w��9Xl<��2���>F�� �{b�O,��?��'����,�X?�Sn�����r�S�s���O,���>��'��b��,v�]^=2{�x���1�y��e����}��O\������7����e
����e���}��O\���>q�'.+�,���(*�y}FQ���}b�O,���>��'g��~#;��=���2���b�X��}b�O,����Y������gq#��A=q�'.��e����}��q.{�7�3�C��.ePO\���>q�'.��e�����t�}�+����Q��.ePO\���>q�'.��e��l������l����K��}��O\���>q�'.����1;��=���rF��h���}b�O����&�����fiq9��Q=1�'F��h���}b����}���$�z��K��}��O\���>q�'.����~;;��=���R��e����}��O\���&�������hq;��Q=1�'F��h����/e���x<�N���bL���n ����^���{�iw��%��;c��������g��p���8�]P����wF��3h��\���H�+sR��p(���K�;�e~�GXT��
���R����nOFn�����(P�;���� �&����$�����s�/�@���m�%��;0n%R��h�������k{��z����q�V���dT�3�X55�%]5�.
f��JO �����z��="�7�H.���\8Wb�������@��W�U��F��,��D�Z���M����
��:��^
8��E��yC�+���~��mSy��_N�bp�o#�xsq|�l�����7�u�pm��%��K��7���:�<�����E��9V�7�0q�E���Q=��9�jv#{��@�E;2&)rX����x%�5\����E~������f�)t���7A����}A�g�������������Wah%��FS:�>�K�h�z;����]�NON�g���Wo�����ob���h�w��d�F�����n���l����=8���kP����vq�o���x�H4���u���F������`yY����UK��R^YI/�\��>G����v�������X��w������S�w����E�q�hF�����������(T�/���!p_�#)��v��%�_*/�^���`��":��9h"(�GL��a����E� �~�6��b=>>��!T�����3��� � ]���7v�8��?�O��(����O�����=B��pP;�#�>�^�D��E��D���V�IBVo����C�|��o1��)���9,6����,|*|��[�5��8|D
�����l4�6N������?�]�������P�lx��D�\���h�:�L��>�������{	�9R ,%:�|Gz��0-��HL�O��������)
���H;��u��o�;�Ea�K1�D���h��_��q�<��,'�)�Y�)~b�.~�(�vWp��~�'�y��=x��R����/h0>�y������LB�gC���;���K�����q�� ��(X�yK[p|�y���@U*�(�|�l�*����@�}J��^|��Ko��1\8h����Y-BD/)�$f�7w�fr\����#U����R�nt�0���P�q������J-6����&�\�G!�/����b;�����.{N��7�U|���[T�^rQ#�M���,P>l�M�N;���m�Pg{�u6XNo���f�/qprq��=�'�Y�
�����GcE��(M�@jq�N}{�a��>��:�X�F������`���E�IjNs1>�}��� ����jG������7o�������Hf�8��Np���/�B���b�Wa���,4@����Hwq^{z���W�#�k�G���_������H
���J}�0B8c����c��R�3��.HA9�^�k�"~E�' O����0}rP�f�;���G<����I�#2�8��9��?�jRZY�J���������c�z<vnZ��GC>������C�[q��SB�����xhl�B.�S��
�}�;�"C�2*�L(K�<���5~���:W^��rr
EB�K��I�r6`0����n�$���-�L+�.�Q������z=��jGGS��a��'���f����d�RMC)�$��)����(��(��)A')	A���?��w���Gg�P;�F
���!����]
���-������[w�=��$����rB�� �P/������A���A�/8����Q3��?���Z���_Fc7h/#���<@��6�KwF#�+-��<����"���j$��t�r����3��[�|V5	0�����Rq��T]�YN��?R�]�I���W^�J\�UH}������L�������s`@�7J��k�t��e�{���@�j���8
���s"��w'g������Y9�'�����9������1���?����I�.YI^��A�\X���za]
h�.��^t������B��`=����Al���U�[Y.��?t������U�_WcW���!N�f��'G��_�|Z#��un�&B�i������P^��<p������g5]I�-��M���w������������@
�����1�<;�I�?��\����=�7�?��*�n����Lb��t��8�\�0��2Z*��R������J�:O������)`���/I<��@%�bRK!�K] ����a��|!% 3=�,��m��?~�
XYeh��mB��!���M��`�K_���2
m��S��T1������a�e��L�e�,�Fd�h)R,X;�8�~
\`�R,����z��� ��Fhl������R�\��u��]� V��f�i��p�1������7?x�������dU�V�}�����~�w�K�'��������\�-����l���T���l��$J��	J�B��fr�o���z���{3�b��[�������z�6���:�����u�*n���n�����x���	�Z*����
��*6�B�0��%R�������������G�WA*}!�����n>���\zQ�~Q���Ri�|���������;Q�</W�%-E-��0�R Z�-�U���X�y0��.��X����KX��&��7VE�X�*c��:��������f~���!�m&�������C��'G;�����R�mX����U�%zT���Xw�v�c�&��6�(�u�.�����v�^Oln^zp�y>l�)/W�a��"�����^yoo�X��������^V����Va
�{�������^�F��m���j�7�8�Y����cw�w:.�!^�_z~BA���/��?<7�HTYUFg�G��y��s�����U��#��,��'�����$�W���59��fZ�f����*@��-�F%[�5���!��N'�~����c&p���O�h�-xN_�0x��g��h�' I��wbvP��3���+����	�
+����6@8c�3
]+x|�����S��� �T���{��>mPU�
���_;c|�� �!�����nm����~Z?>���B�f�6?��4���v�_�0}�������W.P��;z
�(���p&�]t������#�4�o��n�(4+���m�D\�V����h���I�"t{�s/��v<����L3��}��AI[������f5��M!���\�
���g��@��OObc=H8����?�1PT��+���eMToN�nq
�����V�D����@T���������u+�S����-�~v%&��]$��~��i���Gq1������Q�]������zu3�*��.:>i*���Dlw3�B�M����?bG�-�B��7�J*�..�Q@i�_"/������Y����b�������+��hL7A*���6��;�nK?J�������$1��)	�����r�[�"�X����B�z��9�'������
.	����D�:F�Sd:N��HS]��p�]^�������>�#�CTQ��gE;�j���jMR�4�������z�
������+wh�j��j����\6 .�!�������K zN���\�T�e�
��h�u��,�{�.U2F��j��,'���g��Gz�
��O8���0���z�x�-,��G�5��������&S$��&r���E�Cb%��M�� ��,A��t���}Z$��*�O��Vx�l���e��%�Idj�n
�M�S/4�Ct�IHT�d��H�*c)j������t����7��G
"	.�X�'�|6`!��@�����TE������X���3�����.\W^1�[Kxm�o��K�P4�����/i�q�X���P?;;9{!n�|�H��}��96������&aoG�%���#w�@��%�������S�o�|����,ky�4s_���mE���e�x"��u�,��eu���/K��W�����\w��E~<��2�|��9/����\��,�Cw1����e�lmng�e"��RR���Z�Y_�2���+�%z�e9����AI��.`j%fx�M_����V7,�d�'���1e[��BD�i�YE���S�D`��x��k�St������v����V�X9�|
�/���vd���tj7�)�>n��qj�S�4����6V��d;�%�z�-�lB,w�	���D��M�����fucN���@0��M�;h�������3��uq7��
��
/��Y��)1|����3����d���W�d�G
����+����4Z��!G��$K�s}Yo�/y����QuK|B+
��
T'�����GRg{;��������ehz/�q�1R`��'g�`����n5C�:z�L�0�\{���MO��'0���Y�(�����#"@H�d�w��A�^?5�O~*H�D��tP���"����{����x��C���6��t�BZ�����"��VO��hP���M�}q8�t����h�	���g�H:�` ��/ng��cV���~�3���a�h���rK����zv73"�W5f����e]oI1�&0�>����>���Z.�C��
R��w��{]+��8'�Gm��3���Q��_v`R����bG"��"Q����w���@���;y�V�L�K���"���I2�66�RiS����i7��UH�2�[�h�S��+���U�k"���xe����`}��@bc��f`���?����-���Bo�Bd��M2�����^����&������H3����,����,^��������nrS)���\�D:�W�y�)K?�h��S���;D��jG�D���z��P�P<V��}�a��4vL��t3������5�!����Tvv�N�X����n�����l3Z��d�Q�Q���I��2n���]rpr|pqv�����&��N��!��CU�uO�m=}�HaiX���m�]B�FoU�p��d�uXk�"R@�y�k6�Sx]g���4��|B�T��)�D�����I�
.�7�}�AuZ������2�������Q}���@�1���b�>��'e'Du����E�S$��Y-L�$�k!sR�������g�KB�j���������y1j�h5R�&n�Eh��l�����������y�
R�i(r�H�l�FR�v���Sj����Egwogw���5��D-�(G���hi�<�s�R%[u�>��.�o��p�����$-�:�W�G^���`6/�r�����	����}^��
a�EtE�������1$H!?PP.���n�1�^=�o^O�.@i�K�M�np��Sk6*p���;{ Q����z�����k��v����p'�Ql�!q�9~��3��7��pZ����A�1����@��'i��>���h3�7�sj�N�8��U��d�`4	�q%�fu����M�MZ�3v���N4���[���"�aR"NYS%���:a��A������|����y�s����Kbn0���rn�}dGh8b\��K����}��w�����x��������B9������.�d����wF����a��_�lp����6Z�:�<Y����z�u�-��sz!!E�#<��za�6��G�/j�
��Q�������8������k�N��������^K!U�$#@�+���!	G�c2����B�����tB�^G�t���U�lN��H�7G����v������ck�������%Df�U'kY�qW+�,C��gDZ�n,H[g7���fjF��1��/C���;]9�C3��Om�70�DxHeaq`z�i����*�)�~�~w�t�n��{����[�3B������D�;e\���6�D���X�sK�"�8�����U1)^����Z'��a)
�P���Xo���0��&|)��j�������a���
(	[�X%l&�au(P���\����-��Mbf�7 �x�\����v���3�!<�������y6��W��as8X7D{@�?�|J`��D@o�h�\x�f�c5���#c�Eq>!�"OO�D����s�
'�Q��6���W������n]�D����A'j�(z�b1��XU���=����\>{'���?����:��~�?�������!K�2����X����)G����A&u�.A�;���	��a�����*w�t�U�Q�>����8$��D6���a:i��&q����AX."^�m���!��*��X�������{n�-eA�w�������ga�-�a1�px�v���l��
�|O=m�4GR,�i�=\�����?��!w��u9���@\�`n"����������������+�g���e}���fC8�!�}<Ry7�(����R���h_�
�!%e�C�Y8O����~I�P6���0ccfSMW���jq���\��~mW����w�	�����a&�5�"VGy.�� ��E`��E��AUb�1:�;aIu��,�<�����-p�Z,���
[���jC>�)gEB����2392cy�3�A��
��x�9B�/��?����:��
`��L�@j<��B]���/A�}V�diXPy��ZR�*5d=�����T�v���m�o�;Ex��o20�(��z�Zq����>X��'�%dA��n����Lz��X���~,��k����P����\�W�V���u�`�~b�������eH�6}�����r�V	e�x�%��#���aZ�1��${Q��WBO��d#=�����(D���w.9�=)B90@�:��	�OD
R���q�����g�
��^��UK��r����<{�!mQ�h����	�K�-��s����..�(NH�DF�+���`?v$O��tWP1t����\[7���tG�N���,5���`�����,
?	�9���.��yK�Fd��tkxlL;2@q� ��h����F�E��#*'�M5�����D�6�����!��$����=>���]��B}���%vyi�BZ��R��P�a��#J���R?xVg��C�2�������}�����1m��>5���2�q���1�H�0�����#�����&�0j��� �D�i���)�4i\�j����ZB
$"�	I�HR]�B[���&�}\�yRl��W��ed�)p�q������<��6�=4��Q0X]�
[%�Mi�dpw�fn���=�V;t/N�	�8T}q�'��fL���hE�M�W��x������Z�H1;��C&�W(o��k����!�KA)���I�gsp��Zd��s�k�����S�4~`��	E�P�[��D#��9i$��\<�d��~���������.��7r���s}�Qq���xCV(�[������x��N{0dM!�!����RL�!�01W_#�mv��PF�����[;u�~GZK�(J3��	]y���F��]���7c������3�8�C��n����w����nG����7�i����CN^�_��OQP�wGA�`'UHF:e1Z����j"���B_�����j���l�l�������^���5ZYrv4�H��X��,�
�+��0�d�����7�%obO�)�y�?��}'����Rkc����O��gt<���I5��70E���]��s�l����J}��S��s&h������r���1
��������C4_iI��aZJ�l����=�N�}hh���%����:
�,A�:��|Z�9�6BncH��;4zx��K-
('���R����>�46#���c��O_S*�9��L�1
*
7��M�'*Z�'A�8��~�����#?X��Ek��L��a��K*���t�`
����
tE�DK�%����M��L�HAH��
2��8��F7f���|������J�����
�v�N�R��0���H������R,�F'�����(Q,�p<�������Yo��e����4f��-����Nj�,���P>����do�������hU�D��#���q�.�XF;>�j�7��J�	��FcA����t���o�h)���n���}��8NZ@�\��C�}�u�e�~�1l��44��7�|Jd k�u�B�R ���.`^���(p���(�%���G	xc�p����Q�����f���~�|^���~[��[<3_�C ��<\����,I��w%����:���3����L�SI�������)+s�:��a�`���M�H���w<H�-��)�� �@��p��a��)���z�����3���[�L&�y�'��3��n��cC�V%�Ft��HE�1���~,3I������}���S���u��L9Fd�����C�g1t�����1$�z�UT=M��MZ�ZXNJ�55�,�	��`e=��|�Z���M<GI!i�-M��(�"�y�n[������z��q��G�)�]�����y��]?���]�p0sxI��'%���"���>���PPHM�2.�N�n6~�v	�w��E����Kvz.���L���K�u�RSmn�3�,�83".�M���>���������lF��n���=�\0q�$6��JL�\�N��6���DC3�o<�����O���!4�)/]f��_N9���7���v�� 0������$uH��t.���G#W[9�Q��Y����?��q��������0�N����y����������\���y��h�����#n���	^I�hpb
�V6��}��'�w���O�}��~w�������Y=�������^;'������\,���@��+B��_e{&\����	���@"e�z�%J����an��^�l�8�$�L����������t<�����6�iM���4�����]xE���l���/�
g$7�\���w�r�
08�>Z	�zcH7��%_g��G 9��9�Gp��S.�j�w�&5���14�����@��;��*��J*��1?�����������/d	k�e�{gTq�l3Y p��o�*���n��k)�����22un-Pxj1*=�������}����dQ��B�5��)p�\�D�II��/�����5!���'��){���-YT$��U��D�X��_*M���G���#����_���|E�k���~T7_����9�-��K��}J�wwS���w���Sr��������J������[�~��~�(���i;����Suw�N����m������v����J���T��w����b;3�{I���H�%��\]��7��s�s�
Wu4gMB������1W�>�V��$�C�C'��D�"�i�c��?l|��3��������~%���RN�e����=k|��72@�[���v���n�����*�v��P��(�z��>�K�?)�*p2t����^?��t����Y�V�G�U���WV�n����q_�^�f

����,���j��`�����E~_YY!��<�~\��[:�8�K���`<!�4��r���B�Q�*�y���ce�H����O
QOY�9�a�f������y���Y��%������~%��/^�.[2�:<�L=�/���;��E�����9(:��?�kwY� g��Q���^��_1\���s�:J�c+v��"�P�� ���i5���x�Ym9�|�R
�����������������
������r��tC�
��Q�P*��$b�l��q� e:��s��
��5.����D��,5��������kg`�u(�Z�����u�����!+���]g������^
��O�Z����*�#����N�a����/�'�g�}���
��[�Q��I�_��Y��$`F���P����~���L����"�Ar��Q
]Yy�,�l��h�j�y��9���%h�������S�-��Y��o�����Z�"��di��C�h�Ha��A��m�������c;�T�P>��Nj�0�l���d�ioUL�6Hn[�;S�R�t�:��Ny
�I�ba2�	�R�H��It���`,]B���[!G�l���������?����99�7��}�Z�����{��]4'��Gs��-����^K�%��t��f+9���C�
(55pI����M�xb���w����������)��w
�
��($��V���tT;~{Q{[���R���4
������_��i�1N%�����\��N��
����%�����P?�eu��<����jn�[�$�[;����~|��YSK�F�&��@b0\���������5�/�~�F��a'1����kQ{����l��������Q���q�w�����t�J���v�l �S�X��,y]�p�G��&+����[����3
}�0�������;�������Q����)�;�]|��CX��@F0��h���4��T7(-~������4���MzJjI7cK�9����V$����vJ�G^���o�13�����P�N"
��y6A��.�^��h~
������ze��<��a�����m�<k	<#��Q�j������T&�������u|,6m�"on��7����_ykk7����*�>�������+��jywk����]������z[]g�������v����8w��+m�(U2�����9���L��v56X]��u�\���e�����t�2�,��.�J���H?���!E���W���e���gA+�4t�N�H=x������x#���rLk�HnJ�$�!��<'�*�UO��W���6��dO�>�������)g%��,�g*9�jg����n��������������9���q
��G	u���GP�0O��x��/���fo�o�a��Pj��,N�
q��/���1\������~#_�����;�b����hs�z���-/av�	Z��H>\�T�WYu��3��z]c��~�z�����X����{0��������P��
A���&{��Bi����YbX/��J�4��r��m�&���wWp_yW;><	E&o|W���Z�����!�����Vi��o4�W�7�����:���6(��J�N��&zzrF_������aD���i�"�����9|�j�T���Z���_��Q�����h'"R��	=>&4�O.���O��~|�?�����M4�hs�f$=j@����4��	��p�7�t:>'�h�����H�B�*Wv
�l�re�P%""�0�@?�-��s\��w ��D��/���D6���_��{b��&�F�#�}�E3�W�N*��HM8��u�~�	�����
�K��$�����������n�9Y������ Q|/�\�y)�����Gy��j#�)����V@�m�f�2�(�k
�P��N��Hg��i�`�mAd���/LE����������1nDp��F���7:D����.�������w�������������U5�?�~��t�N]�������D���Eq7��t������1��
��2��\����}�3F��Jt������X.G��?>����X�����;;�&>��-dI,z�������x����$R�kT��]�+][@Aj�s�G�y�e(��!���j��������P��%
���S����[�h�bu��@/V�M�/B���wn�7��Y��i��g��"�6��8�z��XQ
���Y���3�IT��Tl@����������^��jU��oI�d8vA��v�-����G	C�/���38h���B�.B���~��x�>_dv����s�n��V����3�{{��]�N�3�����e��]dy����Df���tBc����MD|�D]���a�=A��H�"�H�����m1���P&�T[�V���5u>3�.o
i�q=x�b)����Z�����2ymO{+����T:n�\.{;�;;�N�l9,f��^�d	Z�}:0�-��������trv�[��k:`_�$��~�^�W%�*N�� .����
/u-�U��cj��x���mX�9}�f�h�>aE���y����Q?��y~R��:��i���B�_e����p2��8������i�Z���V*����v����������*���=�Zn���������u���V�i����*������S����J5K�[��_����
M=�a�
5�h��JY@|�:.U�\}&��F�'9"���b�Yz`�O ���?>7sT���r|��\g$mJ��6R�&��3
��NB�@
�����F��-���0^�4�Q-`wh�W���)�����V���t4�+�L�ea��.���M���[q��Ur��)/�|P����Vv�=�:N��[nwJ{���D#imr�J�e���$4h6���&:��$�j�o�1Z�p`����AK�$[����\�)�h�tB���}���'����R�8Y���Y���������4|���S�x����{������,Mg���D��~��ok���p`x��������J�E�����������6gb���v��o�A�����luv�����[���0�Z�F>U��o�]�����Ik��]�D;*�H#��#��X�t��S�`�\��|O��
}�W3����n#�����
YQ"��v�u��?�{�2�����>��^�,�������^���*W�*����^�������r���Z+�J��e{��.�Y%;�K7DH�����C����� ����
a�>�++gT)�>wrP�%�~����:��������.*���n${0���v��7����#���������&8@�sg����a�B<���2���)|Gd�C&�o�qD=p��������z��yvq����+�� �&;�L��Tp�uW�_7$�B��V���;�Ph-�������|XnH���8�T����^��U,V+8�J�9q�hl:�	��vX���{���2<F�33T�vL����	�52eo�v�*��:�z�vn�X�#�\��g��8����Z��1�a�*v=��nd��"j�r���}xwYk����2�}�Uy�����2�4��=��D���v���8�= ���n�u:;n����PZ_c)�:7V���>"�A�Y;:y�S�
���~�,B�E����o���''��k�������/����^�+1����d�������B-���������Q3�#[��tK*�4KX���*7���6xP��I�d���b)D�~���?�.�!��haK�H'-�f6_��f�D�����^1E��s�\�x�m���[�j�"�g��F�t��j��>HLr���p�t`E�I40 2i����$���	[9�6C7kA\���V5�|�5fU���5����K�
�����>��O�(����S~gc�pL�l�i4���h[Nz�t�������S�����`�~�;i��,t��d�R.#����EY����Y%#h�?��/4
	N���-���{��#-H����~�v�Is��v��(
��v�v|�*c���&j����(*����*�a�P������w�Z�?��w{��?�>nR�"���/����VvJ��[I��m(�������-�������������Rw����+]��q�������z���]����L�������$W���@�)�j��������t|/��Z��?�U<%]mDAH(�t-�2E���Z,���*+�2%j�GS�$'�#�kf����<G!e��f�������2[+����d�6�+4nu��"�~I�Ld^��I�piL>rB�����"(�����
(�|�\����*GK�0�����<v��vY9�K	+`p
VU&��D�G��#r�]O<�����h51F���|#D9;� hr�(��
��c��b��\����g&|jp�48/��=*"�Ges�Ca�f��n��Y^�R�R�/��:t{.F��4��j`S_��&���&8�jCV�-���f��xy���<2h����2��	n�x4x��E]��D��V�U#<R!zi�7��?�h��Gg*�*�
oQ��*�poe8� ��U�����n����A��1������ISV�M�-���x1��N���ZQ9
7�����}�� �?a����S�V���r����wd��-��x-�;F~��
^���OE��k����>�2H�
�N(J_W���5�r����`��$�X��X�|�����j���ox�����c���9�'��`�VyOX��t:ot]Y	{�*e��0��n����{��w��E�M<���O���Ft��4���T:u���)�m����@.�MO-
���3i^C-���Pi\�P�.B}RNjW��J3��e�;���y=�+W�Z�*Q��Q:)��RuF}U����5�R���� Q�{��)4��J��N���1��dii��WrQ�
����U��.?[������h{ve�����T���v���v���R'[�lo+�F���p'��d��Cp���k�2����x�f{����t�o�29����/7,/���F� ����k�^���#_qP�}R�������A�0��s�K/�j!�j�l��nrt�Z�'x��R����(G���i�"@���r��
7������m��FC=E�F>~?W�^#����=��Bst��S��v�v"���:���\��mlp�_��/��a0�
�����i��|/�����HU{@)������1�#�{oJ|��&�.5�g.���T����!pI����?�h>�/�pCm��]1;���g�P�p��v��+�R�X-u�%g{��g�����R�v`���.��UN�A�mi���\�AS6���J�������)�K[,���(���[
s(��}#�o']����;s���|.�b��N[�kK�/�8]���&�����������D����K���[��F�5Z7���2�����VL`���� ��i�-���q���"9�D����%��L�y0�L�.A�9�Q$��XNy���'���=h��Yk7-���X�=��[��H�?5��
����~~U����.�.��X�/M��k���z�n�.�������Ypn�3.�Q����tV�C	wU;��$�������t1e��Qcu����P��A�����9Qw-���^�!p������"���42F���g�L[���6R@�2Az��g�;�c8]�p^d���{�KO�`N�����b�M��2N�k����+>8�/�B�������h�e����XH���N�13�(�1m�
]�s_2@�ze;�2�C-�� �������2���Z��-w����9��$�������6E��k�5��������������p�,�������.���>��6{�z9y<o�d[Q���r�b�k
����&W?;;9+H���;w�����[o����Y
Z���O]?���t}\�����Rilk�E��z)���XWP4���S��H����{-OFBXs����o�*(�CI����?F�f��$�;~�5�K�i��/�7nr��Te�������z�]�L|+Jjs�?���+U_Z}y�`�����|zTk���[:=������<�~N�ZaP�<`!x�1lr��`#��D)�.�=��:��'��b�um��9P�
�w�#Q
+P
�tn"����et@���[�����:Q'8��
��mO*�/�tA<w�(�����s��_�h�r���P�%�2�h���*�N"������:�.f�*����icPY����m�sQX3N�uM��dx�����!)(�3 ��O�������������L�BY�e#�m	`���pN���Ec.P�/	(�%7�����J��'��$J���#h��Q���`A� ����<a�������=f�Nj�����P�b|J>�j	sK�����R�����5e�W:���=e�=1�/%$�Wk���������&9���tV<��������M���E�0=�!2DPp?Z.��d&�H����n��0[�C�"=:kQ�V��&�lr�l0.��|���Z�X��I�~b�d$E�WB���*�dN��3��c<�w�{+A$?F�tC�6����B��P�3�Ox�JH+��(�'>)���y�j��u|p������=h���h��82b�7��������O����)r�U��r�j���Y�n(Hq_���N�\�*��87��I���ex���;/6D��0�����y��^���GM���|�a��Q?]��	j���a��$���7�L�J����
w�
}��}t	�F��q��!��Q�_��N�����cHpq�R���)DH����i7G������r��3�17��3��Y�E���C���Q��k��SH�)6��e$q���v;B� ~Eb>&�(|���$����z����k�J��<����
����4�r�X
�2�b�K9O�!_=*ns��9-�4��X��a+(�� ��v����r������l^��|<Bwe��G�
�a��������b`���*�;�	��~s��������R��
M��Z$<�'Y��)'�n�1�$�@�$d��|"�}�h�@�"=>��MP�/���Wr ~��3AE�"�9�H�Of�~>s2�����<��G��~`���K�����}�B�@�����6LW��_���X�P �rH'l"���B���V�S
���s)�A'i�=�-�b�>���
|�7
#��B]_��8h!��d�"�V;'����*T���N�P��bO����U�%?"1^���1c;�Mf���P��Y�-�x�nY��Y�l�
/S/�abC�\�~�����9�VF�����x
y�������B��o���&-��-�FE���K��:�BA	����I�
��xM�SJ��=�����(�u���{���6L�US�0H��L�
V���=46��M:�Bv����X��E*?g,�_�Z
��-o��.�5�"���-���+m������1C]����W3F�����e�l2�f�_e�LF���$�g��T�����u�,5O�R����(ULv��i{�`2$I���	�����R�����#�������
���^p�~6NB�!����r%E���+�� ���
��{����f@@�;,��i�:��WXh&�����Q��#�)O����4���<6,i �Lr�&��*���Z=c%�^��8.sb��d%+��^�k]iL�Y�����m4 j��Zu1�N�����O�g�C2`~#o*P�S��<�OB�[��������Rt1@>�A)���E�k��N~W�������"Gj1�\l�r�":�����0��"�����a��~|X?>��E�Z���=&��t7���u~\R��e������AN���d7�>�$���"�3��wC���3�jV<����y�X#J�����@��t\hh��������:�����>+%NK�}Q{�i��n���s"���8n��tB����hS�%�X/�4.a$K�������s
���h���w1�Z�����D:f����+w�����Gg@���S�l���C#�/�G$|��89;��a��;H8_x�����\_��wD-�|�����'o����w9�+'P�'�;����:9��Y��
��g����i|�>Dd	��/u2t�$���C�I�Z�$��� �N��.N)L����u�x��P7<�_A�����q�����N����A�����:n/{-��'��y���7�l��`���Cm�0�d���g���s�������w������]��0�(�)m7��>�n����F��R���A�O�Z����]D�}���v�M����A���5L�ul��_KgPUwlhq��w��O��������0v�"�%�
�[&��1���
2��2���*'#��1 �^I�X�����������;�;qN�;��)F��
dot���7{���z�Yb��U����@ZI@=\�U3�;^G��NZ��,�����1z=����*����� ���BOi��X�T���jcs�{4������`�^wQ�R��-[>:�)U�W�86Z\�.:q���������N����~t����
�����,0��*��N�[e�9E�%��5�c�|p��`	!���!@��g�l��>8�8��T�����	��/~�C�06�%r��a(�N���A�P����8�R
�4j�VXW�@+��S�G�dw*������x��7z4k��@F�N NF<��FQu���0?�A���|�/��M`|��Q�>RST�3,�P%zj�iL3)h�Z�~t�����������A�i\���]�X��m�B���8-
j�������yp�[
,j�X���*�1�HO7��84��E[l%��9�������G�}gh>�]^������T��b�|�����[#����G0*��U1��
������u.���#���[5	&=��
CO�y���k���p�5�H�
�q���v�Xtz;���vo]��).�f1r��P�������Vm��Y>���Y�R"��vn�W���n�Q�����|�$s!��[��m�.�P�Z��Hr��]{[���x&~rE�(����_'2�K��hN��Fe��H��w*"`������������0�<2�!�Oc�C������7�����ll�������
K1�
P��T��_��q�\��|��(3�L��/���1�^�A��q$�n(neS��@]�;�7Q�B�5�d;Q-]�2W�M��< �pI!,!T�n���E
�!������^[��$�GJ��fEYM��I��YV"����B#=��mA����}��|������>��"�2a�������W����w�J���x����,��17�����.���'���#�G�D�2�h���1:z��h�!r] 7�+�@�"s%1��P������C��p6���ep]@��+9�����_,����~���/���������y�n*�d�����e	�`a��#B`44�m�Y�Kv�
*o��w�]�b���
���L���N,�bm��q1lqm��qo6,�Br�T��s����H��
?��"#��V�{Z��y�Wd����������$J ^�#+�����X���%��-3v����Y��fD����k|S�7��wf���KX��7K�
$������_�.�cg��q������+���}�K<O���9f�<�]�q�q#-���f���Z���EN0�j����W_)��^PDZ��s�+����%��
������g�j��c@L5)�����}����%�ly]�0��qh�LD�`��;6��#�/�8�+����'�_T��}fu����~,I��9�EO|�(�&E0���� Exn�4��vV:�!�h�F����L�G
8K)=��D�g��(i���Y.u+�P�y�
M��d�o���&*����*&_#'j�nC��V�l*z��F�C�b$f��$B"��Y����'���E���
�aDu�a���j�������>c��3�k�����@dn:/����rb�����M.�qP��_��
���`k�i0L��8$�������0����P���Z�����L���k�~�0��v���f������8�4�����"�������YkA����:�>5*�;9:L��Q�tX$$P�.��(�W��#�1�LV@���`�.r��2G*}`$����s�����Q��8F�**������|��Q�z�#G�j��� *��T��L����MD,��#O�X!�$J������x�+�����Ro.O"����X��De�{RS�wA"D�(�a���a���c�<�0LBS�}�p�t��%g;1= )�	���e���L$���.,�Z�d��Rak���� 0WO+����C�|�~F�H���)�8I�j<�Y��V6��p��S����8����]q�9>f���5����E�0y�BGZ�h�Q���I�b�G\��b��]�_��("�(&2������$���1���-��XA2�����:c�tH��!��Gk�h ;7������f���Ec�� e	�h,�|p%P���A��Y�(S�
Q�h^PN���,��>����2�2cJU`
�6�\���7�%�h����/�s�^�\DX�$��4�Fy6�x�90d�c�\�����X��:_%T?e[�7}������L��u����+��F��I:��>^��i)��������zD�'�aL������}��NOKV1� ����|AE�e����v���{����y4x���-^v��jYs��5�WX��;i�"����-8�g���9o�(�md��������({p��V����+�s�P6��1B��������s#-�I�?������Qz��	�1�B<��	���$�nMF����[���YN�H�C#��C�@��6$W�,�)l,�i{N�q#�.�JR��J�,�����`���r�:0�Ar��b,\:����{�����J ����~���Y����@2���L�36�]a�sT:�d��4���h��7{���P�VF����e���/���L�Y���W���/8��j!���(��x���=����Q�*f��@�c
���n��:C=����S�%�b��s�A)�O�@���%$�)7%c�G���x0�td��8�?����
p{H��2%����9jS>��yx@�����Q~���	�������uv2=����-:��_&k��L���07%�2w���P�+�=
�� G0.�����k��'�����0=��W�x���Z�W�^�#�������&v�`Qvi�3�;4��V�ss�(�����g|��{C����@3��o����,&�r�:�a����a�35�r.�����u�/p��8	c�X���@�rusEF�=���8� ��$��V��������C�w��C^��������hO���D �RWw	���I]�����Fg��F8"�@�5�Q���,81@�H����H"�@��ah�w��c��D�#	N�]-W�C���P��R�
�>8cw�4����#eT�2a��\1lMxa��\�1
��(&���{:�l%L�NF-�Y�w'N_�[�����V���dP�lV�9n�~n���B�������Y��y�E����|2�8�5k(�_g���J['~��
��p��M���H����=�1h�
�JR��1� ��(eXQ��}XEL��I��y�-�r�p�l�2t�d>�d0���jI�T�9���" _�)��+�}�6WV�������d�x�D9���G�R���������kt��a^E�5r����q^����()�k�Tb��%�����"��[ld+����Jk���=�Z�":��#+:��`A������f\q�HsXd�k��X�/�{!��OAlP�����R�����p��W1�1��w��4`��N7��0$cc��k��p�9*�y���Y���7Tyqd�laC��lt3��,_�'fz-8�}L%}����d&��\�l�(���G[�����H�8T�����n���xwg���6�����4
��VEI���8����V���
��{[�!G�[U��?1�)�(�����?�:c�����Q��*r.�qD�����~��X�W
��R�J�_���G���V
3a���0�\q[0f
`���(p���Vy��]Y�JG74.��i:"Y�Q��a*./��7\Gf6����L�5q����*������������;:����9�o�
������p�����D@��������V�B����@�������[���S��_���QJ�x��� ����E��
*9a����L�����l5+�g�A���"����_�Qx��������*G�+.{r`�Yv���} V'�p���	 K��J������
v��
�I����W�Rg�H&�����(��<��{�
�a�'�<��T�%����(��yK����|&����h����!���~��X�6��y`�|���r})�1�|Uc�:m��[G�B���
��4f�C�F���:=�dN
*e�)�����
�h�Q9^�`���1@�����"�=�.���I����F��'[Bf@~/S)�l��6a��NN[gu�d��Y���7'g�9�b���EQW�k:T��Q�c�U��r%���S�=f�.9&[>IsiY����]h����lHU8n-�/I8'hB*��/+�0Y+>�������rPDZ_!��B����ll�-WY�G��r4��R��C}����$��)�I7�
��>"X�l;n����0�0��F�mA��A�F$�N�5RR����E�������V���F�2���Z-��EN�b�^��w��B3���T<��}K������2������9�f���p�lk�����"�?�~��������&V�j%�������0m{o���WL���>u�;!�����(��p4	Q�'c��+�:�-��������r(������9�FB���W~���p���as�30�H)�5��V�!����0�H���tB5t�8G�A��L��O-t�`9���*�~�W�V^[e�@�[l=�5����
��{�|��,Gn��%^�L~	�#&��QX���rF��T���IL�*�qo�\E������a7���_�`h�r������}��!�T�i��M�1��bFJ�����)���g�
�.l���z @ j^��������k�g��.ASW*����JiI
��E���"
�����;D��
���r1Sm�1)�Bu�QZ��lN=�������,K��L3�������ER�����\��mc1%���2j��Z\/��mNZ-�Z���bB�7��c��CRUM�mA�c�2J����L�i"��2]��x�@����Z�z��QzE
���0P2i��94r�b��C�GnM����]����n���;}�zsq|@�����s�*�@O��g8gr��s����u<������=/����-�G�J�v��_�/1���,tCn��o�d�c����m����B�*���Oe��`3�iQa���������2m������-.x�-fSr�!5;8y����ag{���I�@v\�^j��U���{B@P�x1v����kz��O�5�hD}���si�AoXf%
/������B��H����/��8�M��'�p�|�%�s����D�"p��a���ND�����[���G'���@�%:���0��^!�F��A4�|]t(5p[��������/W}AE4t�����Wep��d�E[���BA�>�G�`���<N$�~#�i;���dL��Am��5�xm�[`p��d�3����
�y�1�����p��s�da��AKP�S����W-T��T��|�ZZiW��hq��\�a3�&���L�u�7�7Q��[u����D�K��|e�@oM�8�o!����G���NQ�U��+a9v+���o���#1SRyU$�|,m�I����/�8���r�i$�7�v������;��HOX�:F=�����:��a<���RX&������*��h>�$��)���3�����G���tOE12�T$���5q�jy���F�LP�;���&��d}�(���A/{�<p�0���,�Gf�h#Is�[	@VkD����n�����������c�3�O��vO Y��Ux`F�y9����F����L�0�����~?6�1��������������I��RO���w����G���D:�&O�{)�3��R�������"����l�=3�y������/0wjsiCn����^�k��rX�����~/���3���"{d���C�o`M�@*1�-5�������YW1�(�yr��h��eG
�e�(��0����f��K�rH�"$�<�R1O)i�KV�(P{HHj��AD��x�*��E ���bd��i5�\��������5��o��r ��K��!�R����w	'CC����(RT {U��"-Ci�Je�=/�Xu;P�1)_���%qIe�eWTY��Z�p��k!�o��h�J����>eUt�Ek����.�j�|�������n��M:�
������M4��o"C=��A��<�A�8����og�������q(�E�z���u�\������8��<�q��q
�y����9�JDj���d�"�t��}\�^��
��R� ��*%5(�l�p��
�<�D�NE�����I�|�AD
V��w��i~����p�x�T�(V�yD}�Jd�J���Y����8��BC�1&:u�(^{�	3��@+�Vo�Wf,���,���������z�u!�y�o6��V������l1:	�����lZ�"5�x�����V�50�%�c��e%��.���I��U�����#U�&�0����k�P5�������7,����_���re0��1�jj?���I�-�F�.����ud��C=��.vI.ir�������
<���9��*�cGk�m�_t,!�Zd��	���z �\Lw�t���O8�D���*E!j}�����(z{��8:��B"�����$@C����u��;�$��R��K�;m��v�s'v/P�k���!�]k&���F�6�����s�J8�X�m G�U��I�hJ��&���y������E���<9��Q�0j?�x@���<�'G�$�XPe�4�.��~���(BC`PXS{��G7|���$�����W���;��|��)�[�F�1�<�)��*A�*=�d*{O@���Y�h���@

��q2��������k�\i����� w'��J��>:�'��hx�H<�2]ymJ������jvl(]:ql��/#��.�I�������>c-��
�C��.}xz5�g�:��1Z3����2ev�I���(D����!?��8���~����Hi ��g�	#�3
�H����$S�H��5P�������#��xH�R�j����,��6�
5�{����x���H)�o�|b[�bDao�_|�w���%H2���J�5K�ef'���6J%��t�?��n�����du_�zCsc���Q����z�H�YM�WV��T��0�-�#���E�W�%��DqC���;��Z�B�_���T�kve�Qs=0��&/��Xi����%qF�},.����#CbsD�R�,o��|�BcZ�������'f��r���<�E^���(F��� �l��������[�`��=�c�#�����e���C��j��w��e�������6qVo5�����K�]��������81�C�PB���bPXj'Y����_V�D)S�-��2������1Uz����IK����L��P��U���0?*
-�W�.=��x��Y��|4hN����&����
�{�	�L%��@�1	������l�u�'h-��7���O)���y4��hU�2�NA�h�1*v���V2Z�)5k%�F�O���x��M[:�adQT(�E������T���C��ik��=*��H�Ej�0y�L;�H��9����kFF��TF�y���d$l�B�64-�g$�(RT������c)��l�������
���=�%�HG���/���j�����g8u ���6njU�\����Wc����+���H������*�H�G�u���l!�0P����T0��3�/���������1����S��;4�G���ZN^����ha2�m*y�MtL��6���AWg	�J�d�{���F��ye�Nj,�#�P��Q
U��Y�wd�D�N�
��������������k�ypG���X�����h��i��zf�AM\��W����>h��>I�m�+��dv�����GnV<*��������'��t����n��p���
����!eMY)�mU�a�v�fo����<u~�7N�:i#5M
(t�D�Elg=`>C�O���9��Ph����:�E�o`��h�Ld�Z'�q���.���
Rq��&IBo����DD
�d&n�i;�&.�
�c3o��K��P��0�;}T7�� ���}����^���e����*�[3g?C4t�`�o�t���j���I,�
I�S��������e�B��<A��:�w���*�[�HN3�G����Y=]����@�U�.�(pM���To_�s���sN;>����j:�.�/V'����B`��~9�G4�]��<�R94O��:�VT�v�f�����A��:J���%���1>��:��N����!68��������'�-��J���)b���/������p���V�=��t�8fo��Q��m�,<��m����m��&�m��?���&v�aog����k���k�)����i�0��&m��J�	�4�r�)|S�"y���u����S���?�2	Y*~`a��O-��Y����p���,2vH�-�;�5b����D�ruC���B3qX~�o#�8��1��������`f9���
}�V���5�~w��Tm`"���9A�(�����������$N�p
����qN�6ut�]���#�f��<o^	���=V!JM��8��CK�[r��P-���7���t�p:;y/��p��� �9�����/o���e��*�^���S�tqr&k����LD�����oU1�x���u�����y}n���7Ia������4`��X���cQ;:kL�%�Tx�gP�����$���������mYSB��Y^����o�q�P�jA�6Y�Y�XXM39���Dj)e��tYp��L�9Ba��R�ER�u�h�DY���

!/3�3��n��B��"��L�����Po��I
zi����*��AT1�$9�'N�:k�l���
������4[�)&W$[T�h�f���H��/�m��?������	Ph;������)J�2R]��g�"����LKa���b�zT���7M���8
�?RV������+!9]l.��3{d#�o{���X��w��M����72�����G��6e�'���^����g*.|�9�3g��.��M����3�3e61��qq����G��{���H-}1�P���Y��Q@U^����4�H-���d�<�d�� =Nxj{'mym��)����H����/-TWK���CgE��I�S:;���h�����=�r����:��R��5�bL�]p�N
:6$�o�y��bB���H�_���B*����A�������	uR�C�1S{9��d�������I����2{���������=��e�K���S�����
���Y�b�o=(P���@�����p���t���S�M��
gQC2�.�����R��aj�u�cg�j�" ]��J�o^�0[&�b`$. &6�=
�O�������F����ir�)����Jr�O�[S��4I"��9��2L�S����&��L�0�o����f? ��y�s<\�����+}�r��x�7��*==lP
�$mG����>���s��i"��k�<���&�%U�|�\^Rfs���<��9�r�OF��������KV�[J��PN��H����N�v��,��
�LG�8�������'��2���irg��6F�y�i���"�����������4�>���6A:��p��QD�n%�ZZlBJ��,W$Z�h3�.�#W�},�P�{4�����.Rd'���:�rbsQd"h�`���d+N�?1�3�K
bZ�Zt#6b�2�1#��k�Z���~#�KIX����3��&��'�"��.���ilQ%"�Q�b&1�(O�{J���|����q��������}Tl��D�{
�Cm�7
����V����%��@��������"�4-�����& g{&���������1�<�[l?�eSd%��m�I	i�U�5e��K��������:e��LQb�9M5������fU����4��a�E8���G�W]��x�j��T��.�bmLC��s�h��F��`��\_�.���������\��2�Oi���$i�.�FzRy�e����C�1�^��j�[�e����nSzE�]����`gSm�s'���3v����[\+�5�&�"F�IN_�)�)9,u�
`X2���_3�]l�o���3��K
j�L����K�d���|�!^�����w5rm�&��j��~{�{�~e�~o�!��q�����2+�`fQf	L/T���]1:-�sVJ9�j���V#��s�#�~<�5��;fk���?R���3
`D�����f�T��e���������?��(����\9#��vi_m�+�o��-��[�������^ek0!��}��.���iX�GK�kn��vW��yq�[q��T�7%��?N��d� SF��__�g�����Q��?�����r�M���9���
��T��yx�nLC��������o������A3�d�� s�G�<������}��dx/`����1����b���X�����������]c����?�.
q+y�R>�$� ������Q5kx<�,��f�W�Z�>��;.f�7K��c��f%�� ;���t����R��hvV?���6���3�;�l�c�Fmi���j�����k�$}���l���:`v�u�@���x>�1gA�4a����o�C�%z��<�(��{r��v=���Q��wY��.w���-v\�i�[]��p�%!o�c�2����Jh�l���=}���B�}����
����Y��8��]�����r�9�i-��L-Na��i���7����;;���l�����6��O�u��1���]%��w��h�Y�[�1E��$L�Uvd6l����T���F/rQhU�=�(<��/����DM�GGf> S[d>xy	{�4dc}�4��l���E�TKF�>�g����>����[#�L���z����n��F9�	�-&���]�����NK���CT�`W��b"�TP�n�
�>��x�X����w���iG��F�V�e��F��E�\��V$'�P�4*�k��E=Be��w�^��T'��&d���pFq���E�T;9��q��;��*/��2�#C�h�*�]��;8>�lMY<C��uA~�N�2��pn[��,3�u��@�u-_�U��He�Q"G"�hY�%����(��M����j�f^��.1�P���BC~����'MH�N
3�Xf�Y��8����_\�7���0��,�U�o�Q�Ca-�?6�cljk�����S��K���sjC9�r�d_X;����y�Ra�4���A�n6����K�`F�9N�V���,������?R�D��$��s��TYR3����k��Mg'8���ZBW�w�Cu�b*K�u�4��?6~�$����6����D���������5������V"��iFK 
�U�S��D�[���-�f�P>/I*�g�"��k���������:�v���h��%�<�:=���TD>�������������1���iCYGr��Gr����nQM������9Z&F�1�#3D����Z�98�3��x��q)��u^���Q�W|��{�������#Z[�={S�����g�Wf�1�fe{���k�b��B����%�f����/�TZ����.���}��Y�N��LUdJ�e����a����H�yv���p#����p��W,p�]Y���x��'f��&�7l"�5�rg����y����!���:��C*�8��> �z��i��Y����$�J~)s%�Zd-"��2����lt�r���z��`����f��+K�
���.d�?� 1��3��/x������g���u���i�"��sk��N4�YA����r�������(���n�Q��/�L���
>z�����]��w���X���O��bYd��;��S��U��*�R�u��+@VL<�(!��p(R�,���aEH��`�
���������E�H���A��uF�����R\�	�s6�E��Pj��fD�}z�� �=z
��������E�g�����<���S�	�����`�b-'%JM\�@�
� "�1�h�f3�qr�$J4�y���~�#���~�"���}��$��Q!")�`&r�������<�'�8�=O�1���D�L��.M����q����Ks���G�6Rl�4�`����(�)��?RNtP�v��������o�/4/�6��	��P!�:�_N���w�V�����3}����>�IDN5���������ls���8>��i�u��)������-�4�TA;���B�N@�Z[�+�����P��x�sI�R��
�I�x%�D�J2�T2\��3��s������y�+�)�� p�����jAH[���U�R�-��{_��'�^�d��-v�28�V*P�z�Q�[{�A'�����h���q��"�x��y��L{���� ������N~<O�����m9?��K���� ��s-n-�c� w���yu��s����a��7k��s��"�������1���e�kYq�{�F�;V�6���0r9��uE����f���9$��2*�A���4�B�9�6�u�'�d�;��[�	�OY����\/rP��-M����>�/�&s"�����Z�f����`|������FlYUy���@h}V��n��N��J���3���/�I�Q�$b����� r�c6��pu��gM$:�4��(�Tf
��FR��![a�!�[Fl���U00����TnX0g���0��C���b/�UTd��l+�+������?���"�����?}w��ac�Rv��[M��I7#u���|�BR��.]��{A�z��v?�0l";R\�1�P-�^R�$�E����a���S�<�D y�O� ��H`��&�Bx$���X
1_;L���}���������'9lRL����r��)j���y�F�	.a��S�x���!�"��q'n	!{�Kk|�����x|��E�q�����(>�KE[������e����I��~�f��H2����<����Z�#���
>Q<4��u0l�9` E�����l�b��$d�t��C��z&r--D�	���4��&+�q�(�&\������0��9c���c�������v��p�������=�mM�)X�.�����(�(��� ���7��T�u��X���C%1H"����%����B�<����@���e���95�t�bG�gYe���n{{���No�[m�m�+�\*�lm�nnn�������<=������RaGl��]����*�G��k��F������5|�������|�ay+%����(]IY���f�	v��4��q����>��Z�9��`��s��8!9	��!0<�N��89�B^����4A��������/2>$���
rU0Q���w�/�,;�Kl����>^�p��@�"/��T�����e���>���l�C��:Z�����f����������<�VX�v�]�'l��F�u��h]�����OgE&���\�����J�F��Z h�q=���l�0���D��v]�]9�>�T���b�&�5���}�����r{��w�J�]wg�X��������+����n'���e�+[e�����[P~���S�{#H�����_Z����Y�M�~t������oU����1a	�7������X�+�����A�2��C�T��X\RM[]��\^���Va��n�X�u;[��;����5����B��;�UZ`���2s���t���m^���5��SV�^ ��\h����1Q �H,-d ������O�i��VxP��ww����b���T���[��x`4��F�f�;����qJ�1�P�	����:.���Og�f�u�����u��������uWE~<99����W%0%,G/�11^��0�b�Rb��iki����UY�i��A$����m������4��ZM�.g�����I���X$����E(��k��+Eo�-��NT�+�)��<���<�C{��*��\������
�\v�]���uJ���j-�nY%i3�V�I������b�B*?n�I���vP]��#?��@������6v����p����X�K�E!�Q�{*�*�?\�
~��X��8�:��:<��(^���&C��9��c4rA���eYK�4f��-�(R�9OI�Q���+��N������x2����cU^�B����`�$��g��	�S����(w��=�U��=�U����/����1���}�u)�K��;�q(�����X����Pp_��-��s�S$��Y���������������n��n���(���Q�D�e�J���"S��
���>m��N����M�����o�&	�&Z1mk��X��@a��T`���8'���cG�A��q� �]�&s���p<���'����3�S��N���{m�q��c^��og�������M#R�IXg�Br�f<nc���w�r|v(������`�4�f��xP�!i��[K(H����0V�������Z]�s�ik�-07����V7�(gR��?0}C$���C�r8��L�)��'��/�G����X�
~��V��z+E�K����Z���nj��FP"?�����H����z�\5��4%S|M�JIX�c��n�\��y|�78S{��[�����s�td\��gl����L��T��"%�1�U6\��G�N����$�:�4�j�9VbI�N6��������y�@�x��!d��},od�EC/��76��4�B�IBU�����M�$2J������~�W.�vQ�S�t�;�����DVk��DVI��w�I���=D>��M���L��,�G���
>�b��%]9���1>��L�Cf��I S����E�I>�g��c35|�HFf���]6���sR��H��I��:�����7e]��Gc=V_gN���4����q��v�8l����0���5�e������7|�;��g�]�w
���������~����Wq�������
���vJ��_�I��D����V�Y��HR�l84����.^��7�K�1>l��K��(3��vJ�}�"6���rU�4�`����>��
�<�r������,�P��<�a������"zNX������N^9�!xs��)F���5������l���@�~��x?N��2�����Q ����2���o���"��"#�^"_����S����D�R�,�����[�i�Z��0�(��=�4�����f!���c�A�G@t
���kMr�5D�#�r�Z��#h�d$SR���
�O��3��%�
���e+��!{r!�) �u�7"7�CI��Q�h�������%v*�]$�;�r���"kBDH���e,l�;}���coD�O�M%��[c��MKe����;��`y�+a�G"�TG��N��Q�MS����1`���89��������_:�l�-a�:���Vuq���������G�e��G2K��/��u����Y��
s��xd+�5,R�4�3��g,���5���TR�����T|2[HcT�m������[nu����w�������p�
�����YX>p%�n����t�n�n�<w���/pw�h����S�c�n�^�npw�jmg������?����'�kQ�5���
m���~�
���R�_�_�T}X���j��x���#8��=�O�6������`�W!k����2��A48����w���c����t�hi������(���{�@��\�;�;tDN�<r�����l>���������F�
P����~����E>�����t7�:�*���,?0m�o�f�f�NNO�����Bw�veK��s��vq�hg�
:cBh�l2N��(/�wJ{�<���W(�q����95�\.zEY��6���=�7t�7���r�t1.C�R�������5���������X�8a�[���Q�s"Q`�hSi�+�����Qr�8�|ag�Te��V�]=B�M�2����N�g���������o�5��~�&^�5�	�lW�G�������F�'
;/K16�� b�c���q�v���B���K�f�S4��������WS�WR��u�w���r�\,����w;�^�v�`mc����o�v��L�pv�6��o�	E+TH�E���
T��p�r�^���!�`'��)p��Fm�9b�jC?r�{I���&�R���(Y�7Z1u�t��r�9B��+U��++���[������B�Z��1���+7pU~f4+������Pv]���1h������{E�P.���>/�*-�\��e-�E�!i��S���x����l����Jw:����cO�!Qi�n��B�lp#��j(5����t��^�����>�6ZX�%	(�-�����,��$!�u�g��Q��`��&�~���uj�t���n�cL~
+��U>~�kqx�Z5�z�=�����u�~C�H�-�9����D5iv��/��Px�~� ~?��P���W�v��:�2�g���������"+^&s��bV��D�������/��
�b�W+�����o�'�/�e���<�[����<j�7E�
D��j!OO���Q��h>1�1*�q
1b�� ����&���;f4�;D��)%��-�U�rH|�X�:{�=g�c�8��R�o��"!����U�!���+d�*b����!�bD	��n���n^#]�� D�������?���`��A��d��ej+�����?��+�G�
$����Y��jAg���W�r,m�q[������`�v=\[.�������������m�&c���j"�N7���f�B��!����o�M�r��G;�	�<3�������~�Z�lo���[������M�rKK,��",g�Pe`����/��n�r���f�{�'�A�0�V��+���3����a�=���Vn��u��������;��}�	M����~:2��VG�����C����g�f"t�`Hopkb�B�:<��5��r�v�(GM��<���@��N�?:�+O������J�c�v��T�d���;|���G6"mf����N�D��/�����
�Px� Bz���;�Ap��g���guY/��\N�1_e75{js��
���Wu���vg��.��N���9�_������.�d+�:!�B��Z�������
.�^��=j{��M�����;n�tt�:�����l;�G���6�v��.���j�~���F��)��s7�cz��q�p�j.�S��A�E���^1����o��l���D�'L6�o�!��#�("E���N����ZUV�>t��@�8u��������pOSj�qZUZZ3\��z�@�U@��i�������!��p���
���$%�Y�<]@���v����w������N��W�BR,
�I����%�w�O��"H�j2�ZQ~2x��k�"�����J��gU��N�j �!� �p��Ri�9�<����90V��������5L_)�-EKR��%��1�
+"(���z���0k{�<(��yf��g�����Y�p(������vO`k�l�3TvN0h�1n��Cv�������l����=�)xz 8�-��M>�z��c0
���bqodp���'R�L�8G�u���pD��x�Ns���?�p�Z)�w����?=�H�Fh�������R���	�bSgU�/���7���E*�HhZ�?���%��mm��v�T���wwvw�v�*U*���?��]M��7�h_B��fr�o���z���{�*�^y����v�����������Vz������~gkwg������^��#Q��������b3/�/#q�\����!}�o0v|����q�zX���)�rU�Fc�^)�r�Ee�����(��J��Z^�����Di�y��/h%��DK�VAm������4�|!�����F�M?����G}��/�n����fs/*G��x�>��]�/�^�WW&�1���K0�s}Y�1�#��(h1/��������:,��_F(�a�$6T�8�8��g'���_X���$�\e�p����v�XSm $��B������@����lJ/��d����;�3��qA�9 Gk�gB=>�I��X��<�����4�:<9��l8A8X�����<�O�i&��g��jW���jF�pm.�WFr[5��)�s�GMq�8�����Bl����>��	�x�8�J����6�o����:�� _?Zw,	 �m��8���A�����q����.|���L1���Cx�'z�WW��8r�#N�Tz��mEq�#�q���3����9E(R�9��]@ZFD���P:}��;uW?;7Qv
i�P�$|�x��S��������0T�Ma��fd1����X�ja>�1�>��������F�j���>Fr�7��(	D
��3���(V�QPTL����N���P��������t��>Hs�%�-�ee�}��c�S��.�8*f4��A�U�� ���������F��������J\`jH�``�IR��P�$Ul63W���L	��y�)�q,;G��MN��e��(�:�G��������wq,���4"4���=$"%���������j*= �c�������(h��C�����
wD�I9=ua�#���`�
����r�S�,>����r�~��~_�|��Ry�U�|D������jK1������8���8h�Hc���Q��#}Q0%\U���i�%��}��i���j�"5|��*Wz���h*2�*��(�bc��h���QR�wZg��	Q�T���*��\�����,�������[@o��T�+������U*�=��f~u�����3��E%��������J�Xl��v�LoJ<1[KS�������2"�`*�7�M
��>4b����AP6��S.���V3+���o�):#�v����~5)U��xU�c�&�f�X�e�Ij���?�����qC������wo ���-�{%gV���t�T�aJ���Y-�s����zC��C�F��+��8
����]F�����*|�����W1 �a����
k>sQ<������4��|z��>1x#�j�����/���nw���.(����	��(�I�F��xKT)j�l�1�ca��������o�7�DB�K=O'��{�S�p
�5�/�D��7�-I��D�*�8�4���w�q2�$C�>.����L���|tb���N2�&��z[��gM�63�QD��p\9A��2@��)h#VV��I�c���a�R�C�~o}lEp���i���FA9�l��-{��F����^D��������ex��c��ej���+���������0�8nn�4�+b�������X�����V��
�����>W�������'����A�g
����iL4������=��#y�4�F*��"�2s���P��J�*�:.�VFU(D���JE�7q#��F�-V5��=��g���I������*c���_]^��#�A����s���k���x84�
���yf��\�c�<�a�(��]#��}�1���[���XO�T�������.���?���^�L�M�Z�N�s4�&�:��t(p;��u��<��a��Mu�C�����(#�����o�����(���oVk���]�&y4�1��s����r�
;�
��e�;#4�v�
cG��'�X�I0O�6c�r�hG��D �}\�u�/������bBV	���X�d)�����B�d��K���1��:N:��O��$����T]�!����7���#t���$2�B%B��X\�}�3S��t����A��(�;����>�Z��������bF?��~��Np~]32;D���B��d+��'���WVH�u���e�R�5��h��L$��~�-���'9�j -3��c<\0b!��aIJk��e(.
�j-w�f��7�cX�d=�?��~�AR�����
��UW��?*y"��oJ��S�����%
@#���������w%��X�%��{��~��e�O~����jG+p8[�����/��.���8�OZ�Q���!R}�a-�RTI ���>��r�R�n��Ks���&���Ie$�8�]p�G�}g��V-B��ba�p��Yr�i>���2]��0�+Y2��}�Z�;O�(����C�x������g��0��&�^�i5�g*�������*<}��6�����&R�	N(����}�CM�����������������U%:�Q��tF3�$-�i���"�5���
����S�������U��HEw�
{{�QQR�m�5�NY+[1�����v"�����T���m^�)��
a1����)7�m�>�D���jo��N.���������N���T�����}/YY>��x^�=stR���7�>���S#����>��l��/�������65�������������������+����������35���c\���Y-#|g��|��|��>?i��g��X�>�5�������������b��r���?����hVQ���M�����W?�b��8� ���C{�������m���|�`J��lL�wVL�����iL��6L�����X��b
<�b�|nYTxc�Y��)�K{����Um���0^e`
��
�,L��Y0�kLY�	*��t+2]x��JK�ivg_*G��w����\,�z��nw�T,nm�8{����y2�E
M�Q�
���Ga��o���[&��ebK�R����c|�v��ZBa�c�O�j����A���YN�;�7[�<����!~�o��^�on��\~ro�����@<�������X��d2�C�\��p�BG�H������V���d5
����l��|�Z�	)�9�1 �8D��h�?��3F���k���A�4a ����T��V
���K��f����U[���0�C��C��&P�{�W����$tm���w�������W����;;�DzfX�J%F�<�*�����~�\�9GaE"/[�'z��A)��k�~��-^a��x%S��d�D�uY���2�C��G�WPG���'^�F���~=��!����$�E���s5[�BKC/�������Z���f�U�k��b
�US������a������C��8�����Yx��jH\(���5G7kz�xM��F'C<���kwF�V���1�-��x��{�r�RN�Y�����{LJ���i)�]�j#�_j���l�4>::���0�A{D^�t6ti���_�U<�1���mu����R���i�\�U(c�������>��`�Y���Vs5��f����J��6����DFwK�D6w�u*�O:����5�3 �!4#���*e9Z*��.�iw�HE�EcC��Mk��dt�I���"�w8J�`U��K3��c����H��!�����w8�JB����f���p6Y|���Np}��~�-��Z+���{��@���g|YN=����R�J�2y���,����	�:L�������Vh�[I7x��T�B�b��vo3�h����m��m3�[r�$Y��7��������+�#F.�2�!���H�Q2�g@!9��EFT]w�(��|f�X����&��+9Ei�u���=��S+gE���s�h���5���H��:8�����1�Oof>Cc�X��$f�*�3~d�iz�v�:��l{�,	=d9G�z���%�<������,Th�)���D��K	3g�RO
':���������iaY��$���]3Ovc�����N��il^�CdV.g\6S��,,�G��(��pg�}�&��W�0-�q�1�B<��x**AP���T�#�y�<;#,������u
f/mK#��P���`,HK�W+"�X���.��d�1Z�Bi��!g9���)dK�USxW���B����a~��2��d68S$�iU�[���d��"J$���G'8���Z�d��4��z4�h�����	��BQ~�x�T1eos�8�c!j%�xS�8�Z��F��9�@�"��52%=�|�'���p`��!�#�{�7C+�8=���C	'�x�W�A��Fs��Kq����;��x6e��z�0���������2��<�K�`�{�7��O��_��X�X����+f�o�eZ$��(�UtIMI�1���%�M�3-�������9<]<O`��vr!���w��&�Y�
�����X�v��T��4~n�xr�%?}i���I���z2�,v����>�bv.���3��p�������������~6A����"fQ���N����I+zJ�;��^��:(H�����e��YWJ{�:e��WY�0S�#$ntL�x���{��R[�xM�"�����qI��S<�2��\������L�A��9��XJ��2���i����QbE�����wOA-F>�^e"}w��Tn+������Pl�s��+��t�^����[�fd������54���PAy�v9}�Z.���f�V0AE���j�8]O�eb�d�)�!|���?��T�'J��k%<$;��4.&H�W�NwJ_����m�}%��VH��`H3�hL�X�3�M@�r4Sz)<���
�0�Y����F�����O	M+��ja��0���cQ�z��#��b�t���a�8y|��uPk����$�!���	8��^��-��l5\��:�wi��0v�$��������+al:Z��zS�Z�z�>��=��++������C��r�f1w�c�W������St��I��*b�G1(TRr�(2��j[j�uU��@��@�@�<'$��H�zy���	����Y[yJ�fk�e%����~>��
0^F���hX�6�����DN�FB���#A���G�1�r�Cy���!�>��q3|yO;����o�	�+��Z\CE�o���>�h-:�L5��<s
����j�"��	8`]e3R���L\�P�+v'#�++qC���D���l4B-�#��
��_Jo-�6FUi
��i���=H�U�
Ju�\�Q(���������8�H������H*�[�!�8��t��B��	+��2+��%J�j�%�6�%���#hN[���8���'bSDg��<��l���"�k;c��/m{J������R��Q�B:]��(����+w��Y<Iu���;D�h�����xl����-@��8��������*�1be���5�f�kw&o���gf���I�@�x������&��;��i#�����?�#E��r�^�����4W�XO�,R������P�Rk�x�U��x�EM�C�bq[(�}�"�����$������P�g11� �Q�T����Uz`@����x�����m�t�!��f����E	&@U����`�������3�H^R,7�oj~����8)PG�.����>n\�TO���� OF��_{����M��+��}������w�0�`��Sl�bX�r(0�5�
���������;&��1�T����rMv���}X�ji���	Z�bYb���g�+�s���	�g_2�r6��B5*c�5�ZAkB��1����MIh�UH]prSRbT����Ab�����(�}���hu�A����&��U��Hj���N�Q`��uQ�����d��dhvbY�/(_���+�N|��y	c��tx���l��s.n��f� ]O(�k��Y]JP4	�c�B����f�~C�#�< ��;�G�������j�A�0y�S��WC����F!��>�����k{���o���8 �y����������4��0��^�t2�mN:4���
}Ux]��8S����D^�����2����:��QR�K��i���3���_1���f�^�4�5�tj{+�;o���c~��\_�G�)�����gRu�
c��l�`�#��	�Z���e��_��c��]���$����~Vk���0Mg�,>��� �b jU�K�����9.\�z{��O��&���7,<�����$.�9���cqNx�����dN�<�*��r�J�1��������#���	?R��6h������)��,���. -=���}�[�~�s9�DQh�1�������	T3��E~Q�N,�Y�t.�x->{�����y�P����g�
L-�||��v^?����&~� �G����o�92"(�k� �3;�Z����'j�R�����G��y�
���IxSp�+����z�Y������9_���(�s��Q��_��]aq�V�
P���U�h����0{Pq��]<��|���I)a������Y���K���U�=y��`��vV7I\��	+���7=�:�H+
�7e)�h��D���z�x����ph�G��$I(l���u�����S'���9^�y4+UH���3�
�xA�Uf���w����������~W�YI, �����#��+����%���������\���/<�5�E.	}Z�J���b�p�Fj���9S@S�S�SE�e3���5���<����1�������)p�1�*���md��	n.����n\So���)�����uB����'i�-�����<��&H��� �[�:EFGp!�X��C���$�*��3L�VjK���M2�,=
�&��Q��,��4��bYi���F��K��*0�?�����J>�Qu�X������z�%Q;>���{�l�x�O\��Q#����4Y������_�T5�{���� �)k������@������m�������|��H���!�V# U#�X�l�^���*@5���dD��9�L���P�����ln���Z����M�f �����c�)20I4��
�tLQA���M���rFd�F�k�8�u������8o�c�v���y��KP�4N2<�%���� ��4es]P��iRh��Z���\l"��K1�eln7-c�����a��e�j����m��4�V����yS`�1Y�Wnz�������C�*���N�(����������#��T�n���70m�!���0�T�����(�8�)w5a�r*�x]l�-'5�����x���)n��v���W��2M~Y��!.tjte�� �L5j���DH��c��V0�"��o8���#w���U$��%�������5�u*�����T��n��[�����:���7d��a"X���gJ���$J/�"&Eo��s	"�Y=���u����>�p6�����%��taW9�Apz��0jy�yqv�8~���9�;e%RJw�;��m15�m����=����8��w�j+=���Z�K-�5�c�)�2I��l�05����=d�iMT����0;��h�Gz#.Jk���{�[����<����6(�����h�����������q*4*�k��F����2x��
�XC��n-:�r�8�o-'�f����"�0ad�n�Z��a��R�N�����Jgi����}�o�Y�������(�C�����F�5tk�'A^�-2Z�u�v�nx-_�U����-�DE��������S����H��186�m�EX���L�u���0Q��^0��G
���[\����Z���5;
��,8�
1g��I�[3 )w���P<�(��[\��0��R�?]9�5�%������iE�[����$2�`�)$x��������e(Y���U���%���VsMb�4���Ai�l�5������[�j�wY�L��.F�(��u��G�qs�8�QO���G)���������sdN�l:�@t�Q�����7U����02��>XYzlH��XK,���p��{�+���T�l�"���yC���)�(�C]�Rt�cb�����bg��Ly��~��;��$_W}��o�����c��g�����3�c
�>�,������G�T��J�W���c��P�Ji����J;�����8��jJJ����@�)��BL��+I7-)�EE����X@t\Y�$$)7�����z�RWo�Y])o������*R�30�Y�G�P����U�Jj�a���/��^X��s���e��^b�,A������/T�ve����D�����:�ri;�f]�#���m�&����|}����nV�`�<3r)��cQ 3C�b*'��Hd��l���$��*����
Y*���s=�>M�")R�[�2`�����g\�b���Db�A$`Yj����K�I����0���|
#)z��
J���(}g�[��:��p����~��4�7�I���	61d��E.��-��
��_2�F���g+Y��"���JMS�G�����l��CB�r"~$�k�-���F��c�()�&" �OU�bA�B���e#����������l[8��Y��|�6s7������
?���������2f��9��3a�5
em�,mhj��L��P�l1a���a����C\�����c�q9������[�gb�$��n�_)�������9��~���Jd���!=��]�g;y�f�(�"k�O�I����6|�E��tf�{���������Y�P}E��7�pA���o:p""�p�'��f��W����j������D���0��q}����C+���$@s.l���5�_��~���6o^����
:S��(9���)�b'���kYQ�"������I�����O-R����~�(:4~bQ�T��~p���N�$��B@�&�TP��K��f�+D,%/A���'��r��$�7]T:P�~��g�{��F}���������q9�����zc���}�z�*�k���C14i���;7��Q<0�1�i�C<0�&F��H#�����)��e��dq�r��|m�����r�2��C@����ft�l�VE4�[Q�<����	�aS*�?��{U+2P/���?��B��GKE�#�
�u#>�`@h!}�_YP��re<V����<�p�:����n	���+i�p=u-�V�[	qk^�l�B��A�I��3�-f!��E^�p6�;�sM=�����GLel�5z/q��
]��\��(�y����@l�� �4��<��1\��p��-������@c�&FJ�Ml����D�>�����{����C���H��7��>\�%���"B�H��+0��]�[
h��e�#�(%�J�,���pp(%��1�@���i�,Z��i5��F\�Z�dnE3X�����iWb�L���i�����/!�L�z�����.�T�k����U��~�����'2����
���.�!l�Q�=a���)pX��.#��(�`�e�o/���G��x7��yj�d2*���l�P�TS��YS��J������2�5S;sh�����7��8��7���V���xw��,�lB�qx�8��g�z&9J�75~9�}FI<� �����MS�G;>�k���
��8_����W��
!A���O&f��a�R\4�vd	���Y
g\��f2j,u0��~�x������)k}�c���J���J6�[�|ZBj���X���w�	C!j��������z��!���G0y��
V�,/C/�����������wE�3R�"��qZ����@7���D�T���Uw��`@�G���Y��vt�g3�9���isuSyR�*�M"�Z�'2��+q����v����_y?��QS]k"��Z��0kQ��:Rg���7%���Ss��sR�9�^��S�Q���-���=���4�,�����E��c����
������j%�H���nN��o����C)�5��r����H7��>I�J�����-����jA�EVU��k��%�A�B���h�|[&��R�"�D�������K-����*JyZ��:`��2QL��u��j��xi�y����1�6-�e�H6��T�5���MC�����V�m��R��q��c$��i�?�;�R�Hf�r�����S}������b��X����0�DL�Z	F0����s�)��/]��������J"�8��~�8��Y���@�p��>m�g>�dD}������\!B��F]��y*#��S!ob��@96I���A��>J{��>�vJ�"`��������)�a��w]u�=�^��D��'�ia��cK�n�<]0v�#Ym�j�"Y�#�i�7Cj5��M�����S�R��JO����q(�����z����u�0��r��
�-Qb{R����$-��H�RL�X+K���t����2���z���vtQ�S��)T�e*�d*f%se�5�l�BZ���G6JZX@���)r|��I
�G������'$����"l�n���i��/�g'?��^GkS��M��u�4��Sc���^�*��Y`��O����O_��I�E��o������u\�	t��J�BK���j�\E:��[{$�w���Li��v���y"[�g���;���D	�$�B�p��E��x��J�5��.����p,�=����[Du
��Bi�������$M��1�
�k7���_��~���7��n�I��������G'?�������P�<1e���wy�����������+��O]�yg�n���lfke0��u	���$������Urg<a���w������7���xZ5���E^n������|��d��2�^�I�E&c�H�Q��Ds���y�iy�
n��;Z�7���B���s�}�G��H�V38���.��'A��C�F�
�jx�Ky��!���?��a��H9RP3�:�vhF��)
��;���+B�K�q�
�~0���f���}m�Q)X�U�p�@�vL�MN��fB�$\���r���	UB�#����z�L�����uC���;ylH�E^���b%hk�����*�>�_-��I~������\I������m���}�oI�{)���b�^���.l�VW��5}�=s9�}��Esa�a"�e{��}��C�u"�?�((z�Ip%���"���\����]��0�[O�2�q��(�P����,��-��O����3�+��8`��d�tea�Q��.^�����+L���h�]#"c�4�Gz�	���������`�K!'5����
y'?!DH��7o.F8����<��?h�8��k�������������]$��>l��Y��{��;z�IBc�iD�c��l�<�E�zt�O��8�:��?����K8������p/~<L#�q�m�q�m�
cw���o p�`2 Km����(�b�t����r.��������+���>�e���x����r� 7_G�����R���KS�GB����6��@���M�Z)�w����?���p!�r���w��J��&l�M���ep�����`�n�9���fp��`S�J�k�figk?���%���,^�S��[������)��T���l�I�n=��M��������9�.7��7��
-U��v����V���{�������v��.w��������#���+}�g%�@�z�yL<�N4��p�9�fi	�e��5j���������]�� a���������]��v��o���z�b�~��^�q�����V��Qf����E��_o����ir=��zA�����?����Q�7�,�9���+X�_`h�����Wk��[�Fm�V+^���h/�������Z�y����9��g��=?C
�%��	4-/IS��3F�"��"��c2���r4���|�`�jO����$p,}*��������<����K��L>��?��EG��mt	�����������*��m>��P����Y��y��U��I��t�9wZ�	����F���	�����g%���MHE6��Eup��;y�b��C3NJpY�[���f�X$����w�Uw�����;����c4��o��^��S���q��j�\�Fu�8��K�V���X�F�an�l������q$���L�m����$��y��I�@��������szv���d/�p�g�o:�?_�^ ���� 
��Fa+�O�)P���s
�]D����tc�������+�VTv@S^�|Om��k�j-��8w�%����Z��_����ys��KS��+���$B�����xww{��Z}�z����/�4e�Z���[���5_`TT��G�^���xr3��lZ���d���!j�/�U����m����||����6=�=�Y�z\���4����b�9h��`)<����O��C�,MA�������!�����|n������b<�������n�lo��j�[��]���Vs0��j�����f�������Qt�}�z�?M��&��4I����2���Zt0�3��h�m��km��};��sd	Y"��W���t>�YC�R�����;gC��x�OX$������\	�1�QN�x:8)��d���Y��y�	2��(b���;)�"�
H'���;�~�"�%��\�}�xag����q��(J��s��V$�*$�{������P�M'���P/�g�Z.�]��7�m�
����06c>�����n�b\�"�����S��'�RjO�E����h~�0�$a����L��h����Zbz�����f��y��a���#
7�$ �T^L�M�Gl#	B�L4+9�8�g�����<y���nB�y�����f!�B�~k�����r,w�s��D��C�f��7[;!���Y�?�������])�����[���7�W���Ac����[����2���T�Z�W����bI�C���~m�0\P���!!P�3j��W�H"�?'�������^0(�dO$���&�g���@�(��e�������Qg��b��f1
��Z�%B�8�C���z������tzhYtN�#�PF�R���_��.����D����/�R�b�W�R�v����w%��dD;����?�0)�����x��X��xY�8x�O99�<l��~?5$Y�@!dw���
�^�&����cF���%fO����W��2������=�Un'�h���H�#)��"r���v���v�`y�ux5�LYik����]��o4[t�x�<t$T?HJo��$�5�>��Q���14/��+C�c���*0�/�%��%����
]��������|fU5^���3�a�^J2�eZ�e���B�D�\��s�`���gG��W��o������x���������� �8yWZ�*�5���`�gZ���)�+[���FX�5����h����
��������m5���P�;���3TP���B�Q���`^�e�I��j:Gf�$�y���!�^��Y���n���Z�EJ��b�G�.��>cK#�r���p|�>�fU2�
L��/g�Z���_��s�����_��G�t��<8���r_i�
�*O�k��K��Qu:Y��pz>(����K�������$e�f-������	��X��6�L��
�l�;��o�xa�����B�C��I��<��Yg�/��	^�z������~�i)#2�	F�����E����o�M����	ZL�����O$T�C�6�&������o�E����q:�=g8���9���E��g��z�Y���Z��fi�{��&�5X{�w�T]�6�
Z�F��g�^�����b*\�U,�O/w��G�h���9�:�d�Y�O�)�+]���/h��K���Q/�����7y}u�6_SC��Y'�����?����p�t`���R
�\����Yl��C�t��.�^����:A����$�%f|n�S&f���1mmU�*��q�^��X�-���G�����_��p���!� �b�2�����e����#1Bh�GV����`�n��c'��}�?��XFyo}��:��OE��.g[�V[d�P�0f������F�NR���X����(v����?�����X]���+)�A�=_�P���{<�4n������?�������@��d�L���#���>��C��AM�����NI�5$����w��G�a�\��z)���#uoh��z)�8�p�dM?��BG%A��T1�^����j�4�@����K�X��1�k�M�����������n�F&�}p����#���.5>#�����s�6���@\d�k��_�@�$���BH��K��T�h�e\*�3�
|�5p�D�t�!ac;,y��Y7y����Yu����j���N�)P�B~��2��ai����y�R��Qr-�:�rB�D���`M�@A��\�ZV�N�Gi�)�����vPo����3� fK�x�nd���2��$4�0�����m������j�������1��l�-�W^���o���n��jD���f�L������,��=!&������{�)/ZGqCL��!�p�1�7_�7���Co�e���Z=c��[�$\g�AA���n�fw�y��zq�-Z����}&vNx�������,,1{j3�3a����'<��</j_:��r�*�:
�n�y^�h�W.R�Y��i9R����]�����I���Ll�%ej��T<����UF9�u[K��M��,���A8F��^��#2j��'GJ��c��4s3K`R�c�Z���-7&\�?����5�v �e�6�s��3-�<��)B�=j���k.��S��I
h�r���������6�\�3�\��Y�m���J�6����O��h�h%��?�Os�Kg�Mhib?��Q����2_��y��G�d���h���4!0�v��S��&������_v3;434�v��?�F�����g���b��jmww�^l
�j�1�n�����f��[�\":�Z/�w��2��6~��8�Q�KSO��^\z�}����C�=8����kHa���b��q�P����ZY�,>���5_������r;{O����p�eq�V�+W�/�����(H��=�����A����V�Qi��c�����pI�����|�2�����A
�91F4Y0�s���$tQ(N�+�Dq��XpGc9�s�M����Z�(���[������y!t�����;�����$�����
I���hfI���@�������j�9�hpiAyQ"0���g@x�a��6�����	3���T��p�Vhc}�M�3��F(�Q�����}%��*�������*>��<i�p"���$>�*I�,7^<�Vb��]y�k&��F�9� �>ZH�6(E�0;�p����!���

�(� ��R�=m7���.lO�[�JcK�U��3fYY���v��� E�!G�K\��t
L�}|f��`�P��N�,[����-�Id{�"X�\��n�vD�?���1q1������m n ����m�����R�M���7�^�O������oUst)������l�CB\�)h
�R���Cf�Z4\ �0�5#K�'$__��c$o9���
:6
�i�-8=��������A�E�h�3$y���&4s���F��xrrK��egu3�:�`V�F�d^MI+O�dTZ�;���4a\�#���������n������L�&+7q��=3m����^�<V���ET�������"|[��@������S��lh�BK�����(l������w���m��V�1:��]4��uV���YI�������CB4� ���2V����G���vw���Qk�D����js��R�+���Cn,o@g�Q��a`�����-oaG�(^1�jN���l\�<���F��o
�|{���yFu^�q��jNG�h9c��l��n��(���n������6}�w�5�M������t��/�v���;������?ES4Q�
k�B[��?��`����,���	\����p8��Op*h�
&�/q=�O���	��Vo����#�;�w��f|	������g���V��a����HK1~��d$s~��e�y��[���6��($�� �$Vy�}G�.$���{���V����@��
g
O����6�&�`��y�)��Vr��A
D4��[�{G;9(����Mx���@���w���U�jX�C��p
�H�R�$1[Ry�%
��3��L�@�h�2e�^���u�h��GZ
�U)5�BJ[A�pZ�Y�����W�P.2�G�J�,�Y�QA����|�L�Q;�j�M����������d?�HAP+������;,kF����f�y����	�g�l�f��Y\�8Y?X��[���Qj�C������9;G�@�LnI�.�^j3).��]U����r�*��;-�B
Z�M������c��99>���J����$��[u�<N�h�"�3��8��x.��}�:5S��t����lh�6���OXH����c��]�6��L:J
��t��Vc���T�Q�E�A"V
q3��B�ad��O:��t�rZ���,������{���q8�o�eo���n�����\8�:��;3\$,���~���~o)��V��:����=���k����D�U�]��A�I9������~�w�u�p�E�m�a�LP(_@S%Z�A����������h"�6n��/��H	wy�^�vC��l�����Mcn�`�*�#�&�wu-u��j������;KM���s�z��6i�f�3�
/�iB�-��Zjry������HM��l�D�2�t�i�G��g����PtvM�^�6�`z3��zq�[�C�lm|b��~��Y�I��j:�0*w�gZAm�`Q�����e����'}I���Na������P��[R���z�D�?��e,"�[�N���1������l��[�kegb�z�d��\�A>�������-��z��z�(E�^>>����r�}@�m
"G����g:+�C]�p���bM�c�Y��V����4*VR&"m3<����n:��V�:��b�7"R�;������y����]�f�\	D�������e��v���8c���(���&�i�ga2F��=����^�i-CK�����CJ�&���{3Z�5���W�����S��|k-=)���	T#�G�#�[�{D��1�iW��o}��r@�e���X1�2���<�Di0L�Fv�62F��hT����_��:zB�f���B�&����N���O�v�������x1FK������>���#�O�+M�#kC��V�&�K�p�����N#JYs��1���^��f�vZBEy	c"�|U�����&���7����p�8���B�aDD�}Y"�Z����J���|��4���	v�vo�h��wv�������N����{�����t""\r����/���F�{���@<^�������cZ�}�'B�E�L{z`��h�i+��[s�jkR��������?]��%�P�M�6�=S����k����k�D�o��������?��,����%��v~ 3�'�{2�?w	�sz���������[������x���]>��y5h�z��V�����Z������zkw������f�^����C��F0�z����{��Y��������,S���	9!�K�T,�$qrpG!�'�>\1ba�w3�"���d)R)b
��r3 "�-5�a��`������}�T���v�	u��������n:n�?�1Rr���*��3bf��Ei����G]A$O�;z7�S�z��:��!<�&����M�3������j�G�!�5u*A��o>����>a����a��J��wg�k��v�"�	��<N��f|��c[J�D�`��8t���1{�F��:"E)���M���?�X|�"S� �z�>��FxFU����u��r�T��d8��s&GI����:��Z)����l��dTv-����BL�T�%x��Q��OL�L�H��E)��tbc�c��b9[����H]OIR���TI\i�]R�*�Wt<{C�I)	��pF�}�)NB�<z�DD����b��=>���pd[���������G���p[A��b��f>��O�.�����9�����+7y�����,�PL�$
X��4s�b��yw��4j
�L�@��8���a�c�������U��RI���cN�,p8`��a�>y`�)�ia\m�8�,�vID�&#�.�����{nfH�������n�m��n����Z[�����6���c
Bp�b�	^6-
-�N^93k���n�B�����,�b������.�V������_�
���f�=�c�����Jg3��~ ���E�h8�����Tz��=��?����������8l�
��������/��C�[o:kA��Y�[9���t���ipo�����m�} ��u��f>K��F�+v���\&��@sqa��^G�4������G�}d���j#M��p��Wht���������)���Q�>e�X��][��N�v|����NL�$~�b�&b��t+��H^J�nn��<�?�����v� ^,O���d33�y�[Dx��^������A�`p�N�<�.?�B�YN���U��
!b�����RN���/@%���c�5��__�Ks���l���Kkk����Z��]Ki�0F]<|;4Z��������E��I	z�}� y^�f@G�5c��h�P.�YTaK���] ��dS�a����Y`�~�������3��(�����+*�3��
iS�f�c@��B�W����C?���4"�T
i7_�����y�0?a1/Q����'j��F�z����l�����������p�)O�BkC�>|AK��B�&��������[�i��h�#����Z
�S����\��=��<�\����
b���Yv�c^jB�����������Ek"c4C��-����a��{SiI�S�c�/:go/��;9k���&-t�	�,�/�\H��t���/=
����`y��a�����b��*���M%i�s60/=|4p]�^7j,}i��
�t���[�?T�#���.���z�T�1uA�r��4��V���������Do6��vK��*F��������{rEZ��+j��GM���/��;��lCz6���6�"�YZ�:���>��k�#�IR(PZb��������dv�p��#���t�+ )�c��g����mV"na�4iQu���!�y$�U�/�$l�����v����E��\a��u�$1��Bu_1���dKW%e&t�PJ5F�0/a�8��q����;�x|��J.����#&�-��p�0!��.���k9�
���`���p g[t��]"�b��th��G�8��e���Zs~��-�d�
�������!K�G��t���j<���[���#a\n��=�p�8&�������������A����Y#8�>����Kvvr&���6���.����L3,��r�1=�o����e	Ie���Km���,�4�+�0p�����bmcd���M[ ���������+�S�������g�C����n�KO8t1�����F:�s5:H��.\u�A��=�pl�%Z
FG&�.�%�q��'1Gx���C�Y��MLv.�����&�UD5:�5R�at��RI��J�f��p�u�W9+-+�Dj���W�~�����%�f�|�ES�����wEH�&/������J��V=)M���iBSC��X��|�5X� �:Z�qCn;���>�N�]�Y�w,�,�kcG�=�q5,4&?��-y��C{57"���B����)�(2:'�����E|-Q���H���2o�>?��3da&�"�r���e�2���w�
�]yG��g�&,��I�VYRm�of6lt��E��H���'���C��:F�u���7-s�S�L�p1�@����9:2�.�h�%�p��tB���
&.�k����Rz�lD��t����*(�#�#?,s��)w�Eb����:�,����}|3Zg�O"�l�N|S�~5��u�aU&���`�;�9����xUZ����_�^[H%{�����oDNw��F�;`5�����]�������k�s�f���������X6X}�d��z��A9�
�9���L�p��V��!=�X���%;(6�EK+��F&i�f�@nK�z��i�������f�����W�+������B���0_����9���_����
�htu3�N[L�{��A��Y�
��x��Y����x���cZ]��}wt����6M�Y&736������[^�p�m�!��Qw�f1g�P�NM�l)�cr����5��(�||�Wb����U�V��$Ox����DQ�P��t�7CD������u����4���qx ���RZ��:�D������l��;7h9$[c!/�sE�T�&O�tF�_}�UW��z�x��w{6����Y#3�zs[b����R�Q��OG8�����Ib(���`Z-���8��SW|��p(��S���rS��~G���k���f:
n:�1�st�e�0�m���Q���\u���|��}�-������ ��z�Q�h����uT�^l�IM�����CD�P��2��,J��T���!+"�;	�1=+>�-�8>|{���[������}xy|�9{s|~��@�2����������oO��	��EiW	�����s�)+(vE(X	f0����9�,cO��D���|� ��u��	g�)~�%�`�R�^z*'�0r�r9�}&� F�?�)���7 �%O1J)���&K��k;�X"���a��������2��t���Kbu�Pl��5EVN�^+'A4��z�x
�*Y�y2�IU������3MF���� ���3��<3R��iq�3,o'r�^Ky��������`�`V3�����!W�T�e��a	n��O���TP�������#{���B"���+����HKL�0�Jf��0�34M�@C"��!�b{�u��+���������V��oj���/e������7F�!����D�"�(����o�Na�	,���	#t���%y*q�_�6i�����R��dK9�xmM������,_������|����	=D������;��k��7�i�[�����\���������J;��tK�0�BT�;\���|����Eu�����X�Q�o*T��1*tuMK�wqiQ�8N�z����o8����a�-���s�%ia���g���69p���*�������Sd���
k-Z%;�u����	$k��A�����������h�	);�Z$#������%7c�2!���e��~��u������������f����iX��-��3��,��;(��1,��i�F���S�#|�nC�T|�������<'�}�Zy��WR�d��u2�V"�eC'������@�����Uw>�s��`�Q�`����\OoF}2����:&X��3f9g�O�Oi����M���7��
����������+�g�6�QayRFRZ~��s"gsa*uK�����-�/#d3 �F��2�fs��Uke����3=���X���t<�����n����L�t��4
�<�)^�����l�QI��B?1599��n(L=k84���:xh.Q*��������,����er6��r[�Z_F�=�����{���%���5%'����+���^C�Q)y�'2��S�}'����H��������Oa��]���"����#��S
M����Q�:x���k��#L!!��haf�046$2��([X�fh8�+	�aF"6��r�Bi4m������&!~
�CTW�
�az�8h����$0d4��
���l��5P�u���J�X\��q<�L�8�Z�9���em
14�&�8�0��'�ZB3�#�YQ<��-��d�����eA5�2�1�Fw.�7XZ�p�0,w��|J�7&��Djb0��Q�$��P�Y������Q�oZ�f��/nd����/Q*���=:#�e�F9�>�N9���K���1���%���d#������Q��-��`��0�h�	�a��u��#a� e���<~�C�E(�!\�|?4\[8�i#��
������0I����v��E��%�
�ge���,h����L�4%��Z�P-������V7�id(�P�(��
c����Df�f�Q���'v�tV��Q2%��G���	z4��L���G +<�T��`]2�~�U���R�C
G�F������M��� p�����O�!�6�������aOmO
� ������������?�	���Uj<�F3�����+'�V����A[e�$B3xZ	+�h���o����������h�z,TR�P(�T�6�2l�B�i���7vY��65s���1(�^�`�9�� �����x8�FB������B���j�,^��s�w��'��AS���S;�+88;�w��D�K�����w�qH��>��+��(%72�H9/a9�/�#�J����Qh����������;�5���I
���x�������j�d�l��K��V�&�f%������\��W@��J�y�@L�7f,l�g��g�V������e��W�:A�b�E|�\����O�K���i��V"
�o�#��7�T����#�����+���}��o��C���]�3s�!��)���C���d��p`���##�a�����A��]s(�P�I{��E�P�kT��j��+
>��t�p�0]��S��������cw8~.V��F���4S��I����c�5Q8�&@��se������h}��:�C���'�CL:|�b�(�I��3,�Z�K�	Cq��j�c��,���C^g�8�1��9��d�4�X�w%��jW����f�%��f�/����rA�3�dG��X=���7�(5v�WCX��!�}�,'\7��-����O7�Ov�Z����:Z4{VO/�m�����OnTB�D$����l���'��i�����/�)T���hYt�f�X�R��B�K��(�E58#�j4{������v���CZ6�V��{����a���w��
�q�&}Y����c�O%��I�O���g�v�������8���k���7k��������qo�nB,���t��a8�K��D�X4��4��9��Uc�M�W�l�%���x�#�&����n|����5��([�J���}��Fl��L6Ed8P
EZ�n��C,A��n�����]�����<�=���tR	@�p*�����a�Mb�����J�23<�G�<�
�a	}���4����m����l�n��&,���Iq4r���`7��.����8�����?9l��c�o���aH)�q�JJ�'��^;z]h�J�
!^����_�����8$t���!�3[�uW��q��~5nWsx����  �xHDj�X���^�\7�L��e�OP���(�T8����&���,]�\R������^�7i�������lJ����:J4�e�`����6B0�Q���4�l2f-�9H�Z���H����y���%z��mL�0�����g&+[Te�$GT���H�vZ~�����s�����z��g�S��soF������M�&	#+md�D#���24a��.<n�;Tn���rr�rSK��X��_���=��f'U���$�����!��u{����iJ�	3O��"iM����8��h������3I����z�{;�Ry��%����8;�3�dJ��B<P}Le�)����Q�|}�������.!0����L�[�����g	����M�
��}�D" Wkd�PS4����(hE�FjbXZ�_����i[������f_�@��:��.$�����p����s*NP��U^g��ZN�TT�,�[Set2m%�����y$>6�<�G�����Gq�=
?�#a�m0n(t������&'}NON�:�D��|����t�RZ�gDN+��GX�R����1��kc�\��5���&J�a�U�D��L�.fh���1��?
C_��Yf��$���Z0lh>�zC%��"�`@�<[�E�h�����:@U:)��r�L�������,O{�/�(9�XV��/s��u�9#wq����b��L�21@!��E"b7]�����Ka=���i��$��r�Y�36��G4�/�j���!'�4��#+	]|�D�!	����Ij�gFYct$�����X�����}�qM�&ps1�8^ R����MI�T�����>\��R�<%s����t���ZI������g��RO��3��BU��dy���c
���=dy>YM����!H�.S�E<���
NC<?0��S�a�D����`�5�5�v�"�Z���.b�l2�������=�	��XsV�p�'@W�o!@.�
�����P��B�����W�b��}��U��9�1�y��_�|i������������	�a�^��[�����3��|��a�`���9�*���-F�!'q�`o��)@>Q$��OL������R*�p��g��.���E�A�(Zs"�����'�p��2x�����5���~�d/���KY��5se��^�]�����s���m��]��y�
b�e�h�&�m��rqv�]4�cOx�e��_���su3�;�?u��8����Ka�i<��NS�(��+�.�Ov���������}���G��A�������M���	����H��^]��w�98y�����3&4n�A�D��zt���NG�;ZgQ���2��K�el��[jxZ��A���4R��f��-:�h�ZFP��g�����rAE�mb���V��+�WHkt*����dj��z"�'/��$6�^$��]���ti�ISBr}�����~<3f���i��(���	��{�1�M�TF�����3���_����n���5|[
��T]��	���1���j1�k/�.r�y�=�Q�������������s�����_�\�V)J,���_�������y�Z\�u�9�G�:5���(2Y���y�RI�k���3�x��������%z��z�o����-u�ZU)���-����&TO���@�n�n=���pZ��k��h@
�'F
��c����+f��C)h�Z��������C��8'��G�%``SYy���R�N�f��b+3yX�m���}Qv(�������>tK�W������$"��G%��Z���"	� ���dx$v����bM��o,HY6N�����j4���Y������|����N-9��d%
�#���O�f�sK\V
i7]����a��r����!�b�nJ�S��<y&�E�7�w
��j!��mPj��v+�����~(29]���=s1�F��������a�-������\�3zf]Y����I7�X���z)�J� ����1����o����Hm�D��h�T�6�y3]�
]�:�~<Y��y�,0��~���:�L+�S�F�J��8h�\��e?�h�U��<��G�X����;3b���Wk��z�"�d�EPnn�z�)��"Oq��hm��p��{������2uA��"��1
�A�~��!;�E����{g��`[���<x,��RN<6��}�e�e�����U��TP�D�,���!L@��	M�6�p1O\�����Q��QHO9r�|����K�<�Z��-Nv��?7�f�����TS��M���������![���bt4m���
��5�D����i|����ZH`�i{U��6(��]7q�����&q$U6��;�'N��0M��}()�3�C�2er�r��\��2p�:A��w���f[��Y����� ;,2�E]����6)��%4�g�"�^eu;�t���������	�����s��A���
�U�N"��U���~Z��(r�N%���-EN�����L�QQ���Ii���X.K���n_\^�cL��$/_Z+����p&���%S)�7�C��G�6�j���,�y��S`*�!"�[v�hl<��B�4�\��6P;���%��/���J��0�S�>&6_N��^ol������%����:�i"Y����To2��=f9�_���y����g"�w������j��������[u�0C��w���u��R�	�QIkT6_��LLX3�*��/47�����P��K*=,�5=^�`T�C�X�U�s]O�	2�����Y�&/b��C�Rgf�����:3��hr�YF��g���p����b]�W��a���M���uE�������D�M1�5K�G�������g�N�����Q��������7��Etzv�f�yg���������O�y��=�"��/�����9�kdf�,67o�}zq|~�O/�����ay����S�.�H�~
4�
k%y{�Q��`IP���_�EtY�;�q[�N~�C/����n��L	eo<Q�:�Z��OyC��YwuM� �.u�SJR��f����4���)�	�8$���R�y�j�������,G��d.����w��
�u�+����j�
�����S���;�M�h�����G�v�W�1�z�l����eJC���Y����p�,���X��+f��L��E��;;CyB�_�O(�O��}���,���y����.'\������fFb���A;qX����2���1�����v;�������[����.���*"�B�|�6��O����|��<�T�mn*9��j7����#`
/�����.�����S�N��^
4CG�E�X?�+����J�b��9C���s��~�8&d������j�X�?O��������y�<}�>O��������y�<}�>O��������y�<}�>O��������y�<}�>O��������y�<}�>���R��
#129Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo NAGATA (#128)
Re: Implementing Incremental View Maintenance

From: Yugo NAGATA <nagata@sraoss.co.jp>
Subject: Re: Implementing Incremental View Maintenance
Date: Fri, 21 Aug 2020 17:23:20 +0900
Message-ID: <20200821172320.a2506577d5244b6066f69331@sraoss.co.jp>

On Wed, 19 Aug 2020 10:02:42 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

I have looked into this.

Thank you for your reviewing!

- 0004-Allow-to-prolong-life-span-of-transition-tables-unti.patch:
This one needs a comment to describe what the function does etc.

+void
+SetTransitionTablePreserved(Oid relid, CmdType cmdType)
+{

I added a comment for this function and related places.

+/*
+ * SetTransitionTablePreserved
+ *
+ * Prolong lifespan of transition tables corresponding specified relid and
+ * command type to the end of the outmost query instead of each nested query.
+ * This enables to use nested AFTER trigger's transition tables from outer
+ * query's triggers.  Currently, only immediate incremental view maintenance
+ * uses this.
+ */
+void
+SetTransitionTablePreserved(Oid relid, CmdType cmdType)

Also, I removed releted unnecessary code which was left accidentally.

- 0007-Add-aggregates-support-in-IVM.patch
"Check if the given aggregate function is supporting" shouldn't be
"Check if the given aggregate function is supporting IVM"?

Yes, you are right. I fixed this, too.

+ * check_aggregate_supports_ivm
+ *
+ * Check if the given aggregate function is supporting

Thanks for the fixes. I have changed the commit fest status to "Ready
for Committer".

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#130Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Tatsuo Ishii (#129)
Re: Implementing Incremental View Maintenance

Hi,

I updated the wiki page.
https://wiki.postgresql.org/wiki/Incremental_View_Maintenance

On Fri, 21 Aug 2020 21:40:50 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

From: Yugo NAGATA <nagata@sraoss.co.jp>
Subject: Re: Implementing Incremental View Maintenance
Date: Fri, 21 Aug 2020 17:23:20 +0900
Message-ID: <20200821172320.a2506577d5244b6066f69331@sraoss.co.jp>

On Wed, 19 Aug 2020 10:02:42 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

I have looked into this.

Thank you for your reviewing!

- 0004-Allow-to-prolong-life-span-of-transition-tables-unti.patch:
This one needs a comment to describe what the function does etc.

+void
+SetTransitionTablePreserved(Oid relid, CmdType cmdType)
+{

I added a comment for this function and related places.

+/*
+ * SetTransitionTablePreserved
+ *
+ * Prolong lifespan of transition tables corresponding specified relid and
+ * command type to the end of the outmost query instead of each nested query.
+ * This enables to use nested AFTER trigger's transition tables from outer
+ * query's triggers.  Currently, only immediate incremental view maintenance
+ * uses this.
+ */
+void
+SetTransitionTablePreserved(Oid relid, CmdType cmdType)

Also, I removed releted unnecessary code which was left accidentally.

- 0007-Add-aggregates-support-in-IVM.patch
"Check if the given aggregate function is supporting" shouldn't be
"Check if the given aggregate function is supporting IVM"?

Yes, you are right. I fixed this, too.

+ * check_aggregate_supports_ivm
+ *
+ * Check if the given aggregate function is supporting

Thanks for the fixes. I have changed the commit fest status to "Ready
for Committer".

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

--
Yugo NAGATA <nagata@sraoss.co.jp>

#131Thomas Munro
thomas.munro@gmail.com
In reply to: Yugo NAGATA (#130)
Re: Implementing Incremental View Maintenance

Hi Nagata-san,

On Mon, Aug 31, 2020 at 5:32 PM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

https://wiki.postgresql.org/wiki/Incremental_View_Maintenance

Thanks for writing this!

+   /*
+    * Wait for concurrent transactions which update this materialized view at
+    * READ COMMITED. This is needed to see changes committed in other
+    * transactions. No wait and raise an error at REPEATABLE READ or
+    * SERIALIZABLE to prevent update anomalies of matviews.
+    * XXX: dead-lock is possible here.
+    */
+   if (!IsolationUsesXactSnapshot())
+       LockRelationOid(matviewOid, ExclusiveLock);
+   else if (!ConditionalLockRelationOid(matviewOid, ExclusiveLock))

Could you please say a bit more about your plans for concurrency control?

Simple hand-crafted "rollup" triggers typically conflict only when
modifying the same output rows due to update/insert conflicts, or
perhaps some explicit row level locking if they're doing something
complex (unfortunately, they also very often have concurrency
bugs...). In some initial reading about MV maintenance I did today in
the hope of understanding some more context for this very impressive
but rather intimidating patch set, I gained the impression that
aggregate-row locking granularity is assumed as a baseline for eager
incremental aggregate maintenance. I understand that our
MVCC/snapshot scheme introduces extra problems, but I'm wondering if
these problems can be solved using the usual update semantics (the
EvalPlanQual mechanism), and perhaps also some UPSERT logic. Why is
it not sufficient to have locked all the base table rows that you have
modified, captured the before-and-after values generated by those
updates, and also locked all the IMV aggregate rows you will read, and
in the process acquired a view of the latest committed state of the
IMV aggregate rows you will modify (possibly having waited first)? In
other words, what other data do you look at, while computing the
incremental update, that might suffer from anomalies because of
snapshots and concurrency? For one thing, I am aware that unique
indexes for groups would probably be necessary; perhaps some subtle
problems of the sort usually solved with predicate locks lurk there?

(Newer papers describe locking schemes that avoid even aggregate-row
level conflicts, by taking advantage of the associativity and
commutativity of aggregates like SUM and COUNT. You can allow N
writers to update the aggregate concurrently, and if any transaction
has to roll back it subtracts what it added, not necessarily restoring
the original value, so that nobody conflicts with anyone else, or
something like that... Contemplating an MVCC, no-rollbacks version of
that sort of thing leads to ideas like, I dunno, update chains
containing differential update trees to be compacted later... egad!)

#132Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Thomas Munro (#131)
Re: Implementing Incremental View Maintenance

Hi Thomas,

Thank you for your comment!

On Sat, 5 Sep 2020 17:56:18 +1200
Thomas Munro <thomas.munro@gmail.com> wrote:

+   /*
+    * Wait for concurrent transactions which update this materialized view at
+    * READ COMMITED. This is needed to see changes committed in other
+    * transactions. No wait and raise an error at REPEATABLE READ or
+    * SERIALIZABLE to prevent update anomalies of matviews.
+    * XXX: dead-lock is possible here.
+    */
+   if (!IsolationUsesXactSnapshot())
+       LockRelationOid(matviewOid, ExclusiveLock);
+   else if (!ConditionalLockRelationOid(matviewOid, ExclusiveLock))

Could you please say a bit more about your plans for concurrency control?

Simple hand-crafted "rollup" triggers typically conflict only when
modifying the same output rows due to update/insert conflicts, or
perhaps some explicit row level locking if they're doing something
complex (unfortunately, they also very often have concurrency
bugs...). In some initial reading about MV maintenance I did today in
the hope of understanding some more context for this very impressive
but rather intimidating patch set, I gained the impression that
aggregate-row locking granularity is assumed as a baseline for eager
incremental aggregate maintenance. I understand that our
MVCC/snapshot scheme introduces extra problems, but I'm wondering if
these problems can be solved using the usual update semantics (the
EvalPlanQual mechanism), and perhaps also some UPSERT logic. Why is
it not sufficient to have locked all the base table rows that you have
modified, captured the before-and-after values generated by those
updates, and also locked all the IMV aggregate rows you will read, and
in the process acquired a view of the latest committed state of the
IMV aggregate rows you will modify (possibly having waited first)? In
other words, what other data do you look at, while computing the
incremental update, that might suffer from anomalies because of
snapshots and concurrency? For one thing, I am aware that unique
indexes for groups would probably be necessary; perhaps some subtle
problems of the sort usually solved with predicate locks lurk there?

I decided to lock a matview considering views joining tables.
For example, let V = R*S is an incrementally maintainable materialized
view which joins tables R and S. Suppose there are two concurrent
transactions T1 which changes table R to R' and T2 which changes S to S'.
Without any lock, in READ COMMITTED mode, V would be updated to
represent V=R'*S in T1, and V=R*S' in T2, so it would cause inconsistency.
By locking the view V, transactions T1, T2 are processed serially and this
inconsistency can be avoided.

I also thought it might be resolved using tuple locks and EvalPlanQual
instead of table level lock, but there is still a unavoidable case. For
example, suppose that tuple dR is inserted into R in T1, and dS is inserted
into S in T2. Also, suppose that dR and dS will be joined in according to
the view definition. In this situation, without any lock, the change of V is
computed as dV=dR*S in T1, dV=R*dS in T2, respectively, and dR*dS would not
be included in the results. This causes inconsistency. I don't think this
could be resolved even if we use tuple locks.

As to aggregate view without join , however, we might be able to use a lock
of more low granularity as you said, because if rows belonging a group in a
table is changes, we just update (or delete) corresponding rows in the view.
Even if there are concurrent transactions updating the same table, we would
be able to make one of them wait using tuple lock. If concurrent transactions
are trying to insert a tuple into the same table, we might need to use unique
index and UPSERT to avoid to insert multiple rows with same group key into
the view.

Therefore, usual update semantics (tuple locks and EvalPlanQual) and UPSERT
can be used for optimization for some classes of view, but we don't have any
other better idea than using table lock for views joining tables. We would
appreciate it if you could suggest better solution.

(Newer papers describe locking schemes that avoid even aggregate-row
level conflicts, by taking advantage of the associativity and
commutativity of aggregates like SUM and COUNT. You can allow N
writers to update the aggregate concurrently, and if any transaction
has to roll back it subtracts what it added, not necessarily restoring
the original value, so that nobody conflicts with anyone else, or
something like that... Contemplating an MVCC, no-rollbacks version of
that sort of thing leads to ideas like, I dunno, update chains
containing differential update trees to be compacted later... egad!)

I am interested in papers you mentioned! Are they literatures in context of
incremental view maintenance?

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#133Thomas Munro
thomas.munro@gmail.com
In reply to: Yugo NAGATA (#132)
Re: Implementing Incremental View Maintenance

On Wed, Sep 9, 2020 at 12:29 PM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

I also thought it might be resolved using tuple locks and EvalPlanQual
instead of table level lock, but there is still a unavoidable case. For
example, suppose that tuple dR is inserted into R in T1, and dS is inserted
into S in T2. Also, suppose that dR and dS will be joined in according to
the view definition. In this situation, without any lock, the change of V is
computed as dV=dR*S in T1, dV=R*dS in T2, respectively, and dR*dS would not
be included in the results. This causes inconsistency. I don't think this
could be resolved even if we use tuple locks.

I see. Thanks for the explanation!

As to aggregate view without join , however, we might be able to use a lock
of more low granularity as you said, because if rows belonging a group in a
table is changes, we just update (or delete) corresponding rows in the view.
Even if there are concurrent transactions updating the same table, we would
be able to make one of them wait using tuple lock. If concurrent transactions
are trying to insert a tuple into the same table, we might need to use unique
index and UPSERT to avoid to insert multiple rows with same group key into
the view.

Therefore, usual update semantics (tuple locks and EvalPlanQual) and UPSERT
can be used for optimization for some classes of view, but we don't have any
other better idea than using table lock for views joining tables. We would
appreciate it if you could suggest better solution.

I have nothing, I'm just reading starter papers and trying to learn a
bit more about the concepts at this stage. I was thinking of
reviewing some of the more mechanical parts of the patch set, though,
like perhaps the transition table lifetime management, since I have
worked on that area before.

(Newer papers describe locking schemes that avoid even aggregate-row
level conflicts, by taking advantage of the associativity and
commutativity of aggregates like SUM and COUNT. You can allow N
writers to update the aggregate concurrently, and if any transaction
has to roll back it subtracts what it added, not necessarily restoring
the original value, so that nobody conflicts with anyone else, or
something like that... Contemplating an MVCC, no-rollbacks version of
that sort of thing leads to ideas like, I dunno, update chains
containing differential update trees to be compacted later... egad!)

I am interested in papers you mentioned! Are they literatures in context of
incremental view maintenance?

Yeah. I was skim-reading some parts of [1]https://dsf.berkeley.edu/cs286/papers/mv-fntdb2012.pdf including section 2.5.1
"Concurrency Control", which opens with some comments about
aggregates, locking and pointers to "V-locking" [2]http://pages.cs.wisc.edu/~gangluo/latch.pdf for high
concurrency aggregates. There is also a pointer to G. Graefe and M.
J. Zwilling, "Transaction support for indexed views," which I haven't
located; apparently indexed views are Graefe's name for MVs, and
apparently this paper has a section on MVCC systems which sounds
interesting for us.

[1]: https://dsf.berkeley.edu/cs286/papers/mv-fntdb2012.pdf
[2]: http://pages.cs.wisc.edu/~gangluo/latch.pdf

#134Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Thomas Munro (#133)
Re: Implementing Incremental View Maintenance

On Wed, 9 Sep 2020 14:22:28 +1200
Thomas Munro <thomas.munro@gmail.com> wrote:

Therefore, usual update semantics (tuple locks and EvalPlanQual) and UPSERT
can be used for optimization for some classes of view, but we don't have any
other better idea than using table lock for views joining tables. We would
appreciate it if you could suggest better solution.

I have nothing, I'm just reading starter papers and trying to learn a
bit more about the concepts at this stage. I was thinking of
reviewing some of the more mechanical parts of the patch set, though,
like perhaps the transition table lifetime management, since I have
worked on that area before.

Thank you for your interrest. It would be greatly appreciated if you
could review the patch.

(Newer papers describe locking schemes that avoid even aggregate-row
level conflicts, by taking advantage of the associativity and
commutativity of aggregates like SUM and COUNT. You can allow N
writers to update the aggregate concurrently, and if any transaction
has to roll back it subtracts what it added, not necessarily restoring
the original value, so that nobody conflicts with anyone else, or
something like that... Contemplating an MVCC, no-rollbacks version of
that sort of thing leads to ideas like, I dunno, update chains
containing differential update trees to be compacted later... egad!)

I am interested in papers you mentioned! Are they literatures in context of
incremental view maintenance?

Yeah. I was skim-reading some parts of [1] including section 2.5.1
"Concurrency Control", which opens with some comments about
aggregates, locking and pointers to "V-locking" [2] for high
concurrency aggregates. There is also a pointer to G. Graefe and M.
J. Zwilling, "Transaction support for indexed views," which I haven't
located; apparently indexed views are Graefe's name for MVs, and
apparently this paper has a section on MVCC systems which sounds
interesting for us.

[1] https://dsf.berkeley.edu/cs286/papers/mv-fntdb2012.pdf
[2] http://pages.cs.wisc.edu/~gangluo/latch.pdf

Thanks for your information! I will also check references
regarding with IVM and concurrency control.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#135Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Thomas Munro (#133)
Re: Implementing Incremental View Maintenance

I have nothing, I'm just reading starter papers and trying to learn a
bit more about the concepts at this stage. I was thinking of
reviewing some of the more mechanical parts of the patch set, though,
like perhaps the transition table lifetime management, since I have
worked on that area before.

Do you have comments on this part?

I am asking because these patch sets are now getting closer to
committable state in my opinion, and if there's someting wrong, it
should be fixed soon so that these patches are getting into the master
branch.

I think this feature has been long awaited by users and merging the
patches should be a benefit for them.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#136Michael Paquier
michael@paquier.xyz
In reply to: Tatsuo Ishii (#135)
Re: Implementing Incremental View Maintenance

On Thu, Sep 17, 2020 at 09:42:45AM +0900, Tatsuo Ishii wrote:

I am asking because these patch sets are now getting closer to
committable state in my opinion, and if there's someting wrong, it
should be fixed soon so that these patches are getting into the master
branch.

I think this feature has been long awaited by users and merging the
patches should be a benefit for them.

I don't have much thoughts to offer about that, but this patch is
failing to apply, so a rebase is at least necessary.
--
Michael

#137Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Michael Paquier (#136)
Re: Implementing Incremental View Maintenance

On Thu, Sep 17, 2020 at 09:42:45AM +0900, Tatsuo Ishii wrote:

I am asking because these patch sets are now getting closer to
committable state in my opinion, and if there's someting wrong, it
should be fixed soon so that these patches are getting into the master
branch.

I think this feature has been long awaited by users and merging the
patches should be a benefit for them.

I don't have much thoughts to offer about that, but this patch is
failing to apply, so a rebase is at least necessary.

Yes. I think he is going to post a new patch (possibly with
enhancements) soon.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#138Fujii Masao
masao.fujii@oss.nttdata.com
In reply to: Tatsuo Ishii (#137)
Re: Implementing Incremental View Maintenance

On 2020/10/01 13:03, Tatsuo Ishii wrote:

On Thu, Sep 17, 2020 at 09:42:45AM +0900, Tatsuo Ishii wrote:

I am asking because these patch sets are now getting closer to
committable state in my opinion, and if there's someting wrong, it
should be fixed soon so that these patches are getting into the master
branch.

I think this feature has been long awaited by users and merging the
patches should be a benefit for them.

I don't have much thoughts to offer about that, but this patch is
failing to apply, so a rebase is at least necessary.

Yes. I think he is going to post a new patch (possibly with
enhancements) soon.

When I glanced the doc patch (i.e., 0012), I found some typos.

+ <command>CRATE INCREMENTAL MATERIALIZED VIEW</command>, for example:

Typo: CRATE should be CREATE ?

+ with <literal>__ivm_</literal> and they contains information required

Typo: contains should be contain ?

+ For exmaple, here are two materialized views based on the same view

Typo: exmaple should be example ?

+ maintenance can be lager than <command>REFRESH MATERIALIZED VIEW</command>

Typo: lager should be larger ?

+postgres=# SELECt * FROM m; -- automatically updated

Typo: SELECt should be SELECT ?

Regards,

--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION

#139Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Fujii Masao (#138)
Re: Implementing Incremental View Maintenance

On Thu, 1 Oct 2020 13:43:49 +0900
Fujii Masao <masao.fujii@oss.nttdata.com> wrote:

When I glanced the doc patch (i.e., 0012), I found some typos.

Thank you for your pointing out typos! I'll fix it.

+ <command>CRATE INCREMENTAL MATERIALIZED VIEW</command>, for example:

Typo: CRATE should be CREATE ?

+ with <literal>__ivm_</literal> and they contains information required

Typo: contains should be contain ?

+ For exmaple, here are two materialized views based on the same view

Typo: exmaple should be example ?

+ maintenance can be lager than <command>REFRESH MATERIALIZED VIEW</command>

Typo: lager should be larger ?

+postgres=# SELECt * FROM m; -- automatically updated

Typo: SELECt should be SELECT ?

Regards,

--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION

--
Yugo NAGATA <nagata@sraoss.co.jp>

#140Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#139)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

Attached is the rebased patch (v18) to add support for Incremental
Materialized View Maintenance (IVM). It is able to be applied to
current latest master branch.

Also, this now supports simple CTEs (WITH clauses) which do not contain
aggregates or DISTINCT like simple sub-queries. This feature is provided
as an additional patch segment "0010-Add-CTE-support-in-IVM.patch".

==== Example ====

cte=# TABLE r;
i | v
---+----
1 | 10
2 | 20
(2 rows)

cte=# TABLE s;
i | v
---+-----
2 | 200
3 | 300
(2 rows)

cte=# \d+ mv
Materialized view "public.mv"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
r | integer | | | | plain | |
x | integer | | | | plain | |
View definition:
WITH x AS (
SELECT s.i,
s.v
FROM s
)
SELECT r.v AS r,
x.v AS x
FROM r,
x
WHERE r.i = x.i;
Access method: heap
Incremental view maintenance: yes

cte=# SELECT * FROM mv;
r | x
----+-----
20 | 200
(1 row)

cte=# INSERT INTO r VALUES (3,30);
INSERT 0 1
cte=# INSERT INTO s VALUES (1,100);
INSERT 0 1
cte=# SELECT * FROM mv;
r | x
----+-----
20 | 200
30 | 300
10 | 100
(3 rows)

======================

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v18.tar.gzapplication/gzip; name=IVM_patches_v18.tar.gzDownload
�%�z_�\yw�F�������:1e�.��9�%��F�G��dg���@S�4��8���U��������1���������{8���q���L����.	�v�I�r�)����;Ym��f��j�w���M�;h>�0�k��������;�t���W/c�E��w�;����9�����acb���N��4����;�VM#�\��78��>��-M+���T���WZ��_�3�n�7�;�=���p��-o�M]W��+2J#id�S
Y�w�2���	6Iz���������;��t�~t��+��]�on�r����N��xV
��>3�M�Yz.k�6�I��N�������u�{?������pK�wK\n	�[B��O�
��}"�&�e���F����|�Cf�S��G��;�������#����r����$�T�~���CFmM����cxl��@����>��n9st�``�'�xH��z�^�3���q�skX�������UF��P:R�Y����3�8����J��F#���-���R���$�1�'���P3�Gz�!�Z�v4�������Bo�*=�z��N�����
�t�����s?B��gxb�������������s���������������L�������M���;����?4�
�-���H��)�O���8��v5f1
v\O�_@=�R����M����t%��5Y!m���\!U}�3�X����C�w�P�z��g���w}��Wd
T�g�����g���}�u}}�����a,"���[�5�z!�1O������F��������`05 b�u������P�%A���S7��5�}
(V��A������J�.���X��(�I^
������
�R��������k��m���� m�9����������Q=�t��x�� ��_�:�u��/���e�KQ���?���_��c��0�K-E�e�P������G������
H�Y����Z���k��s���%�[�%�#H��[��]tQ���T*��un9�m_� �b!���>1���wO��u~���~������1,�
x*�0e�5�l\4�
�����a���8�p����_�Jv$4�6'R
�\�3&����q�2��:|�p1t���	�z���f���������J���p�1v�P�����c�[!q�@L�-%���
�X�aOM��s�|@L�
5~�j���A�AU�L|�/�9�����VF�15���3eU��!N�<�) ����m��6�%��T����xS#��(`^_4����,?�<#����{54%��z�F��1�}��&�1-��=�;N���q���9*}�\���g���{a���r���)hP��&�=����oM@,"s���u}�9���b�$4
-5J��c�
_G����@t�+�4q�
�����x��p���zG�-������a�������s^���b3�s��a\K6��B1����q��ij����>&Fq�)���Qc�?N�����bm������)���l�:�����b�Gk��=a����]��3�������C��8���]��5����D��#����%A�	���Pu;������}n�<v�F�]���_S_B�>��G��Q�@N�2a�i8���)�H�\54��u��5�FW5_��qcxS&��q�����_�cu3�]42�
���:���eRj�m���59)2}}�b��4��[�A'�`3d�?�Z���T?-���0)7C��]LBX�La��K�^O��'V���ig�,���zN����q���������-rM��������sf�������x����AesK�E��~�9��wa������KU�rc����uL������B�b�G�cA��������f*g_ a�� \���R�4"P�=2/I��P8%��=�{	���(k<f�E��R�&3Z6'�&�JB�������3�yU6��zl�����������W��><��y�%H��E^#wJS�����1���hx��g��j���5���)����]�r�]�o�K�� ��{
��y�{rr|R��E���u����-��N�p}C���!�P�#O�/����}��P��|�y�p�x�@�]���|����2��K����Rx��n��?������(����x-"�����s�~R�vyF�=������K����g��npd��X)i
H����V:����<T���{s�9f�����[��eAY]
���I��i�����
qC��V�D�7$������5hOH�37@p���S>w��X��r�2in��! �Q��E��x��y��Z�`0{a��lDu��w����������7
Wmq�N�r�-D�2j�%K5e'��78��]-�q������d�\���vvp�	�#�:y_L1�M�_$��r��=�����2�!��C���(s�O�3���[�T�Of��S!����������5����y�<B�����2��k����(R���@��n�a���9���K��kT�<���8����:
��M�a���	���OID�)�O+Tf,ER\��Eq��
����*{�b+l��1\�T�BX�[cY��3\B����d���9l6�����a��H%k,����&����}
��yPE�_]q����WKuXv�Z���mn��z5�����g>��s�������:d�S�Ee3���-�;�P�r@l��f��Y��_������������;�Y��9:��wy0-�u�a���v�������]����*r��G��m��������TYo7m���&����kr{��tE���:������(�s���������\������J.:jQ��Fc+l��>M�;3�Rk
���rC=���V���b�:��Mt�&����2�8�K]�����\CL��^�,l(CQ���Pp�O�0��
�e�R���fv7E�wSh3�1!��k���EX����,H����A������8P��:�*�A�Fc�c�>��l�l��L������K�D�I�)����zU�D��
�h�D�����2�}Kx>1���r�e������~�{�;�5���rt1-��;�v�]�w^ta<���@���M�\�A���i�����q��}���{Z���5Y����JM�>�55�����c����ia�����E����q��������/KZ���~8��;p�7Nz����v��}��+uc���V�O���>X����99>Li@G>G�����QJ�!�����V�
������a���`�!�g1#����>ry�;�5��"_Izyc�z�h.t��2
s`�N���7�~FUa�3q~
N�oN��?3����f9M��-���%�]W��u�/^��\��$��*�I_���'U|U����4�U�lV5���4���.[�b�����o{GK|Q��_���I�F|m�8��8�����&nw���8��_��tQ,~Z�a�>�}�1C�pU�v�A3��1�����������_�����YBw����=��J�.d�����i[�*��9���e�N�G��.����?�+��`D~��)����)Z����aV�?9~���\S��0�
$��������j	���vF�������<���
6R����W|B��������d�z<���i����[@o����`*��f�t�5��OIY�s��������;U7�e�g'G�>��s�����O�>%�/����C�O�wxx�1�-���r���In��q��N1����Y<G�S��������Y����b�6l~�q%s:�-C���FN�+I��I��,���XZ~�h:(
���`l�E��,�'�'I��e��y2�(��/*�h����j+I��=DCY���%5VOj�%����
�\����_��N��8��;>;�c$�d���-}�����#��)��2���O�#j9c��'�g�h�V����<��i���c|��T,�K����l�&��W��8�k(R�
���#�RHG0E���N-�#�"LD���2�k���aa`������L�����[�K�{��D��|�y<3�n�bC,��2g	��0o��m��1�[c����E�W�����(
������gR|��^`��!�&�'o���1��wl���c+v��������0��w��s��NR^4�B�e8�L���<�[1]��*b�5���*�>��J��*�����Q�F�\<��\~w�I�BA�I�,pN����g�w��=�_��;M��M�[�N���8��f�o�s��,T�����f��3K�`8]��\��Jv�/�"��bAY\a�(�J>)������Fh��f��6�i:��h�_�/�6+AF������i��@*
��#Z�8����+c���O�)����Bs�gF\���+>�z��r�pJ�@j�ZF����U��e7�+�67����~��o���l{�[��$����+���P�R2��VU�(J�(J�m��A5<
���+��*�4V�vc�[�= F�����[�_RhF�i('�����,���9���v�]��W�C���
h8�w�4\�{�!���(_q��sG��3{�.��~���5�������"��+?-��<q��K�GmfEGmBalJ}5(��jP��lS��3d4S6��q����1�A�4fB�a���UY��I�Qh�[��lRXk���|2��e��� �m���o9����xuG���5"�~�)�B����ri���Jg�d�L�r�*��O��RE0]iM�F��P�\����x�.*?�������8v��*���$�k;��������1J�kh�A�p��)-h���|�X��'?����o�)�e������Y�A�%J/ �#��<�zR���t>�(�u���>q6P��<�KE����<��Bj�VI
�V�|��Y��69�0�x�M��d���x�\��
?>�Wue�#�@R$�BR9�<���pIk1ga�y%���E������&f!<����S/��iJa���!�����Yk�5]�S6
���"K)G_h.�V���u��G�����3�_	���g�a����a��w���{.b=K) �c����=4*2���X7C��uN�:�]�Z"�S�$I���*�e�j�H)&ZxV��p���la��{����$e�rA#�����E���( �G�'1��e�W���G��[��)���w� ��e}N:e�t�$	Z�*K��t(�>����O1��[��Z���t�}��H�7���d��=pB���%e�FG@u���9S� <6v/N�I���������s1K�e�f��)�KJ�k�g)E|���">�YF�N6w;�<<�������4������*^��%J^�d~U��K,�"�@m�$�+�E$��b�����&�#��T����ZH��j�E]�d���wyK]����� JH��K���i�w/a�%��B�}	�+�M.#�%e�Z�Z�g�T#/��W�_i�Yq�U�A4LO��=�K�a�>�Sn�,xI������0�=�T��v@b��^�sXZ�A�/m|�	q����C����k�������~�����r���=f@����x+N����7���V]mFU
hi�$b�O������xN����EUY�by�U��\��Ej�G�%6�{`/lz��.���d�~,���4�
;�tX!��Y�0�aO�[���X� ]�kU�-��������Rx�#����9�+���].q���Z�� ��� �X:���|��$Y@�_x�l~��d���X���d[g0� ��gf���T��%��J��L�������dfemZ@,������\###"#c1��J��"�d������.�C�x
k���C���^6��'+���K}�j9�����)[>��3.�����B;�B�4���5�l���L��TB���L7;���D�����/�D����o��m$J;������O4�6UKSI�A�����C��tg�qw���{~�xp��1�����A9�V6w�Ux��R1�`{�`��c��^���~^����'0���E�W#q�|	��nG"_���������������4m��i+Mv�����?�[�j�����������X��
)�u����n�4�9=�0��7n>���6��ew��|x���	r���%vZ���E2��L�f��A�:�������)��g���[U�T'-�����I�p�.��e�.z�e����k%��}�U8CX�:��u��{^�z�>h;�K&d�����}�����}o/���C�y�4<�]b�6��cz!�����&�d�O���ORq�]yX�o����1��������c������r��n��_q1�jg���/�XQX�����TO� ���:.�1;/c����'��'�X_�#(��~�������J��C�������E�l�k�*+�8��/��G�Bz[l�)�Xg`����������5��8��.����q����mQ����&���A�Gv~w����Q�|�����G�1�7@@�~"h������T���W�l�+4��2�t�a�W�C�G��#�3���J"aM��GI��O1��D�Ue�d��?��*�OT;"�{9�If����}��x�bT�Ib�����bPL��� }�/��]�I�J��:���#��8�R �d=��W�[���jy;6���-n�r�uS�FiN�r�#��m���]9���,S���a�;O���x-�dY�.
������8���\L|n1	��e	�{��
����<�@�
�|RY"��m�����������[��&�.��O��tm���87�J�O�����8]�s;(�
�{���������b
	=�3lh�^Qqn(�x��@������yrZ��?>���������=��rp}�-�0p�?�LK�����um2����o��-kW���d~��=>R'���[!�~������7�������]��$�����	���Y]��4���}1�oG)zu��P�'��\�"O*�H�	���������+�Mu���������|ZH�U~OBOG�7��.����ZUw+���d�oT
7����h�R�n}�7~Z&�G�7��f�9��"�����e�G��0}6�\��fg�lz�zK�_����Q�=�U>�U��h�Q�-��oY��������w|�k�G�;��&�Y����)@����im���e�����J�{�o��Z���fj��N��h��9�WOL�x�x������=+��wK�'i1�Re8��v�[���Z�y���= P�95Z�)vI��SW-���7��Ih���e�m�,��c(��Ax�|R��+�G�-M���t[��TOk����z�l-��&�$���7=�����(<���c������X��js2�u��}�3���������>e�A=�>��"���dYd������~�q<�8�un�������������7��si`f��-X����& ����<���4���~�=�/k��l<e�S�c����=�������5C����$@>�F=9�����9,�-���&�Q}�.J����i�����+O�3�T����[�{���r������d�����{��
y0-��r���|�� ���������m^���������?��h<��Lm�#DWZ���U
������4 �W(�g���sP�g���R��F�wo��R�=\��oB����W�3�o����R�	�����7��z��?a]�sD���<k�D#���pf�_#�R�8�[�_I=y_�S�/�����5P���������p,��x8������v�O��5p3�T��e::�[��oH�q���������i���8����^'���tf�q�1��,eP�c�������em�oF5�������|���M�}�������y������^O��;��F���g��s���X'qf���v��}�N�2�;3.��vz6��T8����Y��~Y����<�
�����+������<���C��4`��RA��1<���:�,�g>�s&�yr������B��'q|}�P���S���(q���Y��������J�_mD2��-��.��}���u�y@(��o�r��e�N���,ePE����J�4 }�qZ��~�����U���G��Y��-����p�RP�)�������{}SJ���s�&t�h�yfv_n<��pN�������_�f���1"�,gT��<|�{���n�K����}y�<U_���7�MJ?�='*�=����������n��n���/*L|Cj�i
��5�GJ����l���&�
��y�s}�����?��y0��������x��;����T�]�M�K�-J�SU�����yVs<�9�����F����P<<@��GR*|��rfONp_Q�����x��x�Q�m:`?F���y�I�(Z���%��:�g��9����s�;s-�sh��,�sB�y����P����3v�s���n��?e����!����`����GO���� �s��?�\�fc�X����u��,����z��4��H��f)��6T[O�'�4+�v�zVI=G�y�6=k�����97�En���	.���i�'I��������j���N������U<|��z��!��Q=�������WN����L��"��O�C^�|s��yNg}�����@��@��}b����d��m����B?�h�)���*�y��V��jC���_�	w����S1$�����������^�B���^�[H)�W<Xg)a��O��Naq%�\��|��5���>���N�"�Y�gq����Y���Ep��NL�����{�^�K��vI�I��K�����l�<���'�����0���Sy
y���S�{�8��!��v�gHx�s���J	"�W�S����Ci%da��wO��/��h��@C����`�.~c]~c���oK�����o�4��o������������h�6~�F(==%�,x/�����lw�5�9������������h�zc�;�Il
mw�������se
/m_���1�%k�]U���is���/��;l��f�bq����8;n��l*�)����P55_B�� K�:[�:c.���h
�]�4zvJ�<q��6���A���Q���2�HS������5�%�y&7�En�w�7�S�U���x�Y�r�r��JLgx#JMW�1W���MU�}�x����������������v��]1���?��6��o�vZ�|<|sq|�l���3�t��-�p���������p\q�����v�����2����y�����]4k?�����f���/�g�'��F��sP��K�Wv��:&��j�IS
J->*�����x�q�����x%�s�����E~kH����f�)�ru#��w9���O���}=_������W<o_��A��Wg0���a_�Dg��c��/���i���<9`����2,�}���C����s;vw�����gZ������I����5r�|�{XS�8j�7��D��s$���g���t#��(?�%�,��W���YY)����H��A;�b�R��E���b�-MD�+I�j7r���Sg����t�A+��ad��7v�{�������]�����A��$������hC��E�;�����r�F>��&�h�	�����h�|�_�I�������_��}b{7H���XX���(?[����H������q
$���=��;�Eq��������;�[Hva8�>�>��'��!1�:����B��:c��;wA��h�?�O�����'��������9��,\c����(��O���_��~�~|�'�T�zHJ8�3������Y�pf����.���
���9b�P��x��}�}LB�����H��>���� �H�L��$�N��M��hV��>�����Q�O���bX�'�8�p��WK�,�;��R!DL�x�G�r�lv���./=�^�q-io_'�l2�|�/o�N.NQe���W�.�3h��_��LL�g@f��j@��8�i>*<����������Y13�.��V�#�������r�@H��������O�'�\
E�x��2���4f�)��O{����C�.:/���X���8p��!Q�^������)���$Ww�y���7��U�(+��#�f���pf�
��Jr����"V�
>��`���z7;���8z��1��R���<Bp�D���70������7@�_�y-e�����}������'�e�������k�mL_�����$4�T���nQAs��m(�`k���o�D86���*���c;��.5
�����hw�	
����3��������q3�"���f�N�y Z:<��>N�����	�ba ���t�1�g�8h�����j&���O��j]X��!�Hm��.��� �\���+.b3�ud�6��5e�=���������0U-+X�%���<X.w��������E���I�	� ���X����� A������:y����L8�,�2-uE�w���4�a,������xL���*���fQb���1�ic���h��k�O��i�Zpnaq;��5{<���Y��i�������������Yp����jKNt&���`��"3�dLz
��Q�����z��o�8�������1�,�n�`Jv�D�8��E�~��)k����F��Mk76-�-aY�����d������;��nZ0��G� '���X(n�Q��T�O<�}���@J_��V(q.B�sM.Ns����I�e,�Mg��3��[�`��	;1jwF�#�t�Ac�������I���w[VF����e����l�����mN�MaO*��%�sLlq���M.���C�A2�����{�M9���[deu�����O��h�E�5E��'n�YG��R��u&*�����m0J�g����aF��������T5>�_���JS;����n�����u��v�EYp.���tgL-6����;��8u&	�x3���F���,���-��1Ro�h���I�/��b��Q3��;�[��t|�w Ct-�Y�/#��}b<���8=^4'�C�5���,����_{1a-���RQ���3�����}�ow����/���1��������4�eS(�{v'�����B����&���/2�bl[��g&�����N���{������>M��f��`������X)���q&������k,��>_9�+q���pZ���v�gg������8��H���u�<����(\hkM�Z]����_`������q��D��&����
P�����e��:����z�|wr����$Tf�����q��q�|@�Zc��8�|�`�H��qYI�zCe���������p��)	����-^�\��_�?��Ah��� �ne�H���6�v��2���l���C�xf��'G����|Z#��tn��L���De���OP^��1x^	�(<���g5]��-���Nd��]���avz!�~O���#���~L?�N~G�u<�\�	�7(���v��s	l+UAw-� m��\���X��r`����e�T�H�Tu��(�J�2y�u���YC������x��|j�1�����	X����K�sR�������O���%Py �W($���8��z*��_���en�$�#�������:y�D8����c6��M eyE��70�C����3��Ab���3Wo#y���@���l��o���J��%�&����a�+���#8���i���p��O�m4�1�C��8J*c�
>���-�F�.i�`
�~����Np8���w����	����K*s4%�gb3�:�����)���������[ ��d�5�<���~��\��	��9
�:���EJU���-t���])m[��beO�j����W�4���1_���������
�~b�}��`o^�p��G�1��k�oc\
��-��k����� �����R<@�`)��0(%��
y�/=w2Bz5*���T����H
��H�l��O����z�K�����\8�Yc��q|x��;���a��I+���o|��������1T��;�I~2����p�)yu��
)�@!��"�J��M��%e��4]��7y�y��h,m���(-�h��J 
�[���5u#D��m�����+U���|~{{��)��Z�� �T�&��-P,n��b?�M ��|�u�T����`C���6���v>���=�Z-�E����?Q'�������Y�'�!�����������
��B��|~��)V�V�T�5����[���
�)]�2rX�\qg{V�-;��	d5�p����	���'�����t�X��<cu�A��a��(�W�6���)��B�bA��J}*�dZQ_�s|���y[w�iZ��h�E�{�P��e��@�)�/D��oa�IG��Ra-�����Isg�7 ���2�������:�C|	=��x�\�M�2,Y��8.�C)pB1]
��"30��D��3N�S�,<L��n�*L`������^j��wv��e	�7/��C>v�j�N���������qDg�����iH���4'C��	�=�zp>��deC�MVi��?_`���_Qj*�]v�Z�j��'6D��N�%�U�${�L	hl}���� X���
�C!3����,�)�a
e���)c����G�`$67t�4X�6���I���xV����Q��+��6oe���#��;!9(����r�1��L0y[�I8#w'azB���j��|4�0cTjY5�P�$�j�#����!�*�!��d��v�������-n�}�����Fsl���Ws9�q����6Ul:�5r�Yk�RK:�SZL���B!����`nY������y����u�:�n}���S������x����6�b}D�0�>�m���qs�?��U����X"~.P?���k�Q����/~���t�b��
���"��t�\;�g���������gwn�����V{2��V�6:��Biy�R'��[)8����{���`����#m��o#gz_�u��h�[�Kx�������1s�9VV�z��-�y�>K����gp�.�#�,�|z����O��31�����^bt'���`�4��{���/��Qa:���<�]������Nn'}����2�qj�������p��AG�a�X4��0�6�����`�<-�lN(����(9K�2���u$������q)m�\�,��X^o����t�LQ������� ��y/���#�)��w(�^���E�J'�4yQ���8%�m0@R2gY?HuS"�ui'��NF]g&eN��;V����S���������k��HF��)u/�������~y�����S��#tu���"�)q+y����F�1�c������a���������6W��Q��������"�#+/%���s/���"��������P������=����!��=�����<z�YN�|&���Daqz�x_;�M���[�1Jv���
L���

Q��ci��n�����|��Wp$�)p��J���e+��q`'8���)��SR��p8���)�-NZ����P|i��*zx�BS�A���4"�|��Nl��a�t���N#�H�L��HP�e���	��������j������p'����6��R��7u�Q����6�Q�u�G$rv)���U�
��K�d�6�� �*W����A�����W1�0�.�B�K����'�j�Q�mr�e�.D���K�lG�F��<o���0��}��E'����6w�i%�!xj����b�;����������J��y)|*1������f*��g���������;�<�j3V���e5�9+1�SF��D^���N3�hJ�@� �X��4����6�D\��
4�1�d14iB��&$�����T��k��E�����C�DB�����$��	i���dDI�HE��"J\�0Lc1g���f}q�3��3��,��4�t�����D�4��p'�-� �+�����p���k����YJh���0�R����mC�@�%��q�������Otq��D��2�����.T�.���D����9/nL�-O��:�7F�B�6!��i8�����4 �x�:�����	�8��%����4E����d��l��k���<�p
����,�Z�V�����������r��:^���/P}z
����[CC�rL������k����;�`�wxt3I���ORq��MY��=����1v#�$X��d�BY�cd��������v��/���� ������<�h�� }��d�8(�����?��9�����[��2jeV7V"�Sl�e�1#l�|�~]�tX�X�HcS�`c��>��(9H��w4����&��������������1�\dz�~_� �#�X�c�� ����?�6��vi���@���������?�t*`��e,���M�������f���y���A,S�c�{��I����<GVj�S
:j������e��kwS��.����L}I`��q*���2���C�IIK�LA����s�:����/���{���yep�,��?��g����Yp�����i�r���O��;��"�s��~f��,���>��g�ge��sy��x�Y���'�d��3�}���\���>s�g.���ph{O��FoO���3�g.��e���3�}���\VrY�?�QTD��$���1�g��b�Y�3�}f��,6�b�Fv&;{��e����>��g��b�Y�3�
���������Fv)�z���\���>s�g.��e�\��odg2����]�����3�}���\���>sY�e��W��S����]�����3�}���\���>s��}�[����e�2�g.��e���3�}���\6�e�bv6G{��������>3�gF��h��3��0��������rv9�zf������>3�gF��h%�%���Q~�$ng�2�g.��e���3�}���\6�e�vv6C{�������>s�g.��e���3��p��������vv9�zf������>3�gF�_�h
K)�xh��5����l2�e�`~{-
���^�[�-.��|8�wOl��8�}p����������7�����A#�����J	D�+�I�<a�PZ�)�'��������,
��8����i�������qQ�,�w&����A�L�ckh�_��v��+kxi���ns-�����q+���F�t�9�Zf��%��������[Q�����R9��C����Ht�L�4�i{+>��:���/�����H#�p{b|e�����~�/��=�4�63�x�Y8
��L9W��M����
��w8���3�p��D��9C�+�7#8��T�8��	���������������%�-g���c��)K����ug8�8�uxyT;~{Q{��
s8�>o�5`�0��f���zz7�=2���F����R�vdLRd�n�u�J`k������&�����'��S�vu#k�.:|�w9�������}=�����?�����������t}��6����{��sw�;=9����'gp\!�U��K
���A���v�����u�������[�����=8���k�P����vq�o���x�H4�c�����F�_Q~��KxY����UK��R\Y�/�\��>G��qK��/"(4'+j�
�
I�T��]w���zq}?�8J4�V���PY];��u*�K{�w�1p_�GR>qmy>�%�_*/���q���sEt����DP����%,��A=m�62A(��mr��z�}�}��7B����Bk8t
AA����d��pr���d>���X�'>�n�5�A����X|z]M�R�#xX���[�'
Yy��CL�9(���O���:+��&�a�!�^d�g�S�cR�9X����G���^}L�@S�����I����m@A��#��:kl����%���I4��1���v�c��D{�#~�[�����K���a)��;������hq���d��t�},���\�����
���'�w���/
�L���$���FK_�������g12N�.��V�#�v��F���l�g2����s=(�� K�2��s�8��R��(dx6��+�^-�_�l�zaj���`��-1l����U���(��#����[�R#��]w�)��z�Z|'�E_�pA�E��p@;�%&�9J�d��~xP�j��bC����4i��<
q~�_���}��qN���X}���X����������� D|~��`���!�N�"!���+'�C�E;�����rz����`��hxrq����F�R�
�;�
����
�t��� XtS�����K��bY�M����c�i0a����5���	��Y��;�d����X�������:y�����I"�d����\��i�r �����8��1�Y$h���!��F�����H��;�#���
����������@����J}0BX�}��T��e���Lv��E�����_�8��0�|����Y����-����P5�x@�GP>���GQ���R��X7�wl<D�=��i�2
�@���8�R!����?�D#M����:�2�,�L@���p��5��b�8',�R��Tl�t�A�hN\�G6Z�s���-+�Pdlga���
���Y2	�<�pK/����c���c��A���z=��jGGS��a��'���>��xG�P*������x�Fj��`4���A�	A��w?��l���Gg��d��z���Cha��]
�����:}���E��t@O�'
j�XC9�C�~O��������A���A�/8����Q3��;�[���5���<��i/�#���@��6���F#���n�@��������0m�Q���qMh
���:��M��#'�����%U�mcmn�G���Y���;M���������>��_3�E�K"����n6��8%i7����\�+4���o_�}���	��y'|q\��F�B�c�;p�/����p0*`V�>��Q�=���5�h�;9k�o��
��f�������8]f�]kl��	�3����5����j�%B��[���zn]
h�����t�my�b��c@d=�p��Ah���n�[Y.��;�����]����<[���'�����Q��75����;�2��3D�'�pz����
�&�q����K��������B���#c6�����i51�k(������qT�PG~~p2�oP��Y��IL�`UAw-� m��`�%�X��wrp�UF��Ra$�R���Z��r��xZFt��<��}�%����BH�<j��'SK!ud]������a��|!����Y�I�������:�2�7��lNC��1�P��&p����H�����OR�0f��j�����j0��Q�~�A��@;��Q���k���|q'_\]����" v�Fhee�����V�P(n���Mk���������x6<��!|�#�7��A&B�9/��S+�}@��j���������_����_���ru{{�\)��P�V�����C`�����\����r���o<w ����Ne�Sn��z�n�W��v��b��+��vv�������=p�s{$�;�P����,�*6�/~�c����C����Y���;n�����!���x�99�'�|�DZ��/��Fa�PX=����{_����<x'
��b���RX��R�]�h)Bh)L�\�_�U���������9W�xu�A���j�FL|<�l�UHoS//:�``
�� ��\���*+����j�����?�G���������V��|�����������A����C��`#�o3��6�!����������s�-mh#\J���3����'.U=�o���Xv�������lds�X]�o�����j����������	����:�����]�{ek'������U���3$D�i��X�������Ji/W��#�����8X���}����o�a��q`����G9>'���������uIur�X��E������h���&b�1���a��+�oK��f�,��|r��w���
Y
 mk���.0���=�?~���Qs�,o9|_>��V�a���X��
A)�3����m�!���B-D��&�1��U���#7�#�W���j������v|x���|W!��u*���?�A��8�}=�j���X�~� �SYo��S�M,�������?G�������i������pL�:O���/�&P�����Q����Hi' R��	��b��E�m���y����-��7Z(�S�f�m��Bv���}�q���q���x������>N�Hp�6+���r6Z�T��K���\�2�`��~�X�����;�B"��}X�e�:�G�e}��3"����Hx��X��]x���
���?Vx>D:j>~�z%p������^���Xr�5��_�����#���x]�T|7�\���C��n�#�W����Di��q��R���i�-�L���v�L�5t�-�lr?Zd����f��^��D#XhU�
�r�TP�'�����@<��B�6^w�4J/�2�'gt�g��2������:�~P&\/T��P��f�����7������7_q@���J4��������x���N��6�������b2>�=>���]�ywt���c������k�j`�����T��=d�j�XAf��}���k%�GV��%I���h`��\W�b%����i��y�cg@��3�%����O����������������_1�6!� ������b�E��i��[Fn���rEo��������o��+F����)pg�����zK�D���Z�N-0XY����]�PL�/�,�M��
����m}�o>�^w_���!��������P�K�Pc~���_$�������������V�m{�����;��]�TJ�n����Np�H/C�,� �����DftM�t��>�)���G��
�VR������P!�6{$|����z{H��Z�������j�ybeEm��(�]����'���������%^�$m|xO�~5�G�����G����E�)v;�^iww�����m�RN_�h+�%�����#~���m����/'g��5g���AN�.��C�P����jg��Q���Qv5R�z���sr��x����R��m�6�bq�#g������7[�-<XMj��m����]?_�>Y�P}�5��?	�u���D�_`����|G5����������}�r����f�K�RD���]�>�����=`�V���Uk��������Jq��)o�vv*{;���]h���wg�TN�����W����@��R�q0Z��60��oR����@l&',l����S��iz��hb����W��=�����|\9+��ue[�|G�?S�-K��(�Tv2v�>�����X�7���(UU�����F�����FD�����O�_[YlDKs����f.��tY�NT�\��u�ST�������Au�X�XV��Aa�����]��R���F���H����(�I
����B�9����DV��-�;9��6��5���/|��x�-6E��x�]������'���m�c�,J����z������U
6=k�[d�����1���3���� �Zi#���,��"���O����B40�'2H
:}��A�d�V��!�,,;�_��
����6s2R���
{;�^�.��|�]����v�����ZIG>U���B�� ��w-&a�!���vTJ=��U�8f�x��S����lu:�����WnW���jm�����n����+-���D�i�������G��#���X�@��@��M������<��q���U���P��n�\��TvK����Z+�J��e��G�,A.�1B�x�M�K[���o�!�5�
a�>�++gT�'��^Tj	���q,�k�K�8�b�yl���W7�=�c��,�;�M"�+c�%�OX�s��:W�7�B��9��4@r�Oj����nN��x�7+������b<��2��Y���<o�]4�2���|��>�D���d�	��*.��o�����Z��VP;D�j#�P_Qg��n,�
Hk����rC�HG������B����U����m��h��������Q�0d^�p��E���x�_3�(H��7��j�t�(1���9&

�����h|�����n�X��	�Ad�.�L�2��j������.T�V7�Z���r�=FgT���e��o;�O���o_�UY����MU�$����0�g��r���-�v����� �Mi(��I(�:7V���"�A�Y;:y�Q�r���^�,@�E������O''��k�����q�b��-s%F��X4M���
�c�e��L�l6�-
�1����u���r��mj����qJ��N(����QN���'<y��s�������-�B��jf�i*N�O���S�;=��������n��)�w�1"�N!�e�%�� 1����!��E'������o`�	%9�V7�Q��}��&��U3�1�jj�0kL�&��Gk�Mmw�g��-8$���T������G;���c����S��ylLZNz�������N�*���b�Zl����7[HC~KF*�""
~�&(�Q�p��l�Ma��?�='4
�O���-���{��"-�����~�v�Ddp���	��Acx�^:�hw\�c���&j�A�l���A�\�bwr	�g�K��	�\Y������c=
U��_��w����6l���}�!�yn���&yz�������T-J����vi�Y����-���nwv
��cu����^������;�v�`W����}����r������h��X-�I�B�U�x	R��`uU������4�h@��ZB/aGz����U�����N��P�Z��6i��k4�NHK@�Q��UVD��������y[���Q�5/�h�9�tP�R��U�7u�<lj�RsM�*�����;\x�c�D����{��{4p
��pP^��t\k�����_]%�� ���QX�!��qN� H���U>rB��������01�=4��m�A��\�\vU@(��o(��h�����i� G�qUpp
V�\6��R�}��@���x��F��l52F����]�o�)D�&���#}�����T(�����l;�h������F29(�_m�����4��
1�}j���aP��%w��cwl�m�D
v��C�g{���c�i`S_���'zf�1��Es����`��� ,����9��*�	n��.��|
���]����U������k�2a����Dsl����
]�E��(�@��xoyE���#W��������oN�����A��ax]�e�mC������"(���&��wH���K]�
�EI_m�|�����h=��O��cyq���L������	���!00Zb��Zw��` 7xU��g�KyEcw��*z=p�[�,�M�>:���	�]�S�r�����u���$�����E%���XM�]����c�p���o1�|y�+��H�K�����te-��RH�B�PG]y���R��66�����y�W���M�:�����/�� ������C���znzjiow����[�(M-�-�P�����P�.��!��dq��T�kG���d0R��m.�sij������ ��Z����J�����3�akm��J���*(o;��W������
B\��KX���P�������!������H����Z���n��rg�X��.U�\�&��:'�C%Ayu�/��V����k�2��� ���Y�uiG��e���a_n$��k�\7�i�
D^�_��F���|��,��b>+�4��A 1n�3�.������������lm\?�����D��F�^�0?z���o}v�O��4�������Q��!��u^�{�(���>��R��Zm�����;���X �H��.1�����pr"�������W8�$N��4��t�8��^�n�?�3�f��X&#nqE7� r�t`���(`
i���L]*x�l4Y��v�n�����9P=�P�k����������
�53�z�i��=���������u�|o�������L1���p��P�v`)��1�Q���A�mi���\�A�����u����ki.���0�XJ��Ql���c��Q�N���O&pX�J�=�x�T�W�s�����i�~�����;I �$��8�n�9/�e�^���l�JX*Iz���Qo��M�#���;�ri8$����4@����'`JD�yG��(#��[CgiG2�-p����-�D��b}y{'`��n�eZ:�y�b&�-������[���H�?6�PG�E�����bq�;����t��~8|����k;C��t�����sp���g\��gpLG
+�An����&���y���
���soRj�n�u�k�q=h�&T78'�Rw����U��A�w�"2�E]��n<��(i}������n f���A����E������K�o%yI�T0#�{�c��BP��2��kw�t�s����F���k�^D\4�[aTH�@$F~AG��=)�MF��k�
��s_d�t`ZE�2�C-��V&����,��AL��eN�L�3����;��F�/�+y�0aaw� .�� |H�#�yp0bk�����=n9���r����M
v'�o���l�A�,�-e^+�um�0�"������^��#|�2�1��8C��?K��~�^F�'Z2�oK�l�(W7�;��W�
�D�L����,'��2��u�����[o����Y
1Z���O^?�������Z4pp<��0��f�����Z��+(�Yb�R��6VQ�8��ee������@��)G|���.%"�y�z�������z���.��j
�P���[��e2q�5���a��K���S.��!�����Tk�Z�q�F;��T����-��Z�{b,A���������N�����z>���hV+z[��	dg�L�$��I�
�r'����%���e
57�xe��j������l��+�T��I��3���XL��y�"�����Xzi���(�����s��_fhs���P�%�2���Uh!73�f4nQ^��
U��2�j�T,��ee�3"%����=�m`��#8�euM�x�6��kjHcB��X�����H��#?�	g��f?�ej��!/a�T�qgCZ�L0�UP[���]t�l�(���|<f���O@9�Fe�d��i�}�^N�(�|f���j�=�[p^�3���!�P��]?<5�_%M�R.k�d5���W����9C�<��P�b�4�����$�&_J��b��kl�*�|�I��u4?/�w���
�����o�	��d�b�i���x��=�%��r���G-���B{%(
������a�	E�Y;{[or�lt/��|�����YT��Y�~g��HJq���:U�z�z�������w ��H:�h���.���c�������6��������
%�P3���'��{N)3k�
�J��LbSz���i���EV
�����ZZl��;�{����X�3���V@��������#5�)���r"S����?�D�^��2����
Q4*����d��\�g�"z!B+4	���mPr�@����W�_���
#����k��`3�+����+��*|����E"^��1n��������t�n�`������&���DGF	Iv�ne���kn�g-s������o^u(���F���H��A�q!B����Q���&����f�~''d���&|
�����~;;D�2��Y���phN2��$W��$�?+yhc&�
�i#�3m�_
�{^�3�
i��gv/X���1GG&cQ+�F|c����,!�G�������t�$�!����O�'�0�~%�\�:�s<&cH@���{���k�$s/C�AL�`�0!�<k�}[?k��4���<���4�b��9}�=4��Y���DP���=@b	�&B��-���.b�]
���7�+S����I�����~����f`���:��+n\���S���<�j����$�e�3��H>A�I�����D�
��r�������i���+j�M�H��x�	���e��`s2����� �w�6�im��Rv���
���l:���t$�~Q%�/�������iZ�
b0�{�[�V����o(p�&e~�&@��}�����=��.��vN.Ylw^�����N%W��cO��Z`�b����Zx� �,���c>R�1t�q���ev��Q�sH9=<������
2�e�
�r��Z9(�!X:�7��[������S
�tO�6OD
<&��^��r
J����O�T� �V��b�I��bP��n����<D���
mv�����A��}�Vz�^��^�?;h,�����4t�X�>��.�����X���5�t�5[��o�m$�kb?����moMr�6@�����1�\����WSF�����e�l2���_��LFu��DHg��V������|j������c,���Iqk&��it)AR�f`B�bh���{9c8iJ�����LtpM�w�c��q�����+1B���91��t0�>�~7�Jl�@��KJ"^a��8N6��PA�[����.z��
��0��a����?B��F���Dwid�������c�������e.@�P�,��Z$A�fW����<�CK
[����0\'���������!����M�H+f@�p'��|�'�Y}�$%�2`�(��R�1%�4�������Ui�C��E�6d�����Ft�����a�9D�_o����i���~|�[��
i%�{�4��7}w�g��8���r���e���N3%���;9�F�����N��uC����jn��$	|������2���HZt��7u�^�Tn��R�������g���iV��� �'��wCWE�?�Rw�k]�bF���\��
�e]�����3V�>��b��dS]�l�Z\�&g�������N��-g�������.������#@����'��}��E� xG����>�Q��{e��z�dxo����xI���������McvF�eL�n�����p�g"�Y������B�\���8�3/������$�{?Q��-�aa���A����]�Z��4a��xw�����Jv���������Q4��v��P�1���>|���Y������R�J�V�D�F��dU���+Q�A��"
���7�y���/��Gh����<�k34�����&�E9�={�I�&)���������B� ��C
*��L�p�@���^���X��K�W�+*N�y�SLN�{��|5��O���$�n�5I�����
���i������@a���B�z7��~�z@���H�S�]�^�v��L� }��K�N�&����|D���K���hxY�(��/t���p������q�G���{0i��(>���F0�=J�q�=��E���t�.1��o�;��"V7�(����[K��Pk��D��^���2��������1P>Xax�J&% E%5W^\[�X���`���y������5]��=����/�����pJ=D]F�(����S��QK��Z�Z�l"�����'�S���|_���I�����1�	��[�8�����J;0Wa~�$?����U���(�����|����XJ�J�4y�!�W��������#��w���
�N���1����d�$+�$@�E!d^P8%9Q~�w����,�f��J��`�2�(�n:
nr�[�>E�'�#g�i?����G��K���O�J������b�|��5"�n~>��1�����������f��.�L])���I0��~5L���d�{��%���(��������j����n��[�V
s8��MM�p7��7N����� �������\Ow'��4w��}��K���u@
���O�:|��*�h��\(��<��6���U�����x!~�E��)�����'2��M��h��nqe�6?O�{@,�f�����~@2�����1����\m@���}S�=�ll7����mo���W��vP����o��8L��Z	>�Q�E&y�����������me8nh�
���)�Y������E
���>W��SK����bs54d�,[T��T_M0�H�0*�c~r�k��+�i��	���ZQ�� l�m�@5���d��7T�v����!�E��?_9 ,f2����c�X��Ur��d���������B$nhC�A�w�~������iO�6c��g��8��Q�@3g4�h�uc$�<4�.��7v@�"�*1p���2���y��<��ln�K����T�k���Z{�|�W�z�bqJ���������ro�J�����C�A%�4I���K��S���,�H��5n���#X>����qO�7S��B�,�^��7C�$��R_f���Hx5&�_e�<g����-��i�[R�����P�����e��/ia[f~���[�H	�����������03���e��I*�~V�)�i���4�T�#6M���`l,�$f��X�$���`g/�]�G�6������~��p��[��x�j�;����U�qh�����f<��Z��F�E�9�j����[_)V
s4("m��Y���Un �N�>c�j���3S6=0�,u��i�0��}��9
$�l9]����qh�y��oa��;6��;��8�2�]�����_P��}ju�kC�P:��/8r�x
�
8��P|[��^}1@�p��i@��tdC��|��1�=��D�s>`z�	����)=�����0u+#���
�Jda
o���E*����*�,�E�����/q��<[�9�QG�1�+��0�#d~F��6�@��� n\�+�[Fe,W�����Y_�6�����A���r���+G�|Z����U1���������u48
������p���8��[�9j�tp�����I��w��O�����(=��]���j�'B�zR���Z�7k-�z\{_��F��b'G��b�0���+�J�E��E����p2�PX�4r��2��	}\$����rF�*�K�(�^���������?�Z����k���^���b�J�j!A���/R����*T������7���{6�����\^;�;�h�\����_��Y?�������(��M���!��~��9>"�0
M��
�����K�+dzjR� hI��d�I��i�`	Q�~�J!W�xf�j���Q��(��Q1���c24N���
��&>��
-:��6UV�(8N����F-�u��o���h�D/L����8aK'k�P|�+��X�F����[k&)���x�52�d!�F|A@k�Dto,�S�[V� ���/1U����_rd���-����C��=�A�s�h,���,��E����p8���fw�2&N�%�����><�bw��K�1W�B��� 	��q�f�k�����D-��!����V��+�I�h����'�(�F=��u�\sOr��������)!�l���ot����3�N��f��
X�Q�F.A��6���1"A&����Q`��:�zD�'�aL����5��}���OKV!� ����|AE�e����v���}�����kx���-\v��jYs��5�WX��i	�"����-8�"�gs������Z��^>����aZ>{<����2Di72�@���Ww4���q1���Q�d����N ��3������Hb<��d�A�[��Ez�L�I�������D�����W��
��1KFa
�5c!u�D��2+��-��R�K+6J�)w8��	$��T���*rfk��h%��,U	��2t�y�_8>i�����H�W���I����k#L�J�6;��}1�=��5) T���%i�B�����f�/)'p����h���N&�Z�%W�q�M:<����{=J���QHwL>���$���j�L�����
��yCl��.9�)z�B������<��d���n�������g�����n��Mr�h��wt�s�Bm��Y5(
\��3�D�]<9���P�#9�NF�g9}^F�~�&)���y
3S��s���
�������	2����*o�FJ�zn�k�i�����~��)�IZ�j�kr���Y2��gb�N��qw?��CSMnE�`�2�
[Q1�{��L�G1��4�����~�.�YAXG�#�.����\U��
��	�4����s!�s;�����!8�\�\���&(5z��A�p>��:>8=D}���KuDX���=����C�`�g^�l��FTW�2�F
������x�*�l�pTlv8NP�Ry��:�9�z�s4c�x�����x(x$�P�Q���
C���P4�B�
�>Xe@|�y��<r*l�0�c�P�&��IfV��BB��"�T����;�l%L�N-�Y�w	�Hh�W���\����%�:f�r��q��k��uV
��\�����G�Q��_�w 3��Z��� ��u�!��u�E��0P���D
����-I�c���$�4�0�1K)����sa1"��#��������1��
���+��`����s:�%�S�-�w���|�Q��W2�z�m��D�c�������?�b�,�T�b-.��k�{�_kt�����A�u6�1���a^H_�.�@I	��IEF�z�Q<P���jo��m�hb�+���*�pk5z�@d��3�#0�T�����&V*\8q�-"�a���1����}=$�8
N�e��g��/�Z�o����{�!|'_;~v�mu�)C26v�|
��<K�o�7��Cq��*/�L�m"lh���n����+r&MC�g����I�o�8����P!�;�<t�N>��v9����tW����H�w�K�u�9 g���A��i�h5��T���J��e�h�P�a��r�)Y����y���rKH(��C=��9�v����7T���z6w`���-��b^N(����*qxM�F���58��f��F���s�m��Q4�����L�B�3�b5�]Z�J746��q:"Y�Q��a&./��;�f)������5q����*������������=:����9��
����@�p�����D@��������V�B���h_�������a'[���Q�����m�QJ�x��� ����E��
*95��������1��jV���C�	��E�������\�S�l���U�WX���[���Q/��@�N�5��-=@��%����YG�����6���7-8��V�bg�@&�~���(���<��}��a�'�<��T�%����(��yK�!��x&����d����!eM�~��P�6�y`�|���r})�8��TC�":m��[G�B���
[��f��C�FH��:=sdj*eT
����
���A9^c���1l��y"#�"����������F��'[B�/���D��6a��NN[gu�d������7'g�9�c���E�a�k:���Q����ZA�J|����5d�.9&[>IsiY�q���]h����lH�8n-�/-I8g8C*��/+�0�1>�������2PDZ_!�t�2�SmG*��U�������@)y>[@����3"�h&�@'�T+��`���]3L����q�6r����'��7"�tz�������
)k��K?:'B�86B��FH]��j�\@pR[���/�3u����gt���X"�d<J�����6'�lf.$!=�h�*���W��"d��z��e3��L��L�xH��d���+s[�aB���YC�M��]�}�R�;�&c~����pEH=�x:��u��������"�����Ck�_��LV�2U�K3��I<
��Y��Z�7�u��#�j�	����
q�2��>������g���=\A�vU�Lw�*l3x�E�@����m��e,�o�/u�����E� 8�z��B�Xp��R%���
�<�7px����D�����z�P�+�0u@�X�\�c���c��7n��II�}��&��b$�@L3%6�a��+r����g�����#���k�������E�J ����HZ�ucQ5��v��uc>5j������FM�RLUV�L*I���(���lN��������Y��q���j7fi�1���q��������b�5�3e��Z� :7�^��-MZ-�2����!�rb���!����I�C.2J��!�nK�U"��2��x�@����Z�p��M��=:�i�8��I�>�'��9�y��D�����+�A������eN���\���v��\�
)��a���\�0���"+z������{�m�b}��������Fv�K_#)�����+�VX�Fz�4�9��������Ri���,E&(�h������r�2��0A	��3;�Y��'K�
x�������F�~�����W>'I���u�|�`0Vp#f�y8=@}��g9�D��8���k�q��ve��"����J^N��?��%��)����_���X�M��'��s�	s����D����_a���ND��������nG����@Y#Z���0���^c�W�1/ �h�2P�w(�t[+�������/W}5E4t�����We@��d�E����@�>�G�`���<N$�nc�i����G��Am�i?�x�F���n36�@gQ��0�Zcl�����I�4���7z���2����_6Z��u�����L�]Y���QA3���@����3	���@�Ht>n�Q/�?-��C��ya�5Y�\��$�#�@�V;EmW��/�D��[��|�u�������"1�c�L��G���a(L�C�"��Y�CE������G#�_uZ�\���r�����@_�H����K3������J��u)R
�\�)T�
*%,T�������bd�GI����>0V�����&��w�!�9M,16�=�&��Q��j3<^��Q��a��3k��T�*.����m��^�';�������+3\M���p91J&�����S���#���T�+'��d���b��1S*�8�����O��xQ���H��NqF�����j�'LV����9����_�'���r^������p�b�e�y�H�q�3M�x�2�Y�����FV{c"�k���i������JFz����aU���B��p��Z���H������
���Tb��i���Q���8�bXAd���c1H��(����Q��`���9�p�!����������E9��� Y�@�!!�a�d6"����	�w�"�����f����D�����I�0bc�����^������<M���2�,�@+��_$WM~��V�e(-[�L���9J��T�yH��!}IXRv�U�@�|��!�p�Z�f�!��R@�y������X '��,�j�|�������n�"������%��O7���0���&��t]����n�i��[?�&���H
����sI��R^���\��4�%g4��y�i�K��X+��������S��� ���F�&�H�������t\���s�s{di%�w
(b{�v7#=�a5XU��c�Ns�i=�l�Q��3�J�*S���_�^���R�~�^'=�Th�:�D�N�kg8a��h%�����������!qN/�@���������f#r	��w3�E���D'��\�1.���H�/��)�/��C
�mI��|YI&m�B:i��jUs�~(�H��)�$��yk�:CM�tW|g����i|�/��G[\Bn(�����}����u#uM����:0�E���`B��	�4��������������*�CGk��Do�P
����3)HG�u_������&?����D���*y!j}�cy"���o/�����6&w,1	�P���d�(��(A.%�lS�V�e�����T�����zH7B��	�/������s�#�v�CI��PG���z:`�2��PI�q^?�8k4����%ON�g����+P��?<���>�DT�0���!��A+:D2�����u��
��3*.�$Ig��o�U'�=�d�V(m ax��}!Ps$:GiJ��J��J�!�v����N)�hKE��A �cfG��Bg;�����5�nc;���$W	����{��(�RD�����	�.�����LW^��-�P����J��[�Gs��@i�Kv�'x(�b7���D�5���A0�K�^
��+d���#d��L�y)�6
�:8/���w9�4��� c�)
df����z�0J<�������K2���\�M��,M	9����d,Ux������x��R�F~/rN8�,e1b��[.����A���~c�IF@u��1_)�f���|D�9�F������uG4�>�����*��$�.���P��c]��]�R��������R�at[zGZy�*t��
JB1����+�w���(i�n���4�%�l�P��z`�8M^��zC�� ���P4��I������Y��E������,�
�f�>23=������o����WG���)��`S���g�@3�o�)��l�q0���e�N,�����;�5�E��n��*�z���Y��|wv�K&�v�{
z��6�p�0CUB		v 2+�Aa��h���z~i�#�L��d���*�Q��T�{Z�,*+en�1Y3P�CaV=���`��(L��bw��=��h��zH���)P�P�duT�Zx�l1�_EI�.�x<B�><��3k����j��d�����<��P��*@�P� j4�:�%��V2Z�)5k%��O���(��C[��a<QT(�������T���C��ik��=*s�H�Ej�0yL;�H��9���H0��4�����h�2������l@Z6��fN�X��A�s����4>��]�5��i��]���m��t�����k��&�6�J����(�_���]���+W�"��C�k��=6o���&�y���Iwd$�������H6���"%g��-����N��>q0S-6�dT�����)���a�*j�i�{�$<F��$������U��n��/��������R����B��ZD5Dt��f�����<	g�S}%� ��e��3{2�="@��5���N��C��=-s�n�=&�v��,�5r]�_������M�%��YT��v�1��B��xX@�_;�)��O�g�*!U���H�Lsx;"�?C*1M�P�Ua��a�I�I>O�X���*��/~����45����B���u�9
�>I�J��<,��C�
�C���)L�9p��t3��j����	�9��W���v7�H�z����'":X�$3a���Si�:� 9Inzp��	T�>�=��
GI�_ ��o�CYb$�'wm�"�������l�/
��:@�����<�&�[��k�(��A��f�Bu��2R!\P��B��;����-L�������y���/��RB� �*C�M�&�S�l��/�IVn�9�]�q	�a��5�n���E�zj!0U4�[$�����b�y�(�n�+*S�q���RgG� ���ej�c��+i�Kh����C��%k���p��$60��yO��*ap��s:�����zLO8���U�r��F]1���6b�1m�*�o}��<��m6��n[����m��]kX�%XP����_�9�TcG���R�J���tA%�}g9���R�|WC�:z�S�)ghj�U��,>�����a�,�jO���cYK��,RyK�Go����;.�\��7��������-��t��
-r�CGg8�%�d���_�����C��]e,�60��M�� l�xF�������K��O8a������
D�o�i�����%<���@B��l{*4����b�>�E�%��
��
yC]�I�q@g'�������1c]��q���f���_�O��Qe��X������L��eT�OD�����oU1�Gx���u�����Y}n�}7Ia���s��4�<q�&.�'��vt$��&�\S^@%����"�c���eM	m�Y�������!���lm�I���"��frP	�<	�R�|��(�*�R'*��s:��JyR	�Eb���h�D����

!/3�3����C��"��L����Po��I
zq��2�9Tn���b�Qr���,u���
"�+�M'��W�l!��\Q�lQ�G��5�,o���zAm���A�������B@��P�U=V\L�H�����Lw8[	����EZ���R ���G�M���a��@����p~�e�}�^��������j[���������O�"�����d2F�od�����m��O��O���-��T<��s�g��S\(��^9�+=f^g�hb��#��*�/���a���>�H-]1��>��Y���?U����iR���W7������y����8�i�;i���
��g]$�4y�}M�2��j	�=;d�L�H�09pLg���m�~��#�N9�_�m�
�������Ia�����5�x�����iu�Ny�X*���I��D)j��Q����P'%:d�0�q/C���������{������L��X����e��
_����E
��E�F��a0��I�������`��x\�f?_�<���������B�'��&�N��|>]�G�O'�v�����7�
�"���EA�: I����ra�d(��@<\@>�l��Y�3�5��E����i��)����$��'��1�u�<�n�S\��1#����wI�*#TD��[��k�����G�
��i�_�J���0���;c��6(`�6�CI�U|
�O���i"����Y2''}�K����������/%y:�����NF��������KV�'�4��������d���@�.��'����LG�8����ShB��A��U�4���P%������NN��e2u�x��]��-��>A���p�1�QD�n%�Z\+lBJ��,W$X�`3%]���z�$D�Ft�	�����H��H�������F���9B����%Wq��)��CXR��6AC�D�c�9f�vtq`]�~R�p$t)	�?R�sf��D���cD:�� �6�-�D�5*0Z�0��Iv���bW�RI6����P:��
������@9�v��@�n[n���4Pb��wdp+$/&�ii$�59��3b�>��'E�����?�T^9��k�F�����U[S�����N��K��j�S�M���U��ds)��oIjV�}�XY���!,K�
�d*�Q�UF�g=���;7�4�K���$��Z�����'���`��b_�����������\��2�Oi���$i�6��zR��f����C�1�^����[�E����nSzG�u����`���V�N>��g�*��!���&V� %j�U"E�4_�<��0c�Sr\��� d:��=�l�������5J:L�� ��)3b��	%��K"�E�x�,]h�����Kb��Dm�oOz/�����-Y"%�\4���|V[f%�,�0�I�����1F�evgNK(�\N���j�9�`Vc������6�rG�m-��'��=����wq�����6����>�2������>e�x�+����.��
r���uwK�s������W���@��_$��vp���R���d��}�������;*�����'�O2���-#������3�SH��$)����Rr��^9���g�������ry��<<Ew�!�kR||p������q��O1oT����d��Ilt�>�S4�0�ts���D�\1��Q,h���z�3n�aj��(����\�V)��$kH��89��T�O���M2z+V����=.f�6N��C�f%�� ;�)��x��V�R��hvV?���6���3�;�l�C�Fmi���j�����k�$�s?�B��OJ0����E vq��<�J��3'b������w�C�Ez��<�(��5��X��e��r��,j�|��L��;����-�.������O�c�2������h��6#6�{��-���x�����J�;��X�a&���������r�9�j-�M-Na��i	��7�����dv-=����G�H�>��e�+��J�jt�������gY\lYZ<�r�0IV�����M#�Z��4z��B��&��Ga��}Qp4FE j�:2���"#��K��!�����r"�0M,BXh��Z
0�Y=�xGPv��v�<�Yg:8&�[tdns}5�qP0|1�x���
D���t[->DevE�<(6�L	�F[2�s����z:	w�}\�O;���0��"/k�4���X��EfmEr"
�O�r��0�\�#T���r������:Y�6!�'��3�C��,*8���Y�+X>���WY1���Y�E���2������������3�\��7��(��
��i��2�^�
�X��9\��T�%rDB����[���z������x������1��Ls,�VA!�c~����'MH�N
3�X�0��Yq�Y��8o�����l0V��%D���2!��Ds�M�Xgj'3�T���aa�����y��\0�6�}ae�����C-��g&��M1�y��2��l�y��������MQ�)j�_����ec�,	���L�ML����/Ig'8���ZDW�w�Cy�b*M�t�4�_���j�\Ho��VM?"xH�2���~n
'�6@*+\����b����d����,A�V�_k9�q�Y7����K�
������.#�a�B�����?��'2�%�<�:>���TD>��������D�qu9c(O�8����O��C!�s�����_��s�Lnc0�Gj�&�-%j��P/
�
������y�6<FE^�U.2�1���n_�������
$���-<��2{1�D�']�����|	�?<�[r�t8O��2M��Q��9��������u�Nf�"S-��l
�O��@����-��Yu�N>��{���5N3�9yb�(ir�x��&R[S.�qp8>�'�y���jX�B
C����bIhg�<��+I��_�\I�Y�/��m�zIt9�H��t9������R��,�2/�}�
Ab��g�_���a2+��������f-�-�Y�nv�q/r�������)�D�A!�N��r���vT��-�#n�����flDd�Gh��m%� V�]����XY!�� �w|2�J��5C]"� +���`��n8�E�B{��$tj0n������I��kXL��Q�S�����:��VP~n).�n�9���f(E��fD�}z�� �=z
��������E�g��������R�	�S�qJb0P����&6N \�����X�B����89C%�<
�H?��`ej?j��C�>M]������U0�lk�XzE����s����v�ut"�&��.M���J�z�E1x������m���i2�����0c���H9�A��������2�/�U�����d��' 7C���X4;����[��['�g�l����@���jj���{�y�ls���8>��i�u��)i����	-�4VA;���B�N@�Z[�+�����P��x�3Q�R�
���x%�D��2�XR�$|�g���r�1;�A���X�v��g~��UsB���O>���<n1���J�=�(D��&~��i�-���u����\����t�<|,��l7�-2��Y�7����<��R�{�������(k�Z�
#�>x�0���yD�bn�[�����-�Emj^����&�s�2��Z��\�����9���J��L�T�4����8�|#�*KD}`������L��U�Y3~��	"nI)��gYIj��_�$�u�'rd�;��[�	%�&���w�^� �r�'\4�N/s���X���0�nj���RI�`�>

���M��m���U�GJ
��g��'�f*������,�Y���".�B��,e�@"N�oz��}�f���a�����BG�&v$���!�H*13e+�1$��'FH�*���I�B*70g��w0��E���b/�UTd��l+�+��5����?��f�����������~���Y�O�oF�0% ��h��|%]��w�����H��,
�K�~WhJ9T���rIp�n�C�uB"����<:�H�U	����U����0G!�g�)Xu����w:����_��$N
�@�74[�;0"%���9��<��%<���1�A��i��H*�D��[B(9�eb��� �)��x|���q�����(B�KEW������e����I��~�&��(2���x<�	�-%�'��!t|�X2h\Y�`��s�@���� ���c���9��SN��U3�[i.X�@p?���6
�P�0�s�@7�.��
<��&��0�h��<������Rw��p��s���7=�iM')X�.����� �(��� �x�7���T�u��P���1C%1�"��U��on^:cam�^g���sK����������;�=��*����S���v>�)[�B�����B�Z��nnn�������<=���bs����
�wG�������LbM���h���ZK~
_���������n$���BZeJ��$��BLh����:�A��8�r���3��j�X�I��E�b��A������3��c��
y1��*��]zA��r��� �@��h$V�(�QP�p^D�V&�Q6���/�cS�x����J��R�7|<lDGw��w��������o�p\9���O�������c'�`xZUE�1�YwM��NM9h�e
�u��[t;�s�>����F.�P`�(Bp+��j��u3d����
�cf��2��������1���8�A��`���������P)�Y�B{�����-�+�*���o'���e�K�"ml���-(?M�OKy)��$Y�����o����Q����Q?:��@c���oU����1a	�7B�Ll��m3H����Sz��_K)���R�2cqIa4mu��v������N����Uv�Kv��g�Z�PCi�*D\�)��'/0��c�-��<o���?�������"�����r;&
��`AB)��Pr~\p'�i���x�+�w�������������n{{��a�Q�xYao�x~2@�0�b:�%�<�������Y�Yo�K\z��q�LYwUb���"?��%���W%0),�-��0���0�b�Rb��iki���Y)�a%w�{��K��J�\����f;i�i���,��*������E����\��Y�v��R�v�BQ���D%��L��Y%��u�Y��Mj�wru�ni�l�j'��VK�b��M�j�6�+~O��]��B_h]��������hj����<�?D��������8~�>���7	�����.qX���^?�����g��>��f�SJ)pnW���jq���[��N�jW��f�3�Z*`c%��U��Ucg�����>�G�{~���t�����B�5����'�dg������l(���eu��L,�H���C�]�
�t\j���������X*g������&�p1��4�F��������c��,���f�U@&|@���yJ���������FY��x|<�o�-�*/[!���e��`��&��V�����9s�A�[��R��}X)���+f�R������t){�K�����������bF�]�U�v�/$�	��s�Sp�a[����	���)�������2����O����f���XeRU�M������V�Z��^���-���������(���Ak��m�Jt��8z*�^�
���B��HY�y���0h@#LD����`�������U~��1�38�����t4�kz)vge��0�w�������n��M�:{���Ax�
/R	/�����,_D8����9�a���b��"I
�dD����J�X�+�	�{�j�q�.���4�5���W�bT�s�
3�U���V�.
#�!`9TC&����+�����^��K�e��Q�i5:���h[4���.����z�M
���C��L6DJp��Q��kF��&�#
�hV�Gw�|���,�������|7�r�p����=���E��`y��8�`���eKrw[�fnA\�����@�0p�CJU#I�_���K����J��-A��A���]�HD��qd2�nl�i���S����>1��;E�����]�W�+�Jx���v�T��-��"�Z��"�J��^�&�>v�H�~6��g�?�S�������6�G�iJ�rN�B<�L#Md�<L C�����I�e�cWfB����"���.������I�[�$VF����wj���?�}`���Y��28S���cJ�����Q��u���`N2�kHS<��'���g�p�aw2�����I����K�Ju�m��������~��<�
��#�T�:��'�Q@i?WC�9X�=w"����d��F=��8-����/������B�Ck��&��1i�
������V�~���Mz��'[��)���Y��y�<��,����<���$����"zNX������J^Y>� x�����������e��l��g y?M@��������+l-h�0�7�x��a�UK�jE2�E F�/�D�0p����d'�_�b�,�����[��G�0(���6Uh�*-'xQ�YH<�n���FP�����2��Z��p
�����9�/p��4j2����a���'��V�fU�v<���y��oH{r�y������$kf���]4O��
r��K�RO�RIR�E����X����� �w��&x��+�����.Jq������	�����\�K^	�?��A/D::uB���o��N�X�!'���8T(C�*����w���]��Bz�ji{{q���
��	���e��G2������z����Y��
F���t�T>kX$�� ��\(�{��"vS��J�@2U��Ofq���
������Uy��������l�V
;�Jq��K��.��L��m����g�.������g�.���Q��g�{�%Sn���3pS��S�#�Nywq�E+|�?��'�Q��5���
mFBq�Nf-GBI~���R���=T����n��Q���q��0�,i�	�R���]bu�"��p��q.�����u[��E����O��y��%����������ye���K���lp�oL;��j>��+@���$��v:��Z��:���Vw�����n	�����Mm��������~(�rl{vW���:W��m��������@��d���yeTS����QM��+q����95].zE�l/�m�u$3$ghy7�������b�R�������=�����������X�F7��]���D�������w���Y��
�q^9�,�����z���J�T?:���cu8��.��N����� ����k������'%O��k���/�_�bl��A��$���%�e
��������&������������J��-�������+��$���6�+}���_�/���glm%'��h���lQa���7-������c<��F~pJ������C�������^R�e�GJ�F7U�]������f��R��,�_��	������Y��-�otzj!v-A��z�����*�5Z�w��h�a�(1P��<��t�@u��"��)y�TDo
��Z�Q?a-�E�t��j�T���v�P-�{�bo:���J��X1B�B�����D�lp#���(5����t��^�����<�6ZX�%	(�-��W�Z�,��$!�u�g��Q�a��&�����uj�t���n�f���%N�+����8<V��{=��^��Kd:n��1$��eP!|����4;Z���9������p�{[�j�_?�a8�������������~�8�p��1��Q��qXk�^�W[_9������V�����oy���}tY�s�����g�8��Z��E�c���Z������obTD�1��D�����8���� ���"`KF��3���R��hJ	e8_��m����^>��������wZK���V�����N)W!��Kd}*b�����!�bD	�s������^#]�� D�S�������A���������V�]����	
G����Xzg���1������W�12,meq[��������:��\�y��aQ��]��#��M�&���D;!�4���>|��0s��X|�S0{���=���#� ������*��W*J��|�\,o,@��,Oh)�J��E�2�9I����V�|��XS������S�W�+��2������;��t����tmk����fi����y:l��@�'@td�%��J����>6��oOq�h&B���t�O�D�W��.zT�Q����������������'����U�����i!��J��>���'�.�^d�b�~�q=4uJ�������(�@���6�a�BO�7}�����t������.������r#����f�?m���t����UA��Bv_�X)��v�2������B|�ekl��@�[�sX������s��e�:�C�^�v�
mA�]e�%
�������X����#�hG[J{k�hi?5�?�^�#}����	�1���8��l5��v�`�F��r!���}�������r9L�����F�����nT+�;A`Nc�kUYA��O"�OgN���i���[��5����U��5�[�!���[��	��y�!_G��=�!�N�z��6����Jd��H�R-tJ��v�Z����ek���&X�Mi(NP
�����3Ia#�4Y�(iZ�4Re|���i�b�������
R3����|�/=[X������`��c��*JoxR���"Sh)��P`$;��a%�[�!������������]��v���[��!���x�B���F�&�=CE����6*[d���_�]��T��c1��!�N��q��S�����?�";=�X�G�	=��	�=S��A$�xd5�W/�Y�����|q'_\]��������~����U(v6�-o�l>�^C�t��P<O��-���gqg�`~��Je����by�\���)W�)�J�J�/�p_�6�&�N���L.��az�Y����7>eg{w�]-�����U,��bq�T����N��i��*p������(��Ba��/J�������
`$��KT��8������[������� ����LN��6��z	���Ru{Wl�
���	I��������;Q��*��--�$4��h��W�ty�x_�������/����p��aq�I��5��|I��o�_]4�l������6�����/v��xu�`�yP��S^B�d���eN08��r_��6\?��Z�)rF4��@l�������K����\���~�����0��*+��z��\�v�Xob
��
i:fuw�;R�����NF�\�s?���T�
��L(�[�;������tM�AUzi6�r��l���huD_��J!�����B�+��C5#���J���-c��>�<HV�#��8�P�y��m!	&��N�.E/��p�B�JQ���6�o����Z�� wJZw,	 �����0��eD�����1��r������LR2�����;��4�_9����X!��UC�����+,��a7�$Q��U�"9�������e�N���J����8���g�&��"M�}���\/5t��������7������bP��,��aPp�B-�s���9�,N���5h��F�w�0e$��}���BW�0o09s)�b5Ek����m�1�e
z
E,�^�wM��4�<������Q�w����>%5�2�cmFs4�[����n�� �E�`Dd����?�����4
����<[����Q�
4s�H�5����L��S��b��qTw����XF�u�]�?
�?�mD�M������#BS��C$�'��	�����A�{S�0��9�42�Z�����Q�\N��[���81�B6��������O��0�!����������������m?W�S���-��+D{��o�PV���q"�AjGy+��y��pU����L6�%����������i��\�EV�Ib��p�8����M.�E��C�H��U����RYl�_Zy�M�(_,�d��?h�$zni#�Ri�(w'"Ff@T*������9�YCO�,�P�y;�nu��])��{���+�����,���D�4J������Y������J�m@�*�
%���qf6��i.�R1�fZ�����?�FR��3������hPG��Pw��7���0��jD�N��AX����/��bUpB����f����?)5��rf$��iVK�rq�A���P�BQ������<N��$(U��h?�Q�.�����]�*�\�: ����;�as&�6xV������_O�Z��
Z����z��v��n�VY/��{���A6@R��A1�U
�!��h,�P����2��9����
 Q$gb��	�.���RCM��3d��M�G�]c,�#��lO3�����8~�Q ���.��I�L���|tb���N27�&���\�|jMM736R���p\Y~���G��)h#VV�������JC�C"�C+���uLS��8
����o1��|�0�s�D�a��=�_e��Dl�"5����+/������a�q���4�� +b��������X�.��,���
���=���L1���{�O0�t��#4�	�e(��S��1�`g&l�L����+`�?5Rde������~Tb_����8CXU!t7�*E���
���XN����d���
?#$�O�z����
1vy)G��e���p;��
�����+d�L���s�y����D>v�HG�p��(�2�����*�$�"�H\^�2�������c#��6�<����hQ9��t�/R�PR��muux���*�&�T�z=�	Tf�@�r	������Q��F��n�7���
L�h c>)f�_�X��6@����V�������o4�-A&�bu&��Y �xlL������2@r����(�_�y=�*��?���/u�����n�d��K������:�?��O��$����#@�!���7���.������$2�B%���X\�+Q3u�ZN�P[k��[�_�V���=<����u3��|�u
:��u��8 3	v�U�$p��05v�2�����u�=�b�1�D���f"���+lH&�,>��Wi��P�����a�=�KR�5�/��Q ��rGv��q�,F.������O7H�9����v����K%�@$�����O��2*C�|�uQ�4� 0�]j��~Z@_�*�w%Q�Q����d�����]��W�6��v��3����V�Pa|5����@6��U�K�2���"�'��+E����>��3���B���c[�q�f�� R3m$��8�>z:�N��p�V-���b�7E��h�&u��S�L��2�J��'s2����N�ib��3�:��N]�v�\�,&��$��7��f�L%]�2q�
Om����f�����v���u����X�=9;��e�n�~,��9�����D�9T����hF�E"M�P����_�a��}�����K*�
���n)���%!�&P�U#����JJp�&7����d^�������;��n> ,��63�����h���V�������1�����_����x���>�x�Yv����J��R��7G'���&���'����e'�����w���g��`jxC�Z��������������������Y�7i�����
fl�������ZF�.iv�yl@�ylv���q��8yf�Mb���f=�y��>�y���$b��r���?��2��4����_�I�S�_�t��<���RX�:��<�zb��o[G���X_0��k:���DL�����qL��I��S0E�I�:S�y"���	�
o�1EVI��er����U�0��a
�J�G"H�0����)�\c��N�dM\�i�C�U�TN�<�R9��N�����N�\-���b)��
�].���)
M�Q
��+�������8y�(w����+�*^����4�'�
cP��~zT;n��jG�b��y����������}�E�����T�G��}�m��.n�!]l��6��*�'�O
s<�_/{xF���O[	r=��:lf2J2"����L��������!�Hs���_1�5_�|5����|�SukH�S��oX)�*�.M^Z�S��V���O���B��7��������4'�k�����N�;��Y|��T�J9W�&�,D������Q*1�5K���$|���8�+:yx�� ��
J�]���x1�n�
��+�ZD�Y��R�_��*�����
��6�2��N�h�^;��?�z~�B�q=8I(����O��-
�p�j�i����T=����5��N��b'G��b��]cW@L��Q�
�h�,�g���!q���s���g�s�a�it2�c�O��H��jz{�����3��|!?:���6�M9��k�DX����E�6�n��m���Z@����?O��G�5�$��� �A���p�m�1���OP%'����w%W�hI{�����>��`���V��]���v<���F�+�XL�����j�@�J��0K��c94x���4g��Y����e�cKe���5-��J��hl�
j���*�=���Z$�Gi<��[}i�2{~$e�<#��8q�1���`T�(u������&�/���\�9d�Ocp�a���P�g%>q`Oa�~`y�������x!�1�X ���a��B�\�5aW��c�>��B�I����VG�
^!����P���$����.Z�sM������-�`-��7��������+�#F.�R�!���H�Q2�@!9U�g##��	���
�0���A��E��i�I��JNQ�i�-�r/D����%�"���s�h���5������:8����!��of>Cc�P��$f�*�3~��iz���u$j���Yz�r�$�l����y���Y���S"
� ��f�(k�Np2#2<�3�	A�:����Iv'��0f�&awDzV��:�VT��]xi�Y���LM��D�;��I��E�Q���8�@@����q��s��3�3�[�?�ae���AN�>�g��3�2�8P	�Z'g���Nh�2G�Z	-������41��
�:a���PZ�m�$����4fK�USxW������NH�0��U�[2��)��5�J��z�]��a������	�t���"�>0���^
4�}%��?��~A(���+��m�Gx,D��b���A���1���8GhZd��&�������a3i,�B:f��D�^���J�2$GOd$@�P��2��wP1G�B!W,P�%��Jw�M�b��I�67`H��)�5amS��P��/a��y�nKo@��V7���X�X����W��o�eZ$^�(�XpI����Q|�.1�$>�"~�A|�^�����V����������d7q�l�4.������J���s����-���H�w��d�p�g��b'�D
��,f�2�9�$�>��?��?�������&�B^�,�Wb�I��>�>jEO9�g��k�b\Ce�����\�*%��W���������!q�c�6������e����"y���.�+k���d�����6�����my-���4>J�C��9/���f�Jl%V���{�x�Ra���%P&�w�+J����^�
�>W��B,IgEV��
��kF�������R�*^$���!\�\���g�V0H��j�8+R�ed�d��)�!|eu�?��T�GJf�k%<D;��4.&H�W�NwJ_��I}1�$����B��Cz`���1�B���6�8�L��p���.(8��c��^ol9�0o3k����z_���c@���Nl��Hn�8X�,���p�������;��X�H"#(���fx5K��C�Y�p�������3e����L��#

S�W��t���������$}$){T%9VV�����G�8;���b���;������Kp��I��*b�C1(T�Wr�(2��j[j�uU��@�s@�@�<kL����h��9V�&���J�l�)a�����Lbc������7�dx13b�a!x�X���n#	�����gSG����$$c
���J�B4}0Q�f�yO;������W������b3��}��Zp�j�qx�J����,E�p�� �f��Wg��8�W�N4DpWV�"���a�2���`�ZbG�sv����Zbm2<���}J���=H�U�
��ue]�A(���������8�H�������H*���HCRq L��Q�.oVheeV\�K,��rK�m��Ks?bG���>C�q&.�N����Sy;������v�2
b������"'/��|��t	��t{����jg�$�����!<�0v|F^#$�c����mb��qd��_����_�U��+^�����
�mo�a���d��K����|���z%�EL�K�������i#�[��i�����xK9r/O[F��D�+S��B�1�����K�E([�k�p�U�p�E��C�ba[(�C�"�����$�	��5c���Bb�AL�P�DG�%H�����y���,��?e��1�BC�/��!Go%��O��!LY��2:q�I,K�"yQ���g��M�1O����:�t.�|��a��zJ�Wgx2����Y���6e�/�Tx����zC��;v��[c��c�F�Sv����4�z|��3�k���*���������
g���e	IN@�[x���D�r�6�����$d00�l�]�jT�<kJ�����-.c��w�r�������������(]���C����|���������GMh+�����([7����r�����)��;�)����	
d��Q�R��W~��d_���o�����l��s.�l��.A�,�P��(E����hJ�4��������@G�y@V��
t�����76P����#�2@��"���0
�G��6(mZ���]<x{Fl�a���5u�m^o�������r��yj��p���a��T����<�����e�&���g>���)���-�Sh%U����K�[M9��z�9���4����)����P�[Q�y�O���m��r>�MYP��~T�8��cV���6�:�����+8[��@v_�]l��]�.HX��i���<9�`FBN������ Nc jU�K����9.^�z{��OM�&���7x���
�]Q\�s�9������E�97���yUQ����0c|h��u��I=F��~��m��UGk��S:)2Y��Cw����X.�y�
nT�1���E���@&�"cZv&P9�8��EQ;�lg}4����������VG�C�ol{�]���Z����A��.~yW?��M��A���1<���s�DP�7I�����kz�?��qKAC"�Z�k�M*h�'�M�e���o���f]��+.f}��B����F ������&�N)��&$q��X#���}��0{Pa��]<��|��OI)�4����3?F�6}���{�27����n<��0�#V�1�oz�u��V\n�R�����z���J�z+��E�������I�P������q�7�%��IKs���`V�"�P<	;��(��O�Uf���w��J�������~W�YI, �����#��-����%��������L���������"���>�I)g�C��F8�?I#�Y�M�)����)�������d��Ag�E�c���\si���)p�1�*��m���	n&����n\So���)�����uB����'i�-�����8��FH��� ��
���!�<b��S�{������0JZ�-)�R4��F�� $�@�F}�������Hsu�IDJ�����#MY(����U7���XYKA�WYQ���4����6�+���5B��*H�� ���*P�%LUS���K(�a��6~��
�]�o@=��6�8�%����;d�j��j�#J�-�7�PH�F>_9���y�9��	�<�27�������1��(/DS��'�3��X��L�����>ST�+�|�4���Y���Z*N�C�*|(��6���������aiG����5��O�`�5o7,�]���l�s*}6M
m��@�#c���M�\�z�������el�<S6���T�#2���a�����{��2o8�����2�7�C�)g����
����S0J�����2aw��9m���,���'�v��}Ll��02L8Us��i�"�7�y�-_M����3�Di�I���&o%�vl
���]""��m�LG�_V>b��]Yk2SM��6'���V(r��#Yd��
�XL��]1=r��"j�@=�-g���vm�|���%8�4�#�t��>~-���'*3�
�6s��8j>���&�:������I�[a�\��{VD{6��-�&�2�
 "���`��i6A�U�v����Z^o^�7��*��d�E�NY������������dB�M����t�X��;O����@A-���71�c�)�2I���0aj�E]U{�����`���av�5�7��F\��ie�L7/m��y4��7lP}[���|'��v.2k+R[���������D0C���qr7�b
�O���$�q���ZFM��yTa����>���������mE�[C� ��lO�+<������1�7g'�Q����9���k��xO�(�"[d����V��Z6��Z�Z<	.�0�W����.N1��#���d�$An3+�����d�PW+�e���3��HA8e��������)�~M��BF)�AnC�Y0�����H�]��T(fC�-.�q��R�?]9�5�%������iE�[����$�O`�)Dx�f�C�������e1��7�F�����[�5�%�P+�����x��ZX�t��U�+���	$���D�Q�Nt��(<n.f7��92�(�5�`��P�p��(�M���8
|{����J�:WF�l��V���5�-�2\'�2����<�#'x��S��!����n��n�Rt�cb�����bg��Lq��~�;��P�;�U_��[,�t-�\����(��9�������>���+���+����R�U %�X8:T�T�%o�������4(�/���R��d������6�J��KJbQQi��(W�	I�M�����g����U�[yV������y?������m��Q$��+#k��ZwX.������2|�/q�q�$���9K���1��
�reG\�/mAm-�Qj�C.�Ig��+x�����"�D���[��gOH�������F.x,df�ZL��w�}���!���?�Q����!M��r{�'"���^ E
cs�Sl�1:�f���E�z|4�Pt	X��R�pdbob�@B��(n�>�56_���=}f9%l�i�����]�N_���PI?Er����������*A.N�-��
��_R�F1��gKi��"���JMS�G�����l�v����H�Hx��[X��
P-��3PR,�D@�����>��6��k�_����Wk��
p��,	��|���x`�������^}�Z���(cF���89	��F�����
�m3�i�*�-&,R�7�nj�q�P��*�7�Xd\�����|����3�y��$n��)�h+�)m)sH2���*�	��v��<�wI����4�DY��XX�|JNZ�=�M����(���x/��:r�005��/�����c���M�CD$���P��������c
`����3"�(U�v�>����4�qhE�01��h��ms[�������������W���cg��=$�:=�V�Dz�|-K �R�t��R4I�0���S�������7���X�"���,���*�1����'���*�Y�
K�KP�s�W�X.��������A
�o����1��Q_F����d��{`dul�i�}����G����B_�^���1�B1�PM���k<��W9��B2D0
tF�da��u`i`D�4B:��D�pi�>Z���9_���8��i��M�! D^�L3�V6����"���� ~��p����)��������WQ����c�����������t�A0 ��>���,��`�2+�e8]�x�:f���n	���+�P�z�Z�+�j��������&�e&��������yA�����H�5�,�L��8b*�DZ��G���������$�!�5����9��f��g�1�K.qA�������hL���HQ����8T@�����GX8[��P�C{(��I���������d!4ZD�"I���*�U������\J :��R2�d��	w��R2A�2
4�����l����V�m�����V4��)��Fq%t�4�x�������P�� ��I$�?E,3�(��Z�G}��m��D��?�s���%+wx�E.}��n������0�
��8��."��(�`�e
o/�(��`;�n"�7���dTN���=�BaRM��gmL=*Y0vf�.R������a��6�0�!��D@��g�[),�������	���I����9���(:�����/�%2���opCR77yHA���\��m����Be�-�UYX�*���>�����)Kq�p��%���f5�q���������0p�1����CV�l��A��7F(���+yH$���K�R�$���%H���
Q{�`4���(����L{����@�`���2������J�[�=9��~W�<#�*BX�����8	t�`MPL��e��Au�MtzT;n��jG|6�m��-1m�n*K�P��I�Wk��D&s~E�[���N�X���+��5�2j�kM�P\9f-����Ye��MIj����\�^FJ7��k�F�8��AE�g�r�Fa�(GO��3M0�f��$Cq���g9}�Y�=p6;n_���?axp������"��b(�����P�'�~?�
��OR���(��/@���h����|�UU�3w	o��P5#5Z(�I�)���+��+<����RKb,��5���RW��X+�LR�i�A���Z!5^\u�5�.k��M�o7���7�tN����iH��Z�4�j�
�u��@�<y��� >�c�g��o%���)WH�/Yw��w��&]TL����F��X+�&4�e��aJ���sT3i3�S^�������O�0�]������M������In>8�1�1W�m�Q�zv����u*"dM��cD(�&I<<��c�GiO��%�����$�B�o����|��m��D���][]:��W5$Q|���GZX��$�j7X�.����6z�� Y�#�i�7Cj5��M�����R�R��JG����q������z����u�0��r��
�-Qb{T�J���Zj������V�$^Y}���"[��U��f�C���>�d5S�J��������-Ph�U���e
iIf��(ia��2������
��Z�����gL���w"��2k���7._ 0�N~�h����I��v�h����L7��zUD��.%#�>,c!S\����`=����#��)���$�����r�2��K�J�XF:��[{$�����Li��v���y"���m{d7�<���I^���	������+����������P�$�V�n�4

��'w��f���4���d*h�q�h�~�����39o�o�,���7-88�����N~:�53�+��{B����i6=����������<;{�����3�,B��I,#��&2S������S/h�Sx�*�3����o�;x{e�N�\nR<������"+7NV�p_G��i2�\F����"�I�H�Q��D3���y�iy�
n��;Z�7���\���s�}�G��@�V38���6��'A��A�F�
�jx�Ky��c���~>[���#�H=@�������;lj�h4�R��Z���	�.��17D�A��Y$�����j�"R�@�����~������}9$�"��I�z[���I��������I�C=w�
M��L����c�3#��v� �����&0|!;�J��.����}�*�>�>_-���?��z�����P�f7�����������b9_�&z�JVF���Z]Ij�����P�u���y��0	����r��1�3������Wr.0])���9\��g�$���r��[f2N�j�S����������rFY���8��c���c����.a*�
�p'(�;���� ����j��h������/g�K!'5����
y'?!DH��7o.F8�����9�4d�����N��
�VsKr[��2�]D��!l��Y����;z�IBC�iDcl��>zn����M��X�:��?����K8������p/|<�#�q�m�q�m�cw���o �|2 Km����(�bdu����r.��������+���.�e���x����r� 7_�����R��KS�GD���km �-�	���b��/��������p!����w�
�By��&l�M�28�Mw��bs��7���g�����`S�J���>��B�R����v����r�X�K��S�no��+��J��R�/�p�Y-�7A.�_n&���0�������Zv����U)V���J�Z����������v����v�X(����=��HwD��O�%X�Ulf_4�O��%����c�zL��W�������;����!��}��sr���(JQ��*����(�
���<��<�5��By�X�7Qm��G�)=��`op5�
FP!tu�I7��:V$5��SL������U���T�1��F$�t��^f�%���q>��`��,��"}8>��f
�
���6�x�����>�Keh����/x777W��u���U�����OFw���$66��3��'];��,CAh�|��;W����� ������zQ���lfWW��ns��jkk���3��:����m��R�\���wm�R�Tv�{�H�g?Go����'��]�^�����0e�1_Y��m��y����D��GA���������o���������w U���i�m���Q����H�%���V�O�X7���2������8��b�.�[�'m�V�����9�������4Z=�3�����;����Y��G���Yc?��Sw@{f���no����n�����N�k[;�B3�6`��b�ve��
�R�h�Ux7o'q����`����������%4��[;�6��X��::��]4��hV5�q.��_�W����_�X
Ka�������E��V���l���=��n�T��cws����"���t��X(��>����C����--�[)ww�]\�j���{m�P��\�nW����Nq����H[�x����>��{��� ��?�Br�{�v>����K���4��X�r���$e	-�DK�-WW�|o��d7�iBP�4���&<�w2M��14�����������F� ��!�(e���6>��P$��8��67����U*F��m-w�)�W���io$3�����r��-�����N�T�v�	bVB�t��YW��WA��>I�PN��Kp�w�~( ��g`q@!MvV<�?�=��e��g��d�<f+��5�.�R��Pnk�H�Q�������-��������,T�U�.���U��[L�U2�����W��:]�����:nd���Rs*z�zQT1A��� �9s{RmP��������x��o�z�e�i����7;��E�����	k�g�
u\��������E2�2���}���������o��N@�H*=��P2%����jV��<�	�;Mk�����,�VR
��W��
>K9N�~���W�d�����1w'�������2�,���
_����q� C
d8Y��k\$�m�_�bY*)?���x�6�:��^��������
PZ<�[Q;d��gRxa#4d�0���S.)�O�\���j��l�v�s���'��%S'�2k��y E�r9
L$��������E;�P��F�2a����
�
�����7�`	�.��~����` ��Y�������Z�C��K�A�0��2R�r�����@�l��W���H�����xa��%�����JS�tv��b���s�^����g�KSL��&)A�P�8�ItYy��.�^kL�p��Ro�(#��-�'���������o�E���Q=�B�����b�x��� �1�`����k��-�/+R��������+�����8>8���7kG0�~��5��~(W"}�oJj�ae���o[�'����S=����*�xX�uv������Q���E�m=q.}�b&��Q��m]���?�8Tc����)���tT-V�0�2r"DK"j�L��\���|'�_��I6'�O1�a��e���?_�!g���~��M$`/2����/��N����	l���njG�"��9�2M j��X��-`��l��EF�G���������*6O�@?��S<�%��N��q��oh�>e���O��<���
cn����]���V�h��_vr��r�Bqg�B����#�y&,�Ye�.��/-�����H(i,]R7�)�%�-i��~D���W$����vJ�G^���o����C���b�$�p����������8/����;���8B��V�0=��wX��-�g
 �g�g��c�%��
��;h����n�nd��/�o���R*G���Y����+�;�n��.�U+��N���v�.lw��r��[����-X�e�����c[�t���wg���_�MS����Rk��!j�VW�����W*&��`���#����uM�0�~^�-�]4t����0c>7�O��2r~u�'���������-09y��<l:�c����w\��#�7����E|g�
2zj��D��]�S��s�Y�tl����:c�]����?��B��-�����������Z9:i��N��2+E�->(Y��y���\�=C*��v��$h���p;��Sf���{k���J*�o�+���T�VbW�����L��@�g�Pg�l,�buv�y�
����T+S��ZJ��N(�g��v�X��rY*1����\c�s���k�����i��-^��\JA�T��!A��+�a������M��bs�BWy�(��My����n�i��T�?��\OgA��w4N�?2�����g#�K�@$��;>?89����@rqV�g�u~qzzr��f�
��������P	�chZ�h@�8f���~�<=�+�H����s�H�edb�I������d��+m���/40����3��u��v�sN.����(�61������Z
���L<��VA������t7���Tb�hE�H',��2�����W^�S���n��e�)�� 5�<�]������B\@;�'�y��%c�4��M������yq@��-y�}���9w���{Jn��b�7�&r�Gp>�������8l��ec���W-
�E��VYx�(��HM�8�{�= �rE^l�qH!f��������b�����C����0���^������`����3\S1,���|�I�=�%���$��2�����d^��w�` 7��?�2~���W�(��Ud���#�=���Q������F
8�d����n ��������,���r�b�������Bi�\��!�
Mt�B��;e����4�F�
[zQ������!�E���[�=���!��%~���}���<��_����D��1����u(��wz�v>��0�[�J'DT�C>��i38����[55b�����dh{���s�1������L��<��	��p����DF�]�.�#�����t�W���X�����]	OE�b�&���0��=Z����$�"�
�v$�R���[4�TD*��&���N-�
��>v@_�t~�G���eV�e��4��#�j�d�xg���A���#X����;q��Y�.�Y]�j��a�e:!��h�YP�N��-���h�[�KV�K�t�4��8��R1w�X+�p	.�)*�<������S�)��0��I��V�x����&���P��^�$W�=���e(�2&�z��!�ZE����w��!n$��{N������A$
�$F#����������6��	J
���U��J)�V��b �5w�����tb!^����:�O,t~��9��f�����b!9|���K_(�����+�����_�+��ba�j��{��v����t3����v����M�|���0A�������K�D;����|�<���/���1�~�=���d���Q���oX�@�{E��h_�1��q��JN��0�-��m��U�$
�&���(L|�/�/����?�ngB) 6�6�iN�����)��Q�O,�|��t�����n���S����b�R�.[������m��;���w�*�j���_N���>�h)-�:��-�#��_������/����@�|�j�|����7+-S"�8���?Di[�lFj���_�W�N0��`�	��H)���.&\�%\y����;���+�)PkOy)yn����[����R�]�k�w���)�0��R�3�V��?9���Y������fi����`�����^����3���}��������[f��H�U	��0(���(<��U��s?�F&�?���|�������{��~����?b����EP1d�tChZ�"T���������-���
�_���}�������_��M��(5���}�t�8�����agC��A�,��
��$�d�P�TIJ+Wv
�����F4������[���'_ ����S.c����������%L�����}m�����Y�>�VM�HA�+m� �����aW��h��H����*�O�n�4i8��r��������������������r&4G��O��gA^��ik4V�b2��cAlSh����e.�F���g��@��O)B���1R����A8���bvo2�xv~
�����V�z���Z'�������JoQ��p��]��?��|z>u���g��|���){������u��?���W7SM:�)o������-�VX;LV7d�P�0���{ t�'�q��������X��"+���^�cvX�� �Wk:���k���4������Ym�c��.I=���c{5��V
������9QPr��K��q��J����S�a�)=�]�cxgo�PpI�M6p$ �!:#�a*�1FT������-�Z�M�����>�(���>��8���Q�I��}��3�]^AC*��+�6hd�����/�!Y9l@�����f���|��|�����j.elc
/�t(p��7~F]�d�P���;���b�1�`�B���p��p�;]�����9�/�B�b��@1kB���<$�QB
|�����0�Q�#__{�8Ud5�Vx�l��J��h�	�[#i����#NB�z ��D��P��%���E��i�������1�D��b���[U��� ��	�}���TE�&���nC=��P(qE��jD\eQ7v�Q;:�����q���@�ht���Mu
�(��s����;��ap�����`��7������m�F7Z(������t1�/R���W�v��P^������b����>]��zkhZa&�JB����D��|��x��L|��&&<K\:����1��V���M7�T�v��O|����X�v6a�";iuc�z���I��)�����K���s�r�����H���s#������r&r'T�' ����z�N��g����|����1
��.�4,	�h�xf�T�c	��gM���R�s�#�I��#~��������@���{�ic�L$�]'�2�|�����=:&����f�Wf�^r���=����kB���!#����R�������h����#��������B�����k���S���"OS�*��e���
��wp�����A
O��t�3�t����V/�'s<6���y���m�,�Z9����r��5�(�(��7 ����<k[�<�P<<�I�$�a����?E���&qh�8���qi�8��:l0�sM��$���AZgd&EI'(I�V7b�:�\� ��.����a�pRu��?�J3]�'�������B`%\� �%���_�JW�$��t�?�,,'��{rL�����I������~���.gT}��#CW�$���u}���o�!�K��mS��>4��-�q1/��%�;"�������^
�������vi����d����Q�R������mLR��p��j3�ULEo��w.���haL��3D�p0R���M���1Q��}��+���/����_r��6���Q�}�IS>y��	��"�����K�����x��C���<'�2�LeD�:D��y���|l\���u%S;
�����v����$*��1"��D:�X��r8T�'��0�:�y�)���I������{$������r�y��%U$��pj�C�����z�H9����e���v�sy���Mg0�p2�G�T�sA����2��}k��8)�Gm���ab2xvA�^�aR���l�gJM���Dd�8�)��r'(/��%���aC��O#���Q2���Rq;���i�7���CVs���
�[����l��@Yj�:!�
�~�a�L4��U[#w$a[���n�lNx8DG����e�j	so\�	�d����v8��o�R�B����?Jf������~�nu���A�G�����ck���,0}���,�*��jN��
6z�k��7���x��x�k�K�3j�E��6v3�������Vv��]-Uw�V7�����N�.�Vv3ZN1��Q��d�1Z���r�w�����������"�$���7����!i�Gy6Dua#���p�
��h�um���*w8�{2N��K)��F�>���e����~VZu>!���Z3e�1���v��N��q�$o8L�m�(V�E[%�e��;6<d�)*m�C{�=o,q����KMR�G�i�i�wh��0	��K���`h/�}x�Y�� ���J�������b���j��M�:��N����Y��37�19�,��^�(P������(�N�����v�qe��W�W�pWb	�������	�pNr_�^ZB*�J�J��l�i����3V��0�;�����3���>{�;[�����z�������h^5�Q�B��w9�����������+���f��2q�����sJ2�7PQ�N*�ghL���a4y&�b�A���>O���IA��W�_����[�8	����Rn<5�����|��G2����{��O�}5/��E~
�������`�����&������(��;�W�_�u��J�����%o�&qn�@��no��no^����s���E] \��3p[9��gc�eD��@*�	'?!w�F��;���P�q7k�7I�R+��@��_���9��������&���n����]as�~��,�� ��0$L}����K: �`z���:��LP��������Ki}X��$����Bqg��J��d�pC�������	.#���^M�����s%%���H�v{z����dU!��3����}������lT�@N�J���t��x�F��Y��8�
ZS��9�8��E�H�}'F�3���L�����g���"�%���x<"���k3��&�H�(��g�)����$P���70���q��*�
���V�l������Zuio��_�lo�j�hY�q�(�r� �3"m�n��#vl�ET#��8����+P�|��������yv�[����_���-��E�����\L�>>R�#����[��g���
	J�q��������skI^�$��e���%����=sx��Vv�[+}�j1;Ov�X�G.����F�M\�����	���x���h�*�
�������K�sy}��_U6�h-����r����J�Dm�h6�h
��)����������hd��Lb���>��a}��)�0�pJ*J�
�0��![x �1�������O�ID)�dH���`�uu1#�"�N�D��i�r
�S��{��Z��]�^�q�f�u-����`A5jc<��'���-�B�p�h���e�`��a��~8 �����5E^J?S|��JE+�_����k~��K�L���$`7KF�(��:�E8����j�%`$]r��v��e�9��=�%6���p6��Pd��G���<:������:f��{�>`��[�������;�s�
��^u��l\�7|L*�]E��u6sS]U��^7G\,�UV�1����9������t���+��y&���J�E\Br�U��>e��&Fnw�q�_�>KI8�T�h���)�s�E�E��&*@E�p�p��,���������^���M���%�������I�Ci�X%j1Z*|L7�no�����4�*����lo��n�3���]@r����Q�"8�\�� 	��"�kz�,_�P�h����	4�.��"����[�f������{���?biH9+���m�I���N-�:7d��9���?��5�����M�3p�~!s �����Q�����gu��<
*�H���*j(W<y��j�l�[�z�4��p�h�,`��c0��Eb�>����ji�E��t$uG�D"3�M�����@�5F@8=d��F�R��H�V��:��)�E��<O+$��`j��A�f����j���Pa+������s:"�0LKsO���fzz _E�b"�I�����!#�"_���Y�k�~�>pc.�0��
�7�q���S��^�����Z
���O����m�-���K`��_��1�U=����iyWB�)%rr�<`��X�r��V�|a�H�|��C'���X��@���r�>����R{L��Ro<2OQ�����Z�vy���h��-��VN���M)����a?��8!l�6�����v��]��+�pE���'�cqlj����x�Kg���Ku�B���N�H9NoC��Y�{)F����fe�� ��P��5g8��4�`QL�"�9j���:���A��N]������4��Fw��$V�F�>�y�O���P�m���A~3![#�����oY����4OZ��]^��ZF����;�����Q��B!���������`}
+l5S7�Y��{D�_�p��v]���j�G�!,��7��w��9��l8���x4�M�p�Q'X����QF]#��Ho$����!c�+d8���s��C�QM�,�<0Mb���})��[wD��9��=<�@#�9:pR�
k��>�'�e@�b>�d|��"i����w����n�;2Ds���]��7x�9w�
���~t��3��LHC��)�N�H:��*�C"�b�1#�K5�we��^��!�����z�aO�%����m��<�_�Q8'�(�1��|��P�T��4br���hm��[o�=q�A�;�&W����Q~G1
F�8�$;�!	�3����������[V���=�mr@��������l;<�e�����Hm����Y�����/���`h~�b�J�5�*|i0L��
�XU�����d��gF/Fgp/��������^L���ATgd�M��������=L�#-�����6��O�<��[��0fo;_q%��aZ���LT�R��>q<I��cH��f7 &�4�,�4��|Z�D�<B�w ]~9!�x�',
+���2�J�*p�lY�sNc�u�X	�q��|�r>�N��$PG2���4�l��aP�]�'F;�������#_x�����������*���|��$fn���`�C#Z*���^$oVy��	i�!����#����R��w|Z$���D������Z+`���O��d;;7	f��z���}��	,
�K>��gE���������s����kq'����T��|�']}	�������5��������O�E�%]D�'	��jt���*������������W��q����e���ti�91�q���<+�0�����������{\�5-? �fo`/L���]��[��`�.|.������e�N��Y��/rV]*�D�D)���O?��1��P�)H�o�,�9��:��1���
�<����g���O/��/�.��rp��[�/�xNV�	�P������)��e�����Sr	����c|��+!1�,��\�	�I�1�\��	�n������k�������6-����8|&`"����w�8���]�g=�4�����s-�]%#��$�)���f�2g2���` �QaYU��V�`t����7���,�
Y�A�ny\�)�`��V����V��$�cU��������3��y�c����������45��	���`��n������j�#�k�����m+`���#��?c�����P	[����{��t�%��
�1�2��Hhn�
e��G4I`8?&�(�.p���N����e��`�W�~��G�����V`}s}��#qx����-�TVz��r��o�]����5�������5�w���N�~x��+E�?��M������s_�h�y���G������c�~�0W�dBuL����[�k��	$OQ.�uGQ2K�Zv<�D��H�u����tZ��p�rs�-���}`.���(y@$���k��=�3
^N<�L�����P8�5{	��7���`^ze'�&�	y�R�t���o����0q�W����WH������.�co8K���I��-���u4���qC��Y�����X|0}�����AX�<r,�t��_e��q]���
���;�@�h��-�T
�pp�g�o��I8R8�`�}�B����`n5
C�L�	G��i^C���4��=��Z���m1BHjWw�9UDm��H<n�o��2��Vh��l,��H�tQ�u�}��P]{�m>�������R#r4�@Cj ��8��]3{���x����|�J�/|/�HN����n$�*�>����,���
��C4�[�V~�+�B�K��:7��T<5�JO�b���p!m'�GN�=[�f���>%�����
y��:;����kB��Mf��������YtV����dUb,���fC	e�����0�Xfuo�p�����������7���r�q�?}�s����1&�������F�������[���H	��h�����mb�1����l����h76��lo��67���?5Z�Vc�O��X�v?3TY(�����d}T��]�����Hw776����V���^��vk�{��l�ll��6��[WW�����b����jn�Fc��S-X�26���`�N��0j�����t�M���K���_����������
��x�m��6��66�Zc��(_��@��������Tc�y��7h���-)��O?��xb��a�QH�+3ey�)���K�VR������@$�C��O������)��*b`����R��22u���.��fm�V�!��Ng�lr����:B�f�gb��PP�!�L6VyT��h����f���5qq�={��uJe�G$oK
����8��|_,z�t��|8���������~c��O�o����DN���,4uh'k�M��q�q<�c�������#+����J�.l�O��}��|
��$L����jxoe?�	���s��(�Vk�d�<<������fC��Z.5X�A��e?�]3���	�I��5��W[[�z=�m�a��f��IyWK�����0G�6e���-LO�N���a9�YoZV��f���	y��b�@zQz����������������;�����*��6�~/����dX���M2^����'����p��C��G�k^V0��4���������(iRB�q�qO�A:!�D����0������	��V�������&�-Wq���6J0dW�"%|nC�;��y{w�F�B�e���v�����B�@r�����b��3��>/�-x����}����s�T��"4r�9W�;n��7�a�w�Mm��-�/����7h��UT�c��{�V��$j�7�H�dO�!����vv���R��t��`�P��������5��k�V�����:��t�x����7�I��YKN`����
/�k�t	�J�vt��o���[#��m�5��s���������D���W�`����c�KE�Y�br�Y�M"D�J��R�T�&�<�W��:���/��uN�.;o��9;�<:���Qq�������lUe^$!3!����5<ZY������J�J��^H�.
�x����5�
!VP�A2G�R8��/��u2A^�"��Bs���R��N|s���%�[�/=&U�D����B
����m�"*��C?��t���tG�g������_�;h1�Pf����9�����a�4u.���?�D��^Y+�UN��p��sU5=`y��B�pH��������s��_�:f���`�O��o��w��"��-�9���W�#Lh�V�rL��d�������G��e������Z^�F$��:��p"�Y������d�i�XL�c����14����{5��{B���s�)n���kM3�)��6���oF^��H�K]�n$��d�%�����!��a8U����	b���?���
i��C%��R<���,��T��q���bgOS{`~_���g�E+$�+.�����;Cu�����������?��;�0�����\*�^����z|�>�
^rg��Wn����)�{�$#>A�RAa��5�����0�C(X����ZSM����?;�����"p+��
�������\�GpB�
L�*-~���OD�`7P�%�(i�n�|�+�r�������������`����jx���B�;2������8��x���7�ta��-E����(�T^{$N��~'*.eZ8��l�;�6��E��
�}a�����I���d����9a����0��:��������&U�Y J�uIn��W�.FF��l3 ����:z�<�x�7*����'�������7�)���W!���,$�BH7A�������*��:�a�H����w��G�aA��G���*��4-;�`��n]X'p���HD��3��>������6`�4`�GD������BW�t�0�G>�a>��R0�wy{t�?Y~��m@nv�K�5��<�{��45E�1S��x~���^�S� ��e\_�gZ`�������f�63B'�--F6��L��A�E���e���x�+r��,�I���F�-��5,`^�b����K`�����%����+X��^�z��;���_�^L��L&"�����Z�g]��B���$n��Q6��[��w����4~w��#�����.&K�;�IvZ�j��&V�8�����eMW��{��QC�i�y/d���U�i��d�jd
i�~z�~���m�mg�%��}�h�-�v2�@������SYE��Doo��D����S
�gj��	0�YR ��=GX����C�|�`uJ@Dc��6�ag�s�����hm�ZM��v�}��p�w.b����jU9���9����wtrq�h���R�w����Z\n[�4Pp��$q�?�ptjB_�$�fj ����J�Y5��#�E%x��a��o3K���a����tA�p�>4��j
 yd��s��_Zt{X�)*�=�<���`"���Z�E��-��F���J�Zwk)��v1r�<D�>r����z)���sr|���&;
{?��}5n�l�	�J��'������� O�

"����I�w*�h��Er���.S��`���wC�������O?"����`}1
�����mDQ�����zk��k�7��x5���85���~�.)�/s���g��;N�����;���\��v���� r��CG*�s1�PE��(�(3���=��m<�}�����n$>����g���
��+z��ZST�y��v��0��@���jq������9��57���F���g���%��a^�����K}�
��li��h�b���=I�����	��%f@��Ck��W*M`������F��G>,�������|��qJLm��p��+���(��F�/hgE��|�z������0YBC'��>��+1Y9U���F�*�Y[��Q\��/���e*��hz���g���P���^>�}�`Z�Y�N���YE����U��Z0G+���V��=�^5�o��Z�����|�7L���nP���C5O�	����;���r����#�$,tya����r��JT=�Y�f��cU������x�����������X|t�zE���o����m�6j�] f[�;����S1���yXXJJ�+�8�l���G���N-���O�,�]hU������0���wl�NW-.�VtT����������b���%7�\D@3��#M����Xj/���*�;U�<X'|�?j��H�~+�Q�y��qu	4
��A�A���
��4��'��#���F'�������=v4��aL�������>�<Xv!��ug��d(�:Lwp�h��	�PM������rG��Z-�x��+��EWu=�Q?cUE��������V^t,�w.�!��`��#�7�.�'+$����f�x���+�����y<z�pZ_�4��Y�IDo��}g�h@~��l�;@�xu�n%����~\�t���ii�s�a�:���������8������������I�<��m��v��hz���n*M���^��<+i�,��^
;�rcq�t��������Qoww��j�wj�����t[�B��_�)p�i��s��d�aS�����=�;p��M���s����\�4��M������c{��p��������]r���,���g�<O9r�r�\�'�gY����\��\�z���f��:{���Ck����S���9��M=	04��8~w������[��?=>���m5����F���G��j47�(������TA���0�h��q�Vs:
�(�=y���k4�9����s� #-��>��������o�����*G�<�3�Z)�����;�[5tL��4�=W(�d�X����4�w�� ��[r������|0�Yc��4@,�y��v���Ls���j�:x�������%��M�X�E������*�_HU�#�$U�_��(KwzA���c�8��d4���t�����5Gy]J�l��h{�q@�C��������3�V��N�5��'q�d���%vVY�.n)`�-�����I�������A��"��pa�fItM�2]����i�:;������[5:gq��b�`4�vO����q��c�|\���9f����N;�2A�&�Z�D�:�P(��K��UH!�#��x���t��#,�yrt������{D��1�����&7
�M���h��'ry��Vi�r	]���q���Bx� �c���Q�i��)�����5����c�d����3�X.s����h���3��w5�,Fq����d�\��)���?\��j�hX�Lc.��%���_��{y>���2�r�����w��IXII�������2:'�V�1�9�)!/&,���[ynVq4^|:��d� s�Hi������������IK�L��4��e�E�>�wp���2#�����q�'~� "-��"r�#�
QC��l�RnEk��m���iD�a�q��B��t[s�l��1W��x��P�����F�a��o�R�}&��q�`�}G�:x�tQ��W��Y@KG����b���&��*�zOa^S�{=@��#�b��BQ"�����C����,�I��:����S�����M�\��@�(G)��{z���?��(����P��[q8w��&G��������FZOZ���Y6G�!�-�oe��V����,|�7q�m�?����x���\�tg������z|X4�N�I���Q�2�1IV���-�I��jh%@�|6�7Hg@��E�yEP1�2QVk�0_�O�d����}����Q
�	Y���;Y�x�����Z���@Fr�p����z�a�bq��&P<O��UVk'�i2F��{��_@�wdZ�������mV��G��l8e1���[��j����Q����=-QL�L�j�-#G=����-��="�k�l��|����;u���7�+@_L����������d8��8
�O�����=6��`�5��Kz�3w��	��C�P����k���
��u�k�	��1O&#*w��Y<�u�
fKAy�@}��+k�J
���v��Du)6V�`1"k��xu��v��2��W��i{����w(�H2R��n^bw<M��Q�f���qb#;j��������U44���J�������RWw< �����F��ml������~������?�i��?�y�w��_�}�\F����������u�;N�hW�+�M�y�hz��wE���|/��5����Jdqu�O?m��Bh��������gl������#����E����7���A	������9I�;�q������;?�{�S������������i�����lnn�3�?���S��/����;�`��ol7w��v�j'���j��`k���n�76z[���C����A6�6�g����E�?wM�O������#
G���)�) �'��i�X]�2|SU��H^)S+:O�}[JA #���>���-&m+��G���h��u�����!�G!Q��,_�K5�����\]sf�	F�\u�@�-�lVw�.����u""\�F���dV��
mW�_�����$�<����6��(u�lo�x60m+�p�yX��t��+���NM�	|�$J1{�\��z,Va�swXa
��ud��muu���Z��[�<�L�j*�K�t���u�������|Wr{���i|�F�i=�RA9�g��K|
�U���vD�<Z��0�jf�u6�;��(�������zei�A:���0��Ma���My��Hf>a����4�
���t�s��Z��?���mN�����_k���|=�������#���u�ZO&����i��o�Q���Q.;�LH����@hy���m���N�%J�d*C�����'�f
{w5�S��Gc���M��I8g.�'����g\����c!MEp��s�/�n�4A��<\�yv���m���P"����;�b;�0����N��v�Kd��m��Vk��mt{����v���'/��n[�\A��������C�3'c�
3��L\�|r$A������{f���f����S�.���~�Z|�$��IK���C�*gcm�~����6f
h���_"�ka��;R���5�������u�����-���VC2����K6���x��
:~�x�7�mr��J2]?4j�D8�d��m���U�����}>�ZW\��su�$W7f�a�bv4L��]�]&�������4WkY\�v@��B�;'C��W��&�$���Cu�r�Z5��[�/��>���[(1[���2�-��w��r��Z�v�c�cv�a��\�E����3��%�Q2�������MQ���3�L#/M��<K�^/HIdC�F�ZA���\[���+8O��e�v�/(_��@�%I�]��U>|��aZH��C��J�n�9������\Kk!To�!h�a����)NS&-�K�����������x��>�c�i,�?u��sA��sN f����|���@�L�Dy��\;F�UN��G;�'���C1`��FqV
g�����.M6E�t
�������\��?����h����g�8�,i�����t�q���>y[���sLE���������`#���
�v��n7^��uB���Fk/��������V�\>��<�Z����Z��d�#����UO�a,Co���h�:�9����0
�}���^��s����s������_g���}�6��L���yQ�P�_V��B�?2��9�s�s�X�Z�73�S+J=�)?S/]|�l�$^�Z�}i77��#I����+<b���Z�?lXK���9��'�+Ho���a|���m�	�L�
7>9WTT�6K�[i�a[�����nW4$hFM��?0�����Jov&=��%�F	���K���`�U����9�1IK%z���������x4�Mm�+P��5+]u(����l�}���5���bf)��4iv1�%	�H��_8I ������&��hD�mc���2>�FJ�	g���*�u���m'��#x0�pa�9��M��n:7��U4�.�U�E!>���<�0�	��vj*|�Q`P2�n�%C�C�@Q��k�Dz?Lgc�C��M<�����UK�W����^p��$��`�������>B�K���^4���vo#z�������p8m��5��?�z�:=��sxtr��9:=��Vg��|^������%��y����hrsD�&��r��
]�o����XBZ8 n�P���!�
j�k��'���$8\�k�Io�D��������^��?5�x|�{��L.`	R;A�0g�v�2�\L�����i�V�\W���`
W�w05�O��k��v@������[����&r��j#<r��Pk��?���e4e�����4QWG�G�9���]j����j�#	��y���b�/$E5~��;R���o�Q�+E=�������R]Z�.P����E/��-1������W�&�	z�q�����7�k����A�-`��sd�����$I`\�Y�wlKo\k�G�=2q,�>I��h������Cj!����)�(�>'��N��=S,�8����m�iJ�y~������)�s�w|9�L[��^I���`����	�Iw��S��E���8�G���_�b�&�+��x4��{��&�i7�2�u���i�#MMs���],�
�&e��x�E���}�'��"W�Lp��\��IyHN��sVC�R��z����s,D�����v\9x��8X��������4(������x<��R�g��;M�W�[]%v�$T�D�����n1���V�,F��k����
�at�Z�tG~"3��q��P�����gS�z��4��'�o5�:e_�r+C?6����V���:��hml{~P�tHu�i�w.V���!�����?y���q��Jp#�4�s�@n�}"�u���9c*M��&q�4�s��a�����T�V2�=�"��:^,������c ��j6�ILpg�\"��������#�,gmRY�v���r��D�?�����pT���1-��&�1�L�����6
j��f����aw�f:�$������eP����������_�wS��3���\aP=���N�������.��>-�?�t�t�5Mp���N�.)�P/�y���p��������v���AK�p��7�����^�P�G%���(��$��vc����$_�r��Y�C<���"^#��>I�d��Knu�c-���8����i�P1��S���j
K��~G��_���r��r�e��H�e���m�������m�\\�aqd��.Q�RiU�.��`i5��8`���J�6*'(^\��L�������:U���������Z�>�3dG� �����?�����������]\�\v����c����P�NVi�^�����p����b4Q�d@��rlN����2�.��U�����@MY�<gO��D���|� =�����5�XOI
J]z�S;y�Q�����3��k��96�X@9~$
���R�=)���6k��;��"�.�a������i�P�nm�@���`Pl���r�F���O���b���K������'�����}���;�h���a\�S<�=��3#���-6
��)���^K{�bU�J�
)�b���Un�D�|Q*�~������������(����KQ���z0��F�&��xr�7�8�\
��VB��D����t��<�`�I�����}���$.����$��$����\^��j������(��#)I������1�P>��s	����t��'@�C}+rUjzo���Dfe:�3��tKr��Mn���Ab����>6��H�&)]D��I�����l�F�~�z��B+�N�%��$�����h�y��BXN��
�WP%���;�N����U���5��
B��S������p���H\`��.�L�o�QUa�_T�z�o��}��������-y���3�B����~���p������S�]���
\+{gWrt|���R>���z{
[L����F��'W�@p^"<�YW��U����R�
`��O�����|�����Z�4%����-�����F,���>��<���bR�B�J;����E��<������� ����_��3���6��N���r�}��H<q���0#x<��\u'�8���	��F���h�����:�
��~q�:��]��r�Y?�<�j���K�u��E��Ap������h
K3�?�tb��}>UiqTFTZ���9Q@\K�x�=  ���!
��[�
���n��6�9�'S�J�}`�A� �w'1����_(G���d�Km�*2��PW��^��f��J��w0c�������k-�V���	�KT*d�5i���jU�{����E�l�o����R�=����{���&���
�SN,�%W����U%�\���������\����?"�j.�u�^�1��Yl"���m#��s�6&�/v���	���u�F�@�BJ��p�W�k���R�+�j��s���?�y�]��
�p,�f��v�Jj!~
�A4W�!0=Kmf����$Y2�i�q/(���[����cY�������U4�E#�U��C�R�I��.�p8�C�i��A�� <�����y���d�� W,'�O�N����@F���N$�
��/\�DJ5�$T�c��[�QQ#:y�q�^N��.U�n�}4���[@����,I�8�

���=��y8nn�F��$�:#�.An:�����,���8�|h+Z���XW�bXmt�r�sA�����l�%��T�]��T���YJg���?J�4�0me�X�9q_8���1��6� +�\��^�\X=/���gAD|���`mJP�Y��(]dl�������Z�"�j�e�o�bpK���q���*�Jr:��H��]��u�?L�-�}�B�^3B�d����z�c�W�:��#�K����./:Z������h��J����0��$2��Af��n/xz������d���W[����`N.���&����.B51���&������v0��	s;�����Xe�$�3xZ	+bq���o9������NG�e=�+TqB�x�����a��/M[���C�>9DM-�����������3���$7�'��Hp���#�l�g#��A
�uR^�����z2�	Jp�R���J����]��Y��K���y������`�}|E����������
���
#2��J�j��@6tM��:��K���vnLj�0�J4ja���0����KN�M\x�[���kY)���8�w�[y�5�����d������
�����X��O���[Y���9t����x�.��EL�K^@��?�9�E~���|�f?�_��A+��NUI�&���k�n��`@I�����f��=d��4���|h]��l����j��a�����sAl0�igM{�\S14j-7����g���&@��C##��EAN|N"����&#��p��_��N����K���\�e���^m
�9&�Z��e��
��;�~N����R��������b�]ab�aI�+J��F�gX!55B��
C��I�@���Mx���G=�pLI�l"�t�]<1����U�������#�4�KyZ8�*2����!M9������9��WY��U6a��N�q��zr�����h���yz��&���"��"K(�$!���^6������b�_����D������GnY������H�������T����`q������sr��Rj����y>ddC#��������?,�����^2.;�������HW�s�
K�'�$�S��0���X��S_�-����;�v���D����X:Ym�,�a].P�UE�X�U�6��9��Vc���n��K~�Q�#��S�x��h���
]��IaK�R��~_D[����PD��P��Av�!��
1"��@������]p�8Q?a/��C��;�����N:
P<�J�ChcuH9jt�&��|����z����!�T�{��	X��`��&g��uK�����bv6a�-�g���#M�O$wCYk�2I_�������������!7h~�|�`@F���WZ�@��������0�4���m�/�%���%	C�:����}A��p�C���K��5�JuS��u�j$v��b/a�8�"q��'h�bt�f)�������=�"�,	�z-K1��X@�k��NV3����=���2�0X���������m�#�n�js4��{�A*���Z��~���=���26B��09�0_O��?3Y��*�$��G����+�2,������^u���Xc
_�VT���ka��f���(�u��\�h�4�Z'>���#~�����,'��Z��c��KX�+�M�c�nR�Wm��@��>�S�,�o3]c.F��|�,R�z��H@���H"�<�����.&U�K�x�`��,���<�t����������V��/��{L��)���a�Q�}}�����jIP�W&�-������|�f i\�<��o�Bj��Z�nL$(m
�HHu	C�3������:I#c����d����N�JT���$Y:�R9�+'�2��$I���L�Y���)����=bk����\[IE3�x`����5O������
1��������3a�o0�?T:N~S��w��d���	Mg5��8�����\����:#zZq��9�r��7�,��c�]��S�3(�p�@)�<�qV��r��cq�l�0��?C�
������1H���Z6��w=P	y�����]Z4�A���s/�M�d���q�����@�`{�s~�M�����5~^`.�S~SY�
�3*�S�U����z�E���0]�����Ki#������������zg]lx�e�?���.�����8����-Z
H��.�6�P&�Y��Z<��m#������`�K�����+Nh�1�DS������q}s4l������>\��A�����Y��X����URs6���W�����6��j�� ���`,Y^�`������=�E>K�%Q<����"����\�h��m����PS����Ug�j��"C��h�C]���/�KN��'XfcT�yS���=�s���l�M0���p5�
XX�;�.��U:������U���9��y�"|�<���������������	�a�n5LX����������H���F��X�*���-�� qCp6U���(R�	�'�K������*P8���Xr�K�-�}�&��N�r"�Ts���m�~-d��U�nEwS��~�d�q�f2oe�~X��k)�z,Y����h�.��]�mC$��v����#J,
�������w*�cOx�E��_�d�����~����!KB�'T4}i��D�������	�B���d6������8-O��/�����9���4�t�4~7�s��E{��E�1e@�8������^t�v���&��M�A������������8���r��M�2��b)5<�3�����k���}�I|��t��
#(]3�L��^�����%�X�uk���8�J�A�7n'M�?]O�����r4�����V#�k0����RB�\�s�U��~4�n�2�$eM�6{���i������M�+������U��al���;�o�xk������ftN��������`��@���#�[�����=V5;�Oy�-����s3��\��f-B�G-P4�_uZag�8����Xq�m������BX�G^�dd^�U��j1;wE,oY<��cN�f���@�O|���n�����Q�3I�<�&f��zJ.*���mm����ByyZ��k�A@6�OTN��C,y������bv��8u�	Z��F���5v��z��A�#o��M�#��� aSUy�R=er;aT���4[S���������^����\���#�E���J=<^��tK�B�$��e����TIJ�&H�	-6i�������X�)?���������e�[���n�t���X|���P"1\Z
��&���
��P6�S�����/]V&�n�uG�r�W-~d(HmU���pS����g�3�L�x�gr2�uF���%R�hl�j��
��P9��t���\����H����T�%;�m�m��ZZ`wi���me������tC�5/�h�
�d��_Z5f�i�Z&�6�:0�,�,��6�>K���C�T'�G�)~�T�=��]�)f��`q��8�U�Z�0��������W�7�8�X=��B�
��[@GB��K ���|�wWA�o���
�`hy���G++�������?!c
�*��.$�p@ �B���]0�?a���M��H��6�-��<&��BA<���������r�~��C��6P'�`+�h��	�;e�0�����'N����n�QA�Q�Oz�%b�G!<��{�����<���\�����
&�b����
K2��d��T�U{��P�ta��>h3���(D�nX4:d,�xH#v�C�N�C���!�E�D�uS���(���kWRe��u����n8�%C|�{�]�LvU���e�X*R��)f�PHC�.i�X[1�;��~A~X�*��
]��}R��K��$&���D:{����D�{'��&�O���u^�\`���!��_��T����'�K��@{����:5�R���0���+����k� u�QR�J�r�"�p�����������K��&�J�(u#�	��:E�4�F�ys�?8�j<@����l�I��S�SRCD"�0�	�����bA=p
�@�d��n9�}A���P��Ve���W#�7��kk�Q�P������F��i-�lN`Dlj�4z���\��hUV<WS���"�wgz��5TM�T\�&6�vcn�sL�.���;��.��n%Q��P�����r������5 �a�wx�Y��0�C=;�8���^q�M	����T��8hq=uO��(���3.M^�/���X�����:c��hr�yF�������-��ptXW��U2��^>�$>]{W�1����8V����/��`���5��[�u�����2�5t����)y,H��/���%���i`��Y������E
��=LEj�_��k���bd��Ll��������:>�<�iV��E�=���NU�w���d�����^i�"w���l	Z����3Y���	�(�q[�O���2��i��n�+�BU��%O����Z	�2A��y���nC^_��N��#U�jv�U�.\/X^�������]i2OQ/X��
�����U��s���/.n��7�pXG��^N��Ae����wxiy{z��~[
�p���(�����n#��?_7its����usF3�/U���J
��m�j����l�
2�gV�H��[t���t!�g����|(��7�|����b�����P�����YM��aO�a8�Qci6m	.m>�����[�9lZ�K�6��n�E)��|�1��e
�$�<I*�6�J?���r�r���Q�aZ���c�B����a'5[�
E����&uX?M��~z�BMe�GlbI��'��������)9����r����7��?=}�>O��������y�<}�>O��������y�<}�>O��������y�<}�>O������po�
#141Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#140)
Re: Implementing Incremental View Maintenance

Hi,

I have reviewed the past discussions in this thread on IVM implementation
of the proposed patch[1]https://wiki.postgresql.org/wiki/Incremental_View_Maintenance, and summarized it as following . We would appreciate
any comments or suggestions on the patch as regard of them.

* Aggregate support

The current patch supports several built-in aggregates, that is, count, sum,
avg, min, and max. Other built-in aggregates or user-defined aggregates are
not supported.

Aggregates in a materialized view definition is checked if this is supported
using OIDs of aggregate function. For this end, Gen_fmgrtab.pl is changed to
output aggregate function's OIDs to fmgroids.h
(by 0006-Change-Gen_fmgrtab.pl-to-output-aggregate-function-s.patch).
The logic for each aggregate function to update aggregate values in
materialized views is enbedded in a trigger function.

There was another option in the past discussion. That is, we could add one
or more new attribute to pg_aggregate which provides information about if
each aggregate function supports IVM and its logic[2]/messages/by-id/20191129173328.e5a0e9f81e369a3769c4fd0c@sraoss.co.jp. If we have a mechanism
to support IVM in pg_aggregate, we may use more general aggregate functions
including user-defined aggregate in materialized views for IVM.

For example, the current pg_aggregate has aggcombinefn attribute for
supporting partial aggregation. Maybe we could use combine functions to
calculate new aggregate values in materialized views when tuples are
inserted into a base table. However, in the context of IVM, we also need
other function used when tuples are deleted from a base table, so we can not
use partial aggregation for IVM in the current implementation.

Maybe, we could support the deletion case by adding a new support function,
say, "inverse combine function". The "inverse combine function" would take
aggregate value in a materialized view and aggregate value calculated from a
delta of view, and produces the new aggregate value which equals the result
after tuples in a base table are deleted.

However, we don't have concrete plan for the new design of pg_aggregate.
In addition, even if make a new support function in pg_aggregate for IVM,
we can't use this in the current IVM code because our code uses SQL via SPI
in order to update a materialized view and we can't call "internal" type
function directly in SQL.

For these reasons, in the current patch, we decided to left supporting
general aggregates to the next version for simplicity, so the current patch
supports only some built-in aggregates and checks if they can be used in IVM
by their OIDs.

* Hidden columns

For supporting aggregates, DISTINCT, and EXISTS, the current implementation
automatically create hidden columns whose name starts with "__ivm_" in
materialized views.

The columns starting with "__ivm_" are hidden, so when "SELECT * FROM ..." is
issued to a materialized view, these are invisible for users. Users can not
use such name as a user column in materialized views with IVM support.

As for how to make internal columns invisible to SELECT *, previously there
have been discussions about doing that using a new flag in pg_attribute[3]/messages/by-id/CAEepm=3ZHh=p0nEEnVbs1Dig_UShPzHUcMNAqvDQUgYgcDo-pA@mail.gmail.com.
However, the discussion is no longer active. So, we decided to use column
name for checking if this is special or not in our IVM implementation
for now.

* TRUNCATE support

Currently, TRUNCATE on base tables are not supported. When TRUNCATE command
is executed on a base table, it is ignored and nothing occurs on materialized
views.

There are another options as followings:

- Raise an error or warning when a base table is TRUNCATEed.
- Make the view non-scannable (like REFRESH WITH NO DATA)
- Update the view in any way. It would be easy for inner joins
or aggregate views, but there is some difficult with outer joins.

Which is the best way? Should we support TRUNCATE in the first version?
Any suggestions would be greatly appreciated.

[1]: https://wiki.postgresql.org/wiki/Incremental_View_Maintenance
[2]: /messages/by-id/20191129173328.e5a0e9f81e369a3769c4fd0c@sraoss.co.jp
[3]: /messages/by-id/CAEepm=3ZHh=p0nEEnVbs1Dig_UShPzHUcMNAqvDQUgYgcDo-pA@mail.gmail.com

Regard,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#142Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo NAGATA (#141)
Re: Implementing Incremental View Maintenance

* Aggregate support

The current patch supports several built-in aggregates, that is, count, sum,
avg, min, and max. Other built-in aggregates or user-defined aggregates are
not supported.

Aggregates in a materialized view definition is checked if this is supported
using OIDs of aggregate function. For this end, Gen_fmgrtab.pl is changed to
output aggregate function's OIDs to fmgroids.h
(by 0006-Change-Gen_fmgrtab.pl-to-output-aggregate-function-s.patch).
The logic for each aggregate function to update aggregate values in
materialized views is enbedded in a trigger function.

There was another option in the past discussion. That is, we could add one
or more new attribute to pg_aggregate which provides information about if
each aggregate function supports IVM and its logic[2]. If we have a mechanism
to support IVM in pg_aggregate, we may use more general aggregate functions
including user-defined aggregate in materialized views for IVM.

For example, the current pg_aggregate has aggcombinefn attribute for
supporting partial aggregation. Maybe we could use combine functions to
calculate new aggregate values in materialized views when tuples are
inserted into a base table. However, in the context of IVM, we also need
other function used when tuples are deleted from a base table, so we can not
use partial aggregation for IVM in the current implementation.

Maybe, we could support the deletion case by adding a new support function,
say, "inverse combine function". The "inverse combine function" would take
aggregate value in a materialized view and aggregate value calculated from a
delta of view, and produces the new aggregate value which equals the result
after tuples in a base table are deleted.

However, we don't have concrete plan for the new design of pg_aggregate.
In addition, even if make a new support function in pg_aggregate for IVM,
we can't use this in the current IVM code because our code uses SQL via SPI
in order to update a materialized view and we can't call "internal" type
function directly in SQL.

For these reasons, in the current patch, we decided to left supporting
general aggregates to the next version for simplicity, so the current patch
supports only some built-in aggregates and checks if they can be used in IVM
by their OIDs.

Current patch for IVM is already large. I think implementing above
will make the patch size even larger, which makes reviewer's work
difficult. So I personally think we should commit the patch as it is,
then enhance IVM to support user defined and other aggregates in later
version of PostgreSQL.

However, if supporting user defined and other aggregates is quite
important for certain users, then we should rethink about this. It
will be nice if we could know how high such demand is.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#143Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Adam Brusselback (#3)
Re: Implementing Incremental View Maintenance

Hi Adam Brusselback,

On Mon, 31 Dec 2018 11:20:11 -0500
Adam Brusselback <adambrusselback@gmail.com> wrote:

Hi all, just wanted to say I am very happy to see progress made on this,
my codebase has multiple "materialized tables" which are maintained with
statement triggers (transition tables) and custom functions. They are ugly
and a pain to maintain, but they work because I have no other
solution...for now at least.

We are want to find sutable use cases of the IVM patch being discussed in this
thread, and I remembered your post that said you used statement triggers and
custom functions. We hope the patch will help you.

The patch implements IVM of immediate, that is, eager approach. Materialized
views are updated immediately when its base tables are modified. While the view
is always up-to-date, there is a overhead on base table modification.

We would appreciate it if you could tell us what your use cases of materialized
view is and whether our implementation suits your needs or not.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#144Adam Brusselback
adambrusselback@gmail.com
In reply to: Yugo NAGATA (#143)
Re: Implementing Incremental View Maintenance

Hey there Yugo,
I've asked a coworker to prepare a self contained example that encapsulates
our multiple use cases.

The immediate/eager approach is exactly what we need, as within the same
transaction we have statements that can cause one of those "materialized
tables" to be updated, and then sometimes have the need to query that
"materialized table" in a subsequent statement and need to see the changes
reflected.

As soon as my coworker gets that example built up I'll send a followup with
it attached.
Thank you,
Adam Brusselback

#145Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Adam Brusselback (#144)
Re: Implementing Incremental View Maintenance

Hi Adam,

On Thu, 22 Oct 2020 10:07:29 -0400
Adam Brusselback <adambrusselback@gmail.com> wrote:

Hey there Yugo,
I've asked a coworker to prepare a self contained example that encapsulates
our multiple use cases.

Thank you very much!

The immediate/eager approach is exactly what we need, as within the same
transaction we have statements that can cause one of those "materialized
tables" to be updated, and then sometimes have the need to query that
"materialized table" in a subsequent statement and need to see the changes
reflected.

The proposed patch provides the exact this feature and I think this will meet
your needs.

As soon as my coworker gets that example built up I'll send a followup with
it attached.

Great! We are looking forward to it.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#146Adam Brusselback
adambrusselback@gmail.com
In reply to: Yugo NAGATA (#145)
2 attachment(s)
Re: Implementing Incremental View Maintenance

That was a good bit more work to get ready than I expected. It's broken
into two scripts, one to create the schema, the other to load data and
containing a couple check queries to ensure things are working properly
(checking the materialized tables against a regular view for accuracy).

The first test case is to give us a definitive result on what "agreed
pricing" is in effect at a point in time based on a product hierarchy
our customers setup, and allow pricing to be set on nodes in that
hierarchy, as well as specific products (with an order of precedence).
The second test case maintains some aggregated amounts / counts / boolean
logic at an "invoice" level for all the detail lines which make up that
invoice.

Both of these are real-world use cases which were simplified a bit to make
them easier to understand. We have other use cases as well, but with how
much time this took to prepare i'll keep it at this for now.
If you need anything clarified or have any issues, just let me know.

On Fri, Oct 23, 2020 at 3:58 AM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Show quoted text

Hi Adam,

On Thu, 22 Oct 2020 10:07:29 -0400
Adam Brusselback <adambrusselback@gmail.com> wrote:

Hey there Yugo,
I've asked a coworker to prepare a self contained example that

encapsulates

our multiple use cases.

Thank you very much!

The immediate/eager approach is exactly what we need, as within the same
transaction we have statements that can cause one of those "materialized
tables" to be updated, and then sometimes have the need to query that
"materialized table" in a subsequent statement and need to see the

changes

reflected.

The proposed patch provides the exact this feature and I think this will
meet
your needs.

As soon as my coworker gets that example built up I'll send a followup

with

it attached.

Great! We are looking forward to it.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

02_materialized_test_data.sqlapplication/octet-stream; name=02_materialized_test_data.sqlDownload
01_materialized_test_schema.sqlapplication/octet-stream; name=01_materialized_test_schema.sqlDownload
#147Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#140)
Re: Implementing Incremental View Maintenance

Hi Anastasia Lubennikova,

I am writing this to you because I would like to ask the commitfest
manager something.

The status of the patch was changed to "Waiting on Author" from
"Ready for Committer" at the beginning of this montfor the reason
that rebase was necessary. Now I updated the patch, so can I change
the status back to "Ready for Committer"?

Regards,
Yugo Nagata

On Mon, 5 Oct 2020 18:16:18 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi,

Attached is the rebased patch (v18) to add support for Incremental
Materialized View Maintenance (IVM). It is able to be applied to
current latest master branch.

--
Yugo NAGATA <nagata@sraoss.co.jp>

#148Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Adam Brusselback (#146)
Re: Implementing Incremental View Maintenance

On Tue, 27 Oct 2020 12:14:52 -0400
Adam Brusselback <adambrusselback@gmail.com> wrote:

That was a good bit more work to get ready than I expected. It's broken
into two scripts, one to create the schema, the other to load data and
containing a couple check queries to ensure things are working properly
(checking the materialized tables against a regular view for accuracy).

Thank you very much! I am really grateful.

The first test case is to give us a definitive result on what "agreed
pricing" is in effect at a point in time based on a product hierarchy
our customers setup, and allow pricing to be set on nodes in that
hierarchy, as well as specific products (with an order of precedence).
The second test case maintains some aggregated amounts / counts / boolean
logic at an "invoice" level for all the detail lines which make up that
invoice.

Both of these are real-world use cases which were simplified a bit to make
them easier to understand. We have other use cases as well, but with how
much time this took to prepare i'll keep it at this for now.
If you need anything clarified or have any issues, just let me know.

Although I have not look into it in details yet, in my understanding, it seems
that materialized views are used to show "pricing" or "invoice" information before
the order is confirmed, that is, before the transaction is committed. Definitely,
these will be use cases where immediate view maintenance is useful.

I am happy because I found concrete use cases of immediate IVM. However,
unfortunately, the view definitions in your cases are complex, and the current
implementation of the patch doesn't support it. We would like to improve the
feature in future so that more complex views could benefit from IVM.

Regards,
Yugo Nagata

On Fri, Oct 23, 2020 at 3:58 AM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi Adam,

On Thu, 22 Oct 2020 10:07:29 -0400
Adam Brusselback <adambrusselback@gmail.com> wrote:

Hey there Yugo,
I've asked a coworker to prepare a self contained example that

encapsulates

our multiple use cases.

Thank you very much!

The immediate/eager approach is exactly what we need, as within the same
transaction we have statements that can cause one of those "materialized
tables" to be updated, and then sometimes have the need to query that
"materialized table" in a subsequent statement and need to see the

changes

reflected.

The proposed patch provides the exact this feature and I think this will
meet
your needs.

As soon as my coworker gets that example built up I'll send a followup

with

it attached.

Great! We are looking forward to it.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

#149Anastasia Lubennikova
lubennikovaav@gmail.com
In reply to: Yugo NAGATA (#147)
Re: Implementing Incremental View Maintenance

ср, 28 окт. 2020 г. в 08:02, Yugo NAGATA <nagata@sraoss.co.jp>:

Hi Anastasia Lubennikova,

I am writing this to you because I would like to ask the commitfest
manager something.

The status of the patch was changed to "Waiting on Author" from
"Ready for Committer" at the beginning of this montfor the reason
that rebase was necessary. Now I updated the patch, so can I change
the status back to "Ready for Committer"?

Regards,
Yugo Nagata

Yes, go ahead. As far as I see, the patch is in a good shape and there are
no unanswered questions from reviewers.
Feel free to change the status of CF entries, when it seems reasonable to
you.

P.S. Please, avoid top-posting, It makes it harder to follow the
discussion, in-line replies are customary in pgsql mailing lists.
See https://en.wikipedia.org/wiki/Posting_style#Top-posting for details.
--
Best regards,
Lubennikova Anastasia

#150Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Anastasia Lubennikova (#149)
Re: Implementing Incremental View Maintenance

On Wed, 28 Oct 2020 12:01:58 +0300
Anastasia Lubennikova <lubennikovaav@gmail.com> wrote:

ср, 28 окт. 2020 г. в 08:02, Yugo NAGATA <nagata@sraoss.co.jp>:

Hi Anastasia Lubennikova,

I am writing this to you because I would like to ask the commitfest
manager something.

The status of the patch was changed to "Waiting on Author" from
"Ready for Committer" at the beginning of this montfor the reason
that rebase was necessary. Now I updated the patch, so can I change
the status back to "Ready for Committer"?

Regards,
Yugo Nagata

Yes, go ahead. As far as I see, the patch is in a good shape and there are
no unanswered questions from reviewers.
Feel free to change the status of CF entries, when it seems reasonable to
you.

Thank you for your response! I get it.

P.S. Please, avoid top-posting, It makes it harder to follow the
discussion, in-line replies are customary in pgsql mailing lists.
See https://en.wikipedia.org/wiki/Posting_style#Top-posting for details.

I understand it.

Regards,
Yugo Nagata

--
Best regards,
Lubennikova Anastasia

--
Yugo NAGATA <nagata@sraoss.co.jp>

#151Justin Pryzby
pryzby@telsasoft.com
In reply to: Yugo NAGATA (#140)
1 attachment(s)
Re: Implementing Incremental View Maintenance

On Mon, Oct 05, 2020 at 06:16:18PM +0900, Yugo NAGATA wrote:

Hi,

Attached is the rebased patch (v18) to add support for Incremental

This needs to be rebased again - the last version doesn't apply anymore.
http://cfbot.cputube.org/yugo-nagata.html

I looked though it a bit and attach some fixes to the user-facing docs.

There's some more typos in the source that I didn't fix:
constrains
materliazied
cluase
immediaite
clumn
Temrs
migth
recalculaetd
speified
secuirty

commit message: comletion

psql and pg_dump say 13 but should say 14 now:
pset.sversion >= 130000

# bag union
big union?

+       <structfield>relisivm</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if materialized view enables incremental view maintenance

This isn't clear, but I think it should say "True for materialized views which
are enabled for incremental view maintenance (IVM)."

--
Justin

Attachments:

0001-incremental-view-doc-fixes.patchtext/x-diff; charset=us-asciiDownload
From 568f8626e2d9ab0deb25ac9e10089a79abecdab0 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Thu, 5 Nov 2020 22:54:23 -0600
Subject: [PATCH] incremental view doc fixes

---
 doc/src/sgml/catalogs.sgml                    |   2 +-
 .../sgml/ref/create_materialized_view.sgml    |  44 +++---
 .../sgml/ref/refresh_materialized_view.sgml   |   4 +-
 doc/src/sgml/rules.sgml                       | 132 +++++++++---------
 4 files changed, 90 insertions(+), 92 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9b52119382..b942605a1b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3479,7 +3479,7 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        The dependent object was created as part of creation of the Materialized
        View with Incremental View Maintenance reference, and is really just a 
        part of its internal implementation. The dependent object must not be
-       dropped unless materialized view dropped.
+       dropped unless the materialized view is also dropped.
       </para>
      </listitem>
     </varlistentry>
diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml
index 6f4e634ab0..e034a2c17f 100644
--- a/doc/src/sgml/ref/create_materialized_view.sgml
+++ b/doc/src/sgml/ref/create_materialized_view.sgml
@@ -67,7 +67,7 @@ CREATE [ INCREMENTAL ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_na
      </para>
      <para>
       There are restrictions of query definitions allowed to use this
-      option. Followings are supported query definitions for IMMV:
+      option. The following are supported in query definitions for IMMV:
       <itemizedlist>
 
        <listitem>
@@ -78,12 +78,12 @@ CREATE [ INCREMENTAL ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_na
 
        <listitem>
         <para>
-         Outer joins with following restrictions:
+         Outer joins with the following restrictions:
 
          <itemizedlist>
           <listitem>
            <para>
-            Outer join view's targetlist must contain attributes used in the
+            Outer join view's targetlist must contain all attributes used in the
             join conditions.
             <programlisting>
 CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT a.i FROM mv_base_a a LEFT
@@ -95,7 +95,7 @@ ERROR:  targetlist must contain vars in the join condition for IVM with outer jo
 
           <listitem>
            <para>
-            Outer join view's targetlist cannot contain non strict functions.
+            Outer join view's targetlist cannot contain non-strict functions.
             <programlisting>
 CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT a.i, b.i, (k > 10 OR k = -1)
 FROM mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i;
@@ -135,7 +135,7 @@ ERROR:  WHERE cannot contain non null-rejecting predicates for IVM with outer jo
 
           <listitem>
            <para>
-            Aggregate is not supported with outer join.
+            Aggregates are not supported with outer join.
             <programlisting>
 CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b,v) AS SELECT a.i, b.i, sum(k) FROM
 mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i GROUP BY a.i, b.i;
@@ -165,7 +165,7 @@ ERROR:  subquery is not supported by IVM together with outer join
 
        <listitem>
         <para>
-         Subqueries. However following forms are not supported.
+         Subqueries. However, the following forms are not supported.
         </para>
 
         <para>
@@ -176,14 +176,14 @@ mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k &lt; 103 );
          </programlisting>
         </para>
         <para>
-         subqueries in target list is not supported:
+         subqueries in the target list are not supported:
          <programlisting>
 CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm05 AS SELECT i,j, (SELECT k
 FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
          </programlisting>
         </para>
         <para>
-         Nested EXISTS subqueries is not supported:
+         Nested EXISTS subqueries are not supported:
          <programlisting>
 CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm11 AS SELECT a.i,a.j FROM
 mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE EXISTS(SELECT
@@ -210,14 +210,14 @@ a.i &gt; 5;
 
        <listitem>
         <para>
-         Simple CTEs which do not contain aggregates or DISTINCT.
+         Simple CTEs that do not contain aggregates or DISTINCT.
         </para>
        </listitem>
 
        <listitem>
         <para>
-         Some of aggregations (count, sum, avg, min, max) without HAVING
-         clause.  However, aggregate functions in subquery is not supported:
+         Some aggregate functions (count, sum, avg, min, max) without a HAVING
+         clause.  However, aggregate functions in subquery are not supported:
          <programlisting>
 CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm09 AS SELECT a.i,a.j FROM mv_base_a
 a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i;
@@ -226,17 +226,17 @@ a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i;
         </listitem>
       </itemizedlist>
 
-      Unsupported queries with this option include followings:
+      Unsupported queries with this option include the following:
 
       <itemizedlist>
        <listitem>
         <para>
-         Aggregations other than built-in count, sum, avg, min and max.
+         Aggregate functions other than built-in count, sum, avg, min and max.
         </para>
         </listitem>
        <listitem>
         <para>
-         Aggregations with HAVING clause.
+         Aggregate functions with a HAVING clause.
         </para>
         </listitem>
        <listitem>
@@ -251,21 +251,21 @@ a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i;
 
        <listitem>
         <para>
-         IMMVs must be based on simple base tables. Views or materialized views
-         are not allowed to create IMMV on them.
+         IMMVs must be based on simple base tables. It's not supported to
+         create them on top of views or materialized views.
         </para>
        </listitem>
 
        <listitem>
         <para>
-         When TRUNCATE command is executed on a base table, nothing occurs and
-         this is not applied to the materialized view.
+         When the TRUNCATE command is executed on a base table,
+         no changes are made to the materialized view.
         </para>
        </listitem>
 
        <listitem>
         <para>
-         IMMV including system columns is not supported.
+         It is not supported to include system columns in an IMMV.
          <programlisting>
 CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
 ERROR:  system column is not supported with IVM
@@ -275,7 +275,7 @@ ERROR:  system column is not supported with IVM
 
        <listitem>
         <para>
-         IMMV including non-immutable functions is not supported.
+         Non-immutable functions are not supported.
          <programlisting>
 CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
 ERROR:  functions in IMMV must be marked IMMUTABLE
@@ -285,13 +285,13 @@ ERROR:  functions in IMMV must be marked IMMUTABLE
 
        <listitem>
         <para>
-         IMMVs including expressions which contains aggregates in it
+         IMMVs do not support expressions that contains aggregates
         </para>
        </listitem>
 
        <listitem>
         <para>
-         IMMVs not supported by logical replication.
+         Logical replication does not support IMMVs.
         </para>
        </listitem>
 
diff --git a/doc/src/sgml/ref/refresh_materialized_view.sgml b/doc/src/sgml/ref/refresh_materialized_view.sgml
index 7241789da1..dc81853057 100644
--- a/doc/src/sgml/ref/refresh_materialized_view.sgml
+++ b/doc/src/sgml/ref/refresh_materialized_view.sgml
@@ -35,8 +35,8 @@ REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] <replaceable class="parameter">name</
    owner of the materialized view.  The old contents are discarded.  If
    <literal>WITH DATA</literal> is specified (or defaults) the backing query
    is executed to provide the new data, and the materialized view is left in a
-   scannable state.  Also, if the view is incrementally maintainable materialized
-   view (IMMV) and when this was in unscannable state, triggers for maintaining
+   scannable state.  Also, if the view is an incrementally maintainable materialized
+   view (IMMV) and was unpopulated, triggers for maintaining
    the view are created.  If <literal>WITH NO DATA</literal> is specified no new
    data is generated and the materialized view is left in an unscannable
    state.  If the view is IMMV, the triggers are dropped.
diff --git a/doc/src/sgml/rules.sgml b/doc/src/sgml/rules.sgml
index f0f010eb3e..460029a71a 100644
--- a/doc/src/sgml/rules.sgml
+++ b/doc/src/sgml/rules.sgml
@@ -1114,16 +1114,16 @@ SELECT word FROM words ORDER BY word &lt;-&gt; 'caterpiler' LIMIT 10;
     and applied on views rather than recomputing the contents from scratch as
     <command>REFRESH MATERIALIZED VIEW</command> does.  <acronym>IVM</acronym>
     can update materialized views more efficiently than recomputation when only
-    small part of the view need updates.
+    small parts of the view are changed.
 </para>
 
 <para>
     There are two approaches with regard to timing of view maintenance:
     immediate and deferred.  In immediate maintenance, views are updated in the
-    same transaction where its base table is modified.  In deferred maintenance,
+    same transaction that its base table is modified.  In deferred maintenance,
     views are updated after the transaction is committed, for example, when the
     view is accessed, as a response to user command like <command>REFRESH
-    MATERIALIAED VIEW</command>, or periodically in background, and so on.
+    MATERIALIZED VIEW</command>, or periodically in background, and so on.
     <productname>PostgreSQL</productname> currently implements only a kind of
     immediate maintenance, in which materialized views are updated immediately
     in AFTER triggers when a base table is modified.
@@ -1136,9 +1136,9 @@ SELECT word FROM words ORDER BY word &lt;-&gt; 'caterpiler' LIMIT 10;
 CREATE <emphasis>INCREMENTAL</emphasis> MATERIALIZED VIEW mymatview AS SELECT * FROM mytab;
 </programlisting>
     When a materialized view is created with the <literal>INCREMENTAL</literal>
-    keyword, some triggers are automatically created so that its contents are
+    keyword, some triggers are automatically created so that the view's contents are
     immediately updated when its base tables are modified. We call this form
-    of materialized view as Incrementally Maintainable Materialized View
+    of materialized view an Incrementally Maintainable Materialized View
     (<acronym>IMMV</acronym>).
 <programlisting>
 postgres=# CREATE INCREMENTAL MATERIALIZED VIEW m AS SELECT * FROM t0;
@@ -1163,21 +1163,21 @@ postgres=# SELECT * FROM m; -- automatically updated
 (4 rows)
 </programlisting>
     <acronym>IMMV</acronym>s can have hidden columns which are added
-    automatically when a materialized view is created. Their name start
+    automatically when a materialized view is created. Their name starts
     with <literal>__ivm_</literal> and they contain information required
-    for maintaining <acronym>IMMV</acronym>. Such columns are not visible
-    when <acronym>IMMV</acronym> is accessed by <literal>SELECT *</literal>
-    but visible if the column name is explicitly specified in the target
-    list. We can also see the hidden columns by <literal>\d</literal>
-     meta-commands of <command>psql</command> commands.
+    for maintaining the <acronym>IMMV</acronym>. Such columns are not visible
+    when the <acronym>IMMV</acronym> is accessed by <literal>SELECT *</literal>
+    but are visible if the column name is explicitly specified in the target
+    list. We can also see the hidden columns in <literal>\d</literal>
+    meta-commands of <command>psql</command> commands.
 </para>
 
 <para>
-    In general, <acronym>IMMV</acronym> allows faster update of materialized
-    views at a price of slower update of base tables because triggers will
+    In general, <acronym>IMMV</acronym>s allow faster updates of materialized
+    views at the price of slower updates to their base tables because triggers will
     be invoked and the view is updated in triggers per modification statement.
     For example, here are two materialized views based on the same view
-    definition but one is a normal materialized view and another is
+    definition but one is a normal materialized view and the other is an
     <acronym>IMMV</acronym>:
 
 <programlisting>
@@ -1207,7 +1207,7 @@ Time: 33533.952 ms (00:33.534)
 </programlisting>
 
     On the other hand, updating a tuple in <acronym>IMMV</acronym> takes
-    more than the normal view, but the contents is updated automatically
+    more than the normal view, but its content is updated automatically
     and this is faster than the <command>REFRESH MATERIALIZED VIEW</command>
     command.
 
@@ -1220,10 +1220,10 @@ Time: 13.068 ms
 </para>
 
 <para>
-    An appropriate indexes on <acronym>IMMV</acronym> are necessary for
+    Appropriate indexes on <acronym>IMMV</acronym>s are necessary for
     efficient <acronym>IVM</acronym> because it looks for tuples to be
-    updated in <acronym>IMMV</acronym>.  If there is no indexes, it
-    take a long time. Here is an example after dropping the index:
+    updated in <acronym>IMMV</acronym>.  If there are no indexes, it
+    will take a long time. Here is an example after dropping the index:
 <programlisting>
 test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
 UPDATE 1
@@ -1234,16 +1234,16 @@ Time: 16386.245 ms (00:16.386)
 
 <para>
     <acronym>IVM</acronym> is effective when we want to keep a materialized
-    view up-to-date and small fraction of a base table is modified in low
-    frequency.  Due to the overhead of immediate maintenance, <acronym>IVM</acronym>
+    view up-to-date and small fraction of a base table is modified
+    infrequently.  Due to the overhead of immediate maintenance, <acronym>IVM</acronym>
     is not effective when a base table is modified frequently.  Also, when a
     large part of a base table is modified or large data is inserted into a
     base table, <acronym>IVM</acronym> is not effective and the cost of
-    maintenance can be larger than <command>REFRESH MATERIALIZED VIEW</command>
+    maintenance can be larger than the <command>REFRESH MATERIALIZED VIEW</command>
     command. In such situation, we can use <command>REFRESH MATERIALIZED VIEW</command>
-    with specifying <literal>WITH NO DATA</literal> to disable immediate
+    and specify <literal>WITH NO DATA</literal> to disable immediate
     maintenance before modifying a base table. After a base table modification,
-    execute <command>REFRESH MATERIALIZED VIEW</command> (with <literal>WITH DATA</literal>)
+    execute the <command>REFRESH MATERIALIZED VIEW</command> (with <literal>WITH DATA</literal>)
     command to refresh the view data and enable immediate maintenance.
 </para>
 
@@ -1253,7 +1253,7 @@ Time: 16386.245 ms (00:16.386)
 <title>Supported View Definitions and Restrictions</title>
 
 <para>
-    Currently, we can create <acronym>IMMV</acronym> using inner and outer
+    Currently, we can create <acronym>IMMV</acronym>s using inner and outer
     joins, some aggregates, and some subqueries. However, several restrictions
     apply to the definition of IMMV.
 </para>
@@ -1280,7 +1280,7 @@ Time: 16386.245 ms (00:16.386)
         <listitem>
         <para>
             All attributes used in join conditions must be contained in the target list.
-               These attributes are used as scan keys for searching tuples in
+               These attributes are used as scan keys for searching tuples in the
                <acronym>IMMV</acronym>, so indexes on them are required for efficient
                <acronym>IVM</acronym>.
         </para>
@@ -1296,7 +1296,7 @@ Time: 16386.245 ms (00:16.386)
 
         <listitem>
         <para>
-            <literal>WHERE</literal> clause cannot contain non null-rejecting
+            <literal>WHERE</literal> clauses cannot contain non null-rejecting
             predicates that can return true for NULL inputs.  For example,
             <literal>IS NULL</literal> cannot be used.
         </para>
@@ -1310,7 +1310,7 @@ Time: 16386.245 ms (00:16.386)
 
         <listitem>
         <para>
-            Subquery cannot be used with outer join.
+            Subqueries cannot be used with outer join.
         </para>
         </listitem>
     </itemizedlist>
@@ -1324,16 +1324,16 @@ Time: 16386.245 ms (00:16.386)
     Supported aggregate functions are <function>count</function>, <function>sum</function>,
     <function>avg</function>, <function>min</function>, and <function>max</function>.
     Currently, only built-in aggregate functions are supported and user defined
-    aggregates can not be used.  When a base table is modified, the new aggregated
-     values are incrementally calculated using the old aggregated values and values
-     of related hidden columns stored in <acronym>IMMV</acronym>.
+    aggregates cannot be used.  When a base table is modified, the new aggregated
+    values are incrementally calculated using the old aggregated values and values
+    of related hidden columns stored in <acronym>IMMV</acronym>.
 </para>
 
 <para>
      Note that for <function>min</function> or <function>max</function>, the new values
      could be re-calculated from base tables with regard to the affected groups when a
      tuple containing the current minimal or maximal values are deleted from a base table.
-     Therefore, it can takes a long time for updating <acronym>IMMV</acronym> containing
+     Therefore, it can takes a long time to update an <acronym>IMMV</acronym> containing
      these functions.
 </para>
 
@@ -1342,7 +1342,7 @@ Time: 16386.245 ms (00:16.386)
     <type>real</type> (<type>float4</type>) type or <type>double precision</type>
     (<type>float8</type>) type in <acronym>IMMV</acronym> is unsafe. This is
     because aggregated values in <acronym>IMMV</acronym> can become different from
-    results calculated from base tables due to the limitted precision of these types.
+    results calculated from base tables due to the limited precision of these types.
     To avoid this problem, use the <type>numeric</type> type instead.
 </para>
 
@@ -1353,11 +1353,10 @@ Time: 16386.245 ms (00:16.386)
     <itemizedlist>
         <listitem>
         <para>
-            If we have <literal>GROUP BY</literal> clause, expressions specified in
+            If we have a <literal>GROUP BY</literal> clause, expressions specified in
                <literal>GROUP BY</literal> must appear in the target list.  This is
-               because tuples to be updated in <acronym>IMMV</acronym> are identified
-               by them.
-               These attributes are used as scan keys for searching tuples in
+               how tuples to be updated in the <acronym>IMMV</acronym> are identified.
+               These attributes are used as scan keys for searching tuples in the
                <acronym>IMMV</acronym>, so indexes on them are required for efficient
                <acronym>IVM</acronym>.
         </para>
@@ -1387,7 +1386,7 @@ Time: 16386.245 ms (00:16.386)
     <itemizedlist>
         <listitem>
         <para>
-            Only <literal>EXISTS</literal> subquery is allowed to used in
+            Only <literal>EXISTS</literal> subquery is allowed in the
               <literal>WHERE</literal> clause. For example, <literal>IN</literal>
               clause cannot be used.
         </para>
@@ -1396,8 +1395,8 @@ Time: 16386.245 ms (00:16.386)
         <listitem>
         <para>
             <literal>EXISTS</literal> subquery must be used with <literal>AND</literal>.
-              <literal>OR EXISTS (...)</literal> or <literal>NOT EXISTS (...)</literal>
-              is not allowed.
+              <literal>OR EXISTS (...)</literal> and <literal>NOT EXISTS (...)</literal>
+              are not allowed.
         </para>
         </listitem>
 
@@ -1409,13 +1408,13 @@ Time: 16386.245 ms (00:16.386)
 
         <listitem>
         <para>
-            Nested <literal>EXISTS</literal> subqueries is not supported.
+            Nested <literal>EXISTS</literal> subqueries are not supported.
         </para>
         </listitem>
 
         <listitem>
         <para>
-            <literal>EXISTS</literal> subquery cannot be used if <acronym>IMMV</acronym>
+            <literal>EXISTS</literal> subqueries cannot be used if <acronym>IMMV</acronym>
                uses aggregate functions.
         </para>
         </listitem>
@@ -1434,7 +1433,7 @@ Time: 16386.245 ms (00:16.386)
 
         <listitem>
         <para>
-            subqueres containing table join are only allowed in FROM clause.
+            Subqueries containing joins are only allowed in the FROM clause.
         </para>
         </listitem>
 
@@ -1447,7 +1446,7 @@ Time: 16386.245 ms (00:16.386)
 <sect3>
 <title>CTEs</title>
 <para>
-    Currently, simple <literal>CTE</literal> in <literal>FROM</literal> clause are supported.
+    Currently, simple <literal>CTE</literal> in the <literal>FROM</literal> clause are supported.
 </para>
 
     <sect4>
@@ -1479,45 +1478,44 @@ Time: 16386.245 ms (00:16.386)
     <itemizedlist>
         <listitem>
           <para>
-           window functions cannot be used.
+           Window functions cannot be used.
           </para>
         </listitem>
 
         <listitem>
           <para>
-            <acronym>IMMV</acronym>s must be based on simple base tables. Views or
-               materialized views are not allowed to create <acronym>IMMV</acronym>
-               on them.
+            <acronym>IMMV</acronym>s must be based on simple base tables.  It's not
+               supported to create them on top of views or materialized views.
           </para>
         </listitem>
 
         <listitem>
           <para>
-            LIMIT and OFFSET clause cannot be used.
+            LIMIT and OFFSET clauses cannot be used.
           </para>
         </listitem>
 
         <listitem>
           <para>
-            <acronym>IMMV</acronym> cannot contain system columns.
+            <acronym>IMMV</acronym>s cannot contain system columns.
           </para>
         </listitem>
 
         <listitem>
           <para>
-            <acronym>IMMV</acronym> cannot contain non-immutable functions.
+            <acronym>IMMV</acronym>s cannot contain non-immutable functions.
           </para>
         </listitem>
 
         <listitem>
           <para>
-            UNION/INTERSECT/EXCEPT clause cannnot be used.
+            UNION/INTERSECT/EXCEPT clauses cannnot be used.
           </para>
         </listitem>
 
         <listitem>
           <para>
-            DISTINCT ON clause cannot be used.
+            DISTINCT ON clauses cannot be used.
           </para>
         </listitem>
 
@@ -1529,7 +1527,7 @@ Time: 16386.245 ms (00:16.386)
 
         <listitem>
           <para>
-            inheritance parent table cannnot be used.
+            inheritance parent tables cannnot be used.
           </para>
         </listitem>
 
@@ -1573,8 +1571,8 @@ Time: 16386.245 ms (00:16.386)
 
         <listitem>
           <para>
-            When <literal>TRUNCATE</literal> command is executed on a base table,
-               nothing occurs and this is not applied to <acronym>IMMV</acronym>.
+            When the <literal>TRUNCATE</literal> command is executed on a base table,
+               nothing is changed on the <acronym>IMMV</acronym>.
           </para>
         </listitem>
 
@@ -1590,16 +1588,16 @@ Time: 16386.245 ms (00:16.386)
 <para>
     <productname>PostgreSQL</productname> supports <acronym>IMMV</acronym> with
     <literal>DISTINCT</literal>.  For example, suppose a <acronym>IMMV</acronym>
-    defined with <literal>DISTINCT</literal> on a base table containing duplicated
+    defined with <literal>DISTINCT</literal> on a base table containing duplicate
     tuples.  When tuples are deleted from the base table, a tuple in the view is
-    deleted if and only when the multiplicity of the tuple becomes zero.  Moreover,
+    deleted if and only if the multiplicity of the tuple becomes zero.  Moreover,
     when tuples are inserted into the base table, a tuple is inserted into the
-    view only if the same tuple doesn't exist in it.
+    view only if the same tuple doesn't already exist in it.
 </para>
 
 <para>
-    Physically, <acronym>IMMV</acronym> defined with <literal>DISTINCT</literal>
-    contains tuples after eliminating duplicates, and multiplicity of each tuple
+    Physically, an <acronym>IMMV</acronym> defined with <literal>DISTINCT</literal>
+    contains tuples after eliminating duplicates, and the multiplicity of each tuple
     is stored in a hidden column named <literal>__ivm_count__</literal>.
 </para>
 </sect2>
@@ -1607,28 +1605,28 @@ Time: 16386.245 ms (00:16.386)
 <sect2>
 <title>Concurrent Transactions</title>
 <para>
-    Suppose a <acronym>IMMV</acronym> is defined on two base tables and each
-    table was modified in different concurrent transactions simultaneously.
+    Suppose an <acronym>IMMV</acronym> is defined on two base tables and each
+    table was modified in different a concurrent transaction simultaneously.
     In the transaction which was committed first, <acronym>IMMV</acronym> can
     be updated considering only the change which happened in this transaction.
     On the other hand, in order to update the view correctly in the transaction
     which was committed later, we need to know the changes occurred in
     both transactions.  For this reason, <literal>ExclusiveLock</literal>
-    is held on <acronym>IMMV</acronym> immediately after a base table is
+    is held on an <acronym>IMMV</acronym> immediately after a base table is
     modified in <literal>READ COMMITTED</literal> mode to make sure that
     the <acronym>IMMV</acronym> is updated in the latter transaction after
     the former transaction is committed.  In <literal>REPEATABLE READ</literal>
     or <literal>SERIALIZABLE</literal> mode, an error is raised immediately
-    when the lock is failed to acquire because any changes occurred in
-    other transactions must not be visible in these modes and
-    <acronym>IMMV</acronym> can not be updated correctly in such situations.
+    if lock acquisition fails because any changes which occurred in
+    other transactions are not be visible in these modes and
+    <acronym>IMMV</acronym> cannot be updated correctly in such situations.
 </para>
 </sect2>
 
 <sect2>
 <title>Row Level Security</title>
 <para>
-    If some base tables have row level security policy, rows that are not invisible
+    If some base tables have row level security policy, rows that are not visible
     to the materialized view's owner are excluded from the result.  In addition, such
     rows are excluded as well when views are incrementally maintained.  However, if a
     new policy is defined or policies are changed after the materialized view was created,
-- 
2.17.0

#152Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Yugo NAGATA (#140)
Re: Implementing Incremental View Maintenance

On 05.10.2020 12:16, Yugo NAGATA wrote:

Hi,

Attached is the rebased patch (v18) to add support for Incremental
Materialized View Maintenance (IVM). It is able to be applied to
current latest master branch.

Thank you very much for this work.
I consider incremental materialized views as "reincarnation" of OLAP
hypercubes.
There are two approaches of making OLAP queries faster:
1. speed up query execution (using JIT, columnar store, vector
operations and parallel execution)
2. precalculate requested data

Incremental materialize views make it possible to implement second
approach. But how competitive it is?
I do not know current limitations of incremental materialized views, but
I checked that basic OLAP functionality:
JOIN+GROUP_BY+AGGREGATION is supported.

The patch is not applied to the current master because makeFuncCall
prototype is changed,
I fixed it by adding COAERCE_CALL_EXPLICIT.
Then I did the following simple test:

1. Create pgbench database with scale 100.
pgbench speed at my desktop is about 10k TPS:

pgbench -M prepared -N -c 10 -j 4 -T 30 -P 1 postgres
tps = 10194.951827 (including connections establishing)

2. Then I created incremental materialized view:

create incremental materialized view teller_sums as select
t.tid,sum(abalance) from pgbench_accounts a join pgbench_tellers t on
a.bid=t.bid group by t.tid;
SELECT 1000
Time: 20805.230 ms (00:20.805)

20 second is reasonable time, comparable with time of database
initialization.

Then obviously we see advantages of precalculated aggregates:

postgres=# select * from teller_sums where tid=1;
�tid |� sum
-----+--------
�� 1 | -96427
(1 row)

Time: 0.871 ms
postgres=# select t.tid,sum(abalance) from pgbench_accounts a join
pgbench_tellers t on a.bid=t.bid group by t.tid having t.tid=1
;
�tid |� sum
-----+--------
�� 1 | -96427
(1 row)

Time: 915.508 ms

Amazing. Almost 1000 times difference!

3. Run pgbench once again:

Ooops! Now TPS are much lower:

tps = 141.767347 (including connections establishing)

Speed of updates is reduced more than 70 times!
Looks like we loose parallelism because almost the same result I get
with just one connection.

4. Finally let's create one more view (it is reasonable to expect that
analytics will run many different queries and so need multiple views).

create incremental materialized view teller_avgs as select
t.tid,avg(abalance) from pgbench_accounts a join pgbench_tellers t on
a.bid=t.bid group by t.tid;

It is great that not only simple aggregates like SUM are supported, but
also AVG.
But insertion speed now is reduced twice - 72TPS.

I tried to make some profiling but didn't see something unusual:

� 16.41%� postgres� postgres����������� [.] ExecInterpExpr
�� 8.78%� postgres� postgres����������� [.] slot_deform_heap_tuple
�� 3.23%� postgres� postgres����������� [.] ExecMaterial
�� 2.71%� postgres� postgres����������� [.] AllocSetCheck
�� 2.33%� postgres� postgres����������� [.] AllocSetAlloc
�� 2.29%� postgres� postgres����������� [.] slot_getsomeattrs_int
�� 2.26%� postgres� postgres����������� [.] ExecNestLoop
�� 2.11%� postgres� postgres����������� [.] MemoryContextReset
�� 1.98%� postgres� postgres����������� [.] tts_minimal_store_tuple
�� 1.87%� postgres� postgres����������� [.] heap_compute_data_size
�� 1.78%� postgres� postgres����������� [.] fill_val
�� 1.56%� postgres� postgres����������� [.] tuplestore_gettuple
�� 1.44%� postgres� postgres����������� [.] sentinel_ok
�� 1.35%� postgres� postgres����������� [.] heap_fill_tuple
�� 1.27%� postgres� postgres����������� [.] tuplestore_gettupleslot
�� 1.17%� postgres� postgres����������� [.] ExecQual
�� 1.14%� postgres� postgres����������� [.] tts_minimal_clear
�� 1.13%� postgres� postgres����������� [.] CheckOpSlotCompatibility
�� 1.10%� postgres� postgres����������� [.] base_yyparse
�� 1.10%� postgres� postgres����������� [.] heapgetpage
�� 1.04%� postgres� postgres����������� [.] heap_form_minimal_tuple
�� 1.00%� postgres� postgres����������� [.] slot_getsomeattrs

So good news is that incremental materialized views really work.
And bad news is that maintenance overhead is too large which
significantly restrict applicability of this approach.
Certainly in case of dominated read-only workload such materialized
views can significantly improve performance.
But unfortunately my dream that them allow to combine OLAP+OLPT is not
currently realized.

--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#153legrand legrand
legrand_legrand@hotmail.com
In reply to: Konstantin Knizhnik (#152)
Re: Implementing Incremental View Maintenance

Hello Konstantin,
I remember testing it with pg_stat_statements (and planning counters
enabled). Maybe identifying internal queries associated with this (simple)
test case, could help dev team ?
Regards
PAscal

--
Sent from: https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

#154Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Justin Pryzby (#151)
1 attachment(s)
Re: Implementing Incremental View Maintenance

On Thu, 5 Nov 2020 22:58:25 -0600
Justin Pryzby <pryzby@telsasoft.com> wrote:

On Mon, Oct 05, 2020 at 06:16:18PM +0900, Yugo NAGATA wrote:
This needs to be rebased again - the last version doesn't apply anymore.
http://cfbot.cputube.org/yugo-nagata.html

I attached the rebased patch (v19).

I looked though it a bit and attach some fixes to the user-facing docs.

Thank you for pointing out a lot of typos and making the patch to fix it!
Your fixes are included in the latest patch.

There's some more typos in the source that I didn't fix:
constrains
materliazied
cluase
immediaite
clumn
Temrs
migth
recalculaetd
speified
secuirty

commit message: comletion

psql and pg_dump say 13 but should say 14 now:
pset.sversion >= 130000

These were also fixed.

# bag union
big union?

"bag union" is union operation of bag (multi-set) that does not eliminate duplicate of tuples.

+       <structfield>relisivm</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if materialized view enables incremental view maintenance

This isn't clear, but I think it should say "True for materialized views which
are enabled for incremental view maintenance (IVM)."

Yes, you are right. I also fixed it in this way.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v19.tar.gzapplication/gzip; name=IVM_patches_v19.tar.gzDownload
�o��_�\{w������S��9��y�������$a������sGH�-[HD~�L��VU��������F3!uWWW��]�;���S#4�y0�R��gOpT��W�������wt<S�zU��ZES�UM��g���,� 4|����������]���GgI����r��z���9�{Vt�1/S�-z�b�n`���Cc���8sC�DTR�@�t}�������U]����
��|�?��;����Uo��cS�Tm���j��^��t�����Z�*c4V����>�2��*�=�������c���X�83B�����7�oxAP2����u���{�����	_J�)@���WY���T2��������������eE�7#X��c�a����9,��%CX:q�0�w�L����l�p��gShz>�"�w�vz�gg����>��}�-6�|�
��}�q��Y|���;���|+���>�nh �tf������K�Y�	N��/�4��18v<7���<����|�Av}�9\�a�lb���Y�M8@B�?���C���s�=�V�vE#9i6��$���)��fyd�� ���M&�ke��d�?Y���w��������t��SY>c����������l>�Q;\�.�2��Y�xf��(����� ���JS5L����RI���P�#p���r�H�����y���V�j,O�J����v�->fA������!��S�
�tda��]�s�@��`�����;�����c��i����:q�F��;}�^� �����a�!-��h>4o������D0��s�Z��!H����tN�h
�x1Id����7'�X`�����]�
�Fj����4=�9�%��!)s��0�n���� ��,��4�	# Z��ls����0/�$�L����UWUR���
�;��a42l����5H�v�>�
 ��;�]���>;7�n��Dh+��0,��ZE�&4C=����oa����s�R�	�c��I��H�=f�{�$�;4�:
	b�	�&�|'�n��")���@�@({�3H!(1�+�JJ���7nq,!������]P��N��B3i T�ZF�����@����!e�3.P�z�f���_���O�.:�8��q�-��lM�����[)}��lq�s����mC��D /	���S������V?o�V����F\�%�>�� "5�k]`�s�`!�������K����:pm3���*M���j��f$��q��KN������K"��"�3������������q�}�6�}cz�r���Z���)7��
���B��s!#�@��������� �AHd����T�����bj(@	��@�^��<\�=�p�F]B�����aW6�f4���k
7����b�}�� �
����RZv0�(!S|�2�?`}�/|�K��_n�4?�~��������
'�N���8h��5@��9�A�-P �`w��v]%�t��$$�R����U`
;;`����^��W`�7�"G~��	l��N"�<'��^��HF�_ky����V �u��_H �(��b�u"�1gl�A���*���JB��&oHm�������5 �M��#y}��^�E�cM�W���W�r���,��&E������H3��AK'�C�-��e����3$�G#eXYD.�y��/�HC�
2b��	��|��!�%�S�EE����ad�AA����c� Uv_R�PI#���+VTPr�-R#h������c��o�oE��X#��|zW����`(�8�`Q:d��JA�#�!���((
H���jsY<� ���]��Qh;!r*a����v�����5 �"�/n1�����L~O��zi��V@X
��%������}@�y>��`��Y���l�?��Nq[���;����#��m��'w0E�Y/���
Z

Z��CA�NA_�%.�e��#�k���U)������d��!���
�4+�P��{���+�����>~��3�+��^/���A�O���+������Y�mCRD�'���=�
[9�;�=�5���rK/�����
�Ym���l 4�c�����5D~���wz������F�-1���n�F����e��y���	d|&:�d�����9�S�G&����-�������Yh;A����o���q��ZR�%%��O�I�8���R�i[�U<���l:���h�Eh����������������U���~���A������7
0������|*�kZ�>�hf���#��s���*;1C��V������4�����cYP�$*��*3�>UY��&��9�<P"�[�`��qv�s`�<>;������6�\�:#)�{b;���~y ��J�<(e2oi�bs����X���Z���apHUB������$E�'�Yh���PRX`L8}��Eu	7?2�7
 ��o��i����S�p��-�(�K&�2����%�����M��	���O���y�t��31B����Qj,�6��0�?�X��TL�w�� ���h���K��\XR*��,%���3o!��Y�)u��PJ%���c�7Me��R����R�F>JE�R�K'�;`�����1��m�+���s��l��pC>kF��]3�A��<A���;�V��> n��rHn_4X��lKn���i���H8�����G����\ ^|
����P�*�6HD|� �[2���N���^?89l��[��^{�=��?�������:	����������X��6%���:��b5�EV�y.'��	�W`
~�����h�<	v�I�����ZA�>����[����;�x�����^����G�L$C��
ZL�%	��6�7g~`_q�D-�!������G#���
����Xr.r�a��?�o�>*�G�}�WL��wv�;ZX����tg?f�l��I�Y6j���\$�ur����-�~
���l����w6�)��r��EB�v�A�)�d�F��Kw������p��tm��aF��X=�IL�&d�Yh�qP4]>q�d�a�1{��H�#���%�k��AYCs�/��C���X+��
}�m��v�"�D�W�rO����� �*
�M�#�&[��G�n@� �U|���h��>i����c��C�/##��14 ��=�$��u�����3n #�Zs�V-���!��T��P�h�HwNhC�;oD��kT��o�l���};�C�-r8��lj������/�4D=_���e����=��p�������b>=H�	�{�3>'a~��>��A�<�2�J��n�.,���n��l��yNH����L�tz*&����������=/$�=��G�����|L��x+,�c�F~�����H_x~"g�����gNl'�w�o5�m�� ����'� ���jMA��ui�"m� ;7���ZTu�d���Lss'��	�?Nt��)F*i��?w�����i�SQ�x�.��� V�3��F��S���^�T\o����������`8h�����
������
�a�&o(M�6*��*W
���z���������� ��@cEO��m�������I�T���Vi���f\%L�K�el�a�o1�B��Q;$�]�+�HQ!f� i�v�iH�V�l�)���r7n���6�����F_8�4��F#AD^�<�z�V��
db$DQ�.�,Eo�(�A�N�w"A�����Pd�c0\�)v� uE���Ol�?}��i��;=�Z^��|��!��0���W�W��NY��7�Fw��+^��RW*�/��5��������i����� ���R���#�����1�8�g���������^N'��`�>a{w2��>n5����7�T�[6G�hm~����?h���J48�2~|�����O	a��
w�h|~T!���Z�ST����2�����!(+v�qB����������U�T�o��W��k?�����WG&��qc4��1��z�0G#]7k�9[�a���|�K���J�ZT��8�%#X�[		X2�%;N<3,�\�R�,��!
��*'��a�M����Q��5V�]����� ��j�ak��A��8���E%�������dG��
|qR��T]�W�����fu�������D��M�,����]������T����t��c�����\G����p�U]S,
��*�3�I���J��B�P��M&�����)���/V����G"=X��������f��d	�x,6�������Gm6����C�em����w��vo�:��I�����i���3�������p���T+h��^���j�ZA������/7
>Z\�GW*j<�R�����������vw�:b�p��iu��}�>u���0�s��4")ev��p���N�0�N��c�u��&x��~��>k��o���=a��AF��vcBb���!Py�;S
jA�v����V���G��3�(�q����t�����=
��P�k����R�)w�J���6�p�`��`~.�v]2�.���	���y���?U8S�O~j����O�t�S���:��k|�����u���C���w�w@pi@�;����?�0��|��.�@]�=6^�
��C����.{�y�G��!��XXB#9��]������(���I��wrt��u��w���u�SE�Q���M��M�}+c'I�-^�t������������h����q���w��n���p�`E���$L��qf�4lrb`�{'�p�d1smH��p���p�HB�ID>�v~=E�������H*� �\b�<����v���
�-]�?3,�a��0D�o��Zp����-H����.;�8v��v��d��n�jG������6{	��K#�<���P^����)�0��-p��$�������
��� �!a��F3�^�^�*�oJ���z��3k6ulS�f�J+����9�9�k��v$��iK4���(6[��u�q��>7�h�/�Y'c^Ah���G>����N!��6,����s{A>V}�����)g����1���q������V2�B�O��,z�w�'Ma ������6��� �%����~�����������l���g�3`\�����B~���(��Cl-`@Q�,2�H�NMm�����vZj;�_D��S�U�a�d�jZ�$6.l����������W���L�Je��<���C�"6hB]����pU[�bke����m��m5�M�P�W�a7�=��}������*rUe��YU���������%��c�-6)1=
�{�B�"H��|o6M�hB���u2�c�-��6�"�\u���*(���R+��#�ig��8��,��g�j��82���Ljc��FVRH���?����
����"���J�����8�����Wv`��]�g��������f�0��h��*��tk�(��mE~����E�(����y��N�?�����#�M#�N��j�G����V!#je����T�����(w���q����;�N��E��C]��@���3#�����	�W��PeIq����%E�K_�N�������[�_���=m$it��_�o��1&H�lg�]b��3���qf���� l��"	�������/R�ss�<3����������-w��>����\��������\Uw��>��9S�E(�G(��ZT}�R}�R��S���.�(AV�W%V_�����l[��������"A}���@���li���hjV����FK�xx0#�����b�D49*O0�cv�����a8��Vlh�X��L���p���D�k�4Y(�y,��bn;�6�a�6�l}DLj���&��<�T���=�W����}N8[�������y�xz��Rk�'*�4B
Lz�*������'��!�G)q��!pW�e@�����T����u��44��g �"��.��0Vzo�_*N"�u����yJR����,���1LPBY;�Z6�w�L�*M��k����e]�����TH�N�]6�����1`��������X(��You�����]�7���7AKAJ��|����
!^��R<����"SOdy`����:����@&�&�4�����b�3�h�����jn�ydJ�81Q�Xab"���,���������(�!I�H"D�!�s��.2U���jf�Ts���U�k�Wu��Y5|���ij�������NY+|�ZR��
&�4�UV���r���P
��hh����������p��8$0b����'"9�h|�]|!�����i�!�0�(zz�
�0'����Q���O�P�'31q3���3�)������1`>��l���!xP�t<:���)s �o��7p4
��	�c.��T�zg0�"��o�V���%~��l,4U�{�S�`'0q���:S=��0;|b���l{b&��?��N-�%���g�a��vEb�?T��j��k�=C�#<o�7���
��������5Gn=�������A%'���I��,�duF�p=�J��f�rs��{������%��(��3i<��1�x�z�1c$baf[doH0�H�:}@���*��DP�av)�SbV�
��<[n)�+2�[������n�����(�����C]R.wo��J�u�`�-�+zG)Q�����)�>W��)j������Kf��D�����|���McX	��`��s~y��\��x!�	XWi���!���wO�V),�}����)���t_��-�\���\�����}}��#��'x@Op��0x1��U�j)�
�j<�*���u�3���U����-�S��DUc��-s���e��F�'T�1F�O��U�0�C�����zU����)�X����_eC�P�����t�A���@���l��%��.��D����[��\r�����
n���b�(�	�(Y�� ��a�	����2�S�zA'#�xtl��O����kgaU3���a�|,qOZid���hm�F��:+��
�q�ECJ���C��L�;�Xf���Gf�;y���8?�[�y�;����s~�N�pbn�Z�*"��������.��sw�t ���,q�v����]�I<���r8��ln�QHh�T����fb�Z�A���d\<���@b/o�$�:K<�}�������f|������\D<�Sc0�l���4�B�~�}\�
� O�O4��O��4�\
,��m�B�z������J���&�Q��B�to�|bX��f�eO�D<�C�r���a�*����s���MAfZ�A�>�I����+�Sp���"+VUY�:��b&��F��S�,�&�Oa���-��%����(
�!�Rw�<u��o�b���A��m2�|���s*l[�����./��R�-C�����	H������`>����i���4���	+.���-�ls�J�/��5����8�����;7���0�~�	���vQO�>�A
F?����+O8��.�8��l�2����6�I:}�k:M�'P$�>������7H��������8��7I�����`r��V��:����|���
���)��1��5FzH���~rE�����>����7�Q�7��M�}�W��l��&��5`���]������[:!{<��
n��7f�{�]eX���.�P���h�!���f�������~{\X$,�/�+��QO�)��Kr��
��D�!���aO2�JO�s���$ce��q7��f�Q1���Qzu�W������������9�5r�o����������R)0J���9���Y7aO�1��SN�������C��Qd��������jB�O����e[)���m�����Uf�;����'^�*a�]T%(+�����)%#13�����'�������>�b#�m�����*N��.F��R���u��8=��cSh%�W]���V��"���{���JY16g<y>T,m;�?[7�E��v�z��^�����9]��q�����c�����w,�'�L��s��-#��l��� �F��<S���-Sok$yS�x��������������%C���lc��EN#�����	�>!��	����%����B	�,����%���W:\��y�pYz�a��b�m�m+��'�#x�����-�[���L�pq�n+��K	�����])�'���mK����/��y�]fzT��Iz���\Y�M�z�Z0��Q�G�}?a�$�2I��q"�qBcKJ�[����2>C�>���F�]6w\Y$2�;�����6F�k�mc�q���e<	9o���)n4M������>�����0�E�����3�Aj�����cm)�$���u���0�unI��&�xq�����(�.���.�����<����[���0J�i����&Y���3��V�$�������g�������6n���t�j��/i����M���UD���Xs_7'���%���Fs2�ZK]N<�us��)����O�����)�yV��e��gY��NbI�#�q8t�'UdU9f?^�����K�~_�5�lC��e�l�"�O�<K'��X���:Go������x��e���Mo��f���/���7jF|+��$B&{��noT�qK��_`���c�6�7j}�t}���f��g\�2R�|RZ�����i�~����^Z^R�&�?�����v0�M�Um�xF{���f[��i�4�t�����l��������a]Jq��+���m�t�e��\#�2&��("��(�$��JEJ�zIP��	��������X~q�g��*N��e6���R�E\ke��,"Tk�����u�I"Bk���$����M����6�s+���������Um����]�����r��-[��#��>�x����������N��OJ���oO�cA���"�-����I����Ml���&�������0��1��t����&N�Y
R��1���7>X��}6�������O���V�WuT�sv�����5���:b�^���NHr6H����k�:9�$9�d>#]���a'���8�d)H��g\����_����Ru7��n"�~Y�|��lv��qX�<;7��$�t������]}
zen����Gu�-�g>�>?��u���	�����$���N�g�q�X4[���M�X�d}��^V���V�:�d��[��Nb�uUrz��m�H����]N�s�g���)n�P�� ��:e�M��QDz���l}|re��I�1	=:O���s�=����SR�@���������@�g�[_����=�sl��lU�x�|��3����6�Z�4�CM�8�f9Xm�����{l>�m�/��U�0���h�$���&E;|���OV��$��eY�7���m�����(4-��]��������Mt��3>ng��dJ����q;k��������M���6R�g��
�����m
�����$a�$��enw���u�|��5|�gCA����u�l���:�j�~�����d$g�����&�(���[qF�R�zN[wf����;1��l�Y���X�-���$T���C�����+��g���#E�n����#Ek��Q�d���9�1���n��fH>��]�u�9���2]�z���2���Z��4��H8�f)H=���v���@dee~T�JN�I�MI�i��&���~�oc-�L���;�31�;���F(�4�����m�z�XE�b�G���l���`���.����ND��q���
��'f��"�.< �#����O��Y'	"I�����lh�w
�d�f5;I��A:I��.����F��&�j,f����w���&�>_�!��w�};d��;C���Bh�`���pF�gna��U^�\\r���F�YhF��5Z����fi���S��^�N�<��*�iNw|�N���V��_����?��0�U�u�=����{�y�Xl������m�NB�`��n�wB1�����=�����Sr�TCL,S�ic�D���0��^��[Q+��7�@�2(�Z����������[�=��wn7������we;�N�z��'*�xWF*m_�v���^��e6C���Q�~l�<����=6�G�M,c0���PM,bk��N:7��Z��h���[r$a����'��������@3�T��7�.N�H!���VDe�3��&�K#����|�Y>������6!�����&�����#�+���5�r+s���7(e��#]��G��X�@�b'7�7���i��j3�1Xs��`$S��V-��rF���d�bj��
�?�DU6��>}&������1�&fXW�C�K�����}W��8u�����N���}������&f/Z�0��l �||.�K��1�K�0
/Oj��jp*��0�/�
 �v���;�C3�g��Q��S�'��b&�h���F�����	�G���8q4����Xi��d��XxI$l��k��!�8�}����	�J������)`���F�_����#�.�G���_�k��F�������O���:�����
�Z�r6���E(?o�g���Z��������ax��X2���������;F��l�_��o�A=:�]^:�1���q�}���I��k���.QP�mS���r��
���yY��7�����Qvv�C�GH�Hjb��?�@�Y
��W�r�.��{����I+��C�a�^p��&�Q��������K��d^�v��l0l���^3-�Rj�����k��hh�&r7GY��9�T7j l����i������o����_\�]���o@F}���#
���&Z�?���-c�1uj���}�5�@C�����CQ�rj�#�!��q���e��C�6w�td���Sbou�!�*@j��Mz�a�)l�#m���#o	�,0��`��)�����X�1��.�h���������O�a��O��3Jyy�����ct���c>�L���!���toWM���>"lN(�(_!rw��0"�`����D��w��v�(*����0�N��=S���+�F�z��V���rX��!{G��|���f�<��J!�4��������e
XS��6�kx-�Z������d��[@��gW���VJ�y�B�����C�yy3��!����!F&9�nn�S�j���e������i��������[�tv(��P�����|�-[���d���I�~PP$r��0�^rJ=�����=��U�������!��RH�q��dQ�L�*��������9�]��o��|,t�/����xz��
�����euA�y��}�>v�p���l6�l6�:h�I#�{��<BrU}�
�H������/��y�����&���������O��cs��ry�	_�%e����*z�P�DKQ��v��{��n�1H�vk0G�x������K����xf�D]
(li��h?^���Gqz���9:�:mf^f���,��=wMk7����nag���L}�DP
1��������wP��}���L\ ?��5��:��]���VA]T]b������9)�,�b3�ux�6%l+�2�{���������a�Z��e�b�L�� �=<����XB�8��S����X���R�� ��I�S�������z3�!��3lZ������I�Ac���X�o��������I`�b������i8,�):|��O�'�(b��7�����-^~�l�?e��(X���7&�7��
d��VB{�Xr�3��.x��������1�S�\�[���<�g��5����h��������l��A��g�@���^�T;�a��,�=���~�[���a��3Nm�'$��{���LS{l��[��#�����A��8���D��6��;)���[�����x���Lq�:����;!��1p07�!�Z��
;:�t�!3��wFsGx����>9r��@�I���w[ZF�r[��@S��
�;�;�DP[6�xp�A���c���mru��P�
�����W3�2�Z��d�;,��E��.����G��������#Q����:�I�bp�3~s&H-���0{&.��C��G�%4"�s���j|�4'dS'R2F6��>a����1����c�"��2g_*��mN�Z����j47N�I?��GlE�v���&����f�H0m����2='Q<�M�A��:���n�^D3,Gc�{<u�C�al��E5�5�;F���@GS/S�u��`����I������a)����.�LhP�����;�����nr�Hy��p�j�\!��l�B��}Y����/���AG���y����
/o3=�����O�.�����D�h��mF�-���VJ~15D�:x�����D�1�����r����]���z�gg��#��qu	�H��vb?���zvP������S�fy�Xw�x�
7����7� ��0e���y���_^R��������5� �2����������eBW�5��+�
O�F������J|�p;TF��{�Q:�����$<W'��Lx1Y6L++�?�����|_������P�������"��1u�m	Q��r��g'��/�?���ot��2�C�������wP�/�1��!�Q���N���a����q9�X?�5#f�^(q���x�gI����8����?��9����pQ;mR�����4�/ (-��\��ib�8�G��e`tK
n��Du'���\�n��?�Ix
"ow.�)
����O�"���/"s�;�����N�\��N�������5s
^���k�����x�Q�d"w������n$��$x�)�'��j��!ls#���B � P��������a���C��Ab����Go7|��[x�p�T�[*�Z�wd�f�
����Q���-�k����X�
���p�����j�!�Z�$�h@��v��U=���s1u�$r��������q�\|J��1��t�-����������k�zdo��T��ev^��e��-�Wc��I�����������Q(e������[���|��-iz��W�R(TJ�������������U�sU�K��O�pH�&��k�?xU��^��qo�u<X�v���-��k��6�{������RZ��C$I	�jC6���h2�M�+��������������w:��0z�k�����F��i6&�<���m[0��I�]��-���2{���k�y�3�O���l�,��S�[����Cx��	�l�8�9�N�q�<wM>R������gB("
0����"�Am�T����:|E�4(��v��_(���N�wP*��%}�4�����b8����]�H��e���u�����)`W@	��?���{S]������.���O�5��_��B���P:DO
a�{z|��Cv64�_>�h�R�P��U:&����+��I�)M�0r��R-�aXj�G�E�`���P 8�������?�����L�)K��i��@���
��75�/�����X���Q���!R
�7�W�Xp,���-��B{h�m���i����+N�JAu�W
E�}�@�L1_|��w�@b&u�N�X���}����5wQv��)����k0��;�����.�M#2��R=8.�=n�dB+�&��y: �/�����,�07���]�*���;0*
����T���FAs!�7/��C�u��u���:9:;��t����><<L;�D�$�9_'��P��{�pg9�x�A���oW����/<�2��e���U[��%
�;�����%�ga��`��C`��9D+�M�G��#3��1��+��0 ���h�<�t�I�F�z�.&s����T����"k��q���^����C��;�fH2! e,]0.�!)os�����!��i�L�h�`rqwTdY���h�@�\�zK��Z�!N3���H��R�������{�M�cY99ge.G[���cDuA{�F�4k��h�&t�H�
��\���,27���w�����q�oXl|�=�S�A��c���W�P��#\zy�u5)�z��yf��W�&S�P���~�>��qA��&�!��FRW�	'��[X��C���{�2�5�I*����;#��;��)�����Le�����ag~.��^�p��;�u�������9�^|��Q��S�5�����Fc�h�!��ny[��t�#+�.2�
�����N=}W��.�GpU]'�>���:��\C��9��C���y�����4k���Ep�����>��B���J_�Vs��	���928�(�����~��9��E��'�}'�Fd4STB2��}E6G[������R���=��}
�V��9b��{��U�o%�]o��50�b�/A�	JI���������!�����7R�;�P����K�	�'�_�5�V��8��E���gYx���8���KgB��d�e��D�	�`y*v�6�8�Z8YJ�r�[�!?�(�"�B���8�����G�=-�8�b�y�H]��[o��:�L�I�g"�3��?KGFS�gVs����nCy��y*3h�bx]k�	����p>7|n��\���<o�8U*��������q=[���:n�����M�zG�:���x��_4>�.��_�_���d�A'�C�N�����<q�����9>���O"&b�
o!5�P\�b����
�B�2d���"D�Nlr
�Di��$����D>�zy�����Tu���N�rbS5��M,L��{���d47f���;�i����������Y���jy ?n�y'�v���lfn���:z)xYme�2���K���Y�*P���N����$��K�T
��m�`�8' �/%�0{]P=��ew(�+^������X6v�����1�
�u���$��U��y;��]���'t+��m��S�W
��,h�������J)�J\	)�p��',f��Y����S�m)
���)%�Q���(���_�;+���	��lp��t��F�.�,B��*6;�(���^&�dq,s�����(�g���A����W8Y���{�Z��e~"��2"������
5T�b�w�����Y#%�-�����4p���r���o�tl���G3���g�,?�X�u����������E4J�p�h��B����@�{A�L
�&`�~�������t����)GI��!��4����^������)������.��.s�����;���&d��v��%#�M2��Ch��~M#2PX�w���7��t���sv����N��u��(�?�p��,zM�]���P\)l��m��-����i70�@;�jO�a���}D���C{f��|c(���G��_�w���mt!���m0�E<�4IW%�������H�a�<m�_1l��	I!w�VJD
9�l����O8&G'��J������������_q����g���L�6�!�t���t�}M+�>�����Z�����t
�4?wL:���f�W�s�Y�+�>`S`
~�.iFN�8a�= 
��������������7�Q��E�7���_	^��}�e"X@v�������.�]b����2��e���-d�^��<kR�k��[��W����#?�����gdC��K�tz:�8�}#��.�
����V��i��jsS`O7��{�R_��v�Z^W��.��L�Z�]�\�+���:��b�'/C�:����y7�\�����D'*8Q�^��{�vy���3������Os��D�&*6Q���MT���bM��f�t�E����S�q�J�l�e-�h�D�&Z��e��P7�K�JIo��e� �h�D�&Z6����M�,��'�DR�/��IQ��)Q���MTl�b��X��]���Lu���e����D�&*6Q���MT�W��Ev�>����R�J�l�e-�h�D�&Z��e��";S�mbEv)H%Z6����M�l�e-��,ub7�$��
�bMv)H%Z6����M�l�e-���k_����6�,��-�h�D�&Z6�����i��/���h�X�]V��Mm�hE�(�D�����fg��M,�.�D�&�6Q���Mm�h���
v��~}���KA*����M�l�e-�hY��]���l������ �h�D�&Z6����M��O��uv�F����r�Jm�hE�(�D����VR��x�N_�X�|J6\�-x@0C���B��\iss��%����������5i�Uh�Uh�5i�UH�UH���GK��$9�DTr�TC�����R&�,(-��
��_�Q�%���i��*D�5��Y���8/Q��;��xd�`�&}[���El���I�F^�
�\K��+702)~o4?Bs���2����7�.N�H!���VDe�R�T�-Q���t)2��V�u$-�j��d{dvc�09��}�K����������4�Y��������d����'�<l'�l|���tFC[3�����3g���3�z���cp���?4Niy��/�u2x��.g��:=j6`H���1�t,���eN�K�	�6�v����I���U�26�����y���rW����zt30=2�Vn������mH�$�`���u�� 4����U~;���gWM�Sh6���I�G_�����`/��Q���������q���M-[��xJc��~�S��n��"�������y���<������f^;����#(66G�;1uw�����A��<j���b{tR��tXdlA������I����2�]]��h���3i���~�%����o�C���������g�#��������b���3�]U�����������M+��C�a��t�����7�oi4*�k���
��p�G�5�������]�6FC+v_��9v�r��R��&e3��m�F%���e-;_K���]��(Ce�l,
u��p��h����Mz�a�)���6�����D)��#w��{Jv�Tc(�.���O���i��B�'��@������,wo���?���6��ub�g�i�v6����v.�a����mX�9����Cj@��7��(��xp8���L�V?6����l=>3.���7�;��$�`��G���@'�K�x�Lq������>`G�%gG��h��>�)�� s��<&.��������@��D7�������DvAi�������l^:x*><I[���-�v��a1��G����?��&�`�
��TI��K\�����2�7��7$]Q
���7��^i��"h���s�������@Q�?(B|�j�HbV����NCw��w���o��k/_4���U}�&R:J�e�:�������Pj>�����#�X}�����/����"C��]7
�o��>��Q�-ZPF�?n.C��n
�����>����O��;�R�6����rZ;���;S�4<�:mf^f�S�������w6V-��3l��T��EKY:Z3�ux�6�����`����:�O��	�nj��h��C[`q���h�:{����X"sb2Cq���_�Zs��1�Zh��"
%		,
4`}?J��}:?qT���j��C!�x���Y��)���7F���D3�'�S	E���r�3��.��^�Akd\}E�9`������=.�fv�5�d4f�x"Ue1����	��>���6��Rb?���J@�'��k��[@���d�������<#�:�6`9��P�_Y�%�q��1�mok��N���C;dE0�b��3FFs�/Y��������`[���I� �`�kA�����
��:N�:��e��{7W�3�1k�vr2��	F!��d�[�"2���bA-����`�����`l?r��P��SP��G��?���iN�d.�1�|u~^]~�����U������X�&��v��"��}1$h������ldh�f�O����H{<�A��:���n�^D��06u������g����!jm<�5S�%�����A@Z�@�����M���N�
���r�������}Y������
/�.z�����w[�Z�[�}v�Q�Y/��QC������%�8�������������}b��	��G�(|u	j��^�B�a�;0��������Z@�����	{Y������������5�X(W�i;�o��v?ct�N�j�FU^�������6���^�MLE�(�to4J���4]��\�t[3��xd���V:�q��������Y^���h�Km��F�W~������(NL����������x�7:�4_��%g@������
F��&&�q��Y�U?l�=6&[�����_32`�,N��3�@
mL���)�yq�;9���B?�����pQ;mR-v���4�/ (-��`��
K�x�GNJ��2�IK�������\q6������_'~>]��"y�4P&�������G����@Sg��v�bM:������>V� �t0��$d�4�2�2���Y�������t>@<.��}T�<��_{^
d|/�b����$7<�������Rj^���T�/������n�1�J�Z����BA)�������cRwG��}6�o{�4L_����
�={�7����Bd�W)���T����J�Z��R������J���2�&�u`�f0!y�\�����f��������{�JI)�J��R��r��*�VQ:J�T��R�}�#�@]�c�TI�pH�#*a
��/@#r�]��eH��LmdY��(;~�:����7��#�z.�Q
��rX.���A�����q�������<��_)�`K"�%A�$[:���� [�RXC0�1�����a�t[��g�1����Mz#]�<��������1|�`^uu�cm=��iE��Jv����$�8�O��\{{�M����n��X�p�Hn�4�����H�tu�+��M��F�G���
p�^E���z�2�]����J�PP�N>�{�rO��'-�#v
����'{J�����]z�x$
�
�&��0F�����N�
L��$�us)���wvL��>��:��k�S��.����0T/��6�����~;;�J��wgg����7���u3��bNr���������:T.T�#�p����b=g�>���0~7��t3�Wpg��^!;�^���^�`��3�%/_��v���-��[�H`��o���0
��$^��16a��<@�9|��y���NH�w���q\�o���,��4'��9�B$^��q}
����	��?���=�e0th��B]�����9��xJ�o:����D��z4��
�IP�4���^��[�e��5<�<C���B�-����`��ym@9"X�2�?��s�t����f������M�+mw���0"�p\��.P�����Yo�Q����������<|��
?[��/$>���G0�s��R[,n�Z,��5'b�`������dd�&i��|K=�*:	�d����-����i���	rH^��/��#f������3�NJ��D�:��YG�S��@nU)����w'�gizH#���3��]�6
�dLl������Rk;���
�$6K8I5����*����Z��za���7o��d�~(!�w���u�D e�:�1_,*�H�b���uQF6ie^�:f"�!7��!�,`-��E�C�m�-�~4��#s���ad%��>���D�)4���$t��s�������4�����8=n��L}dO9JY:fT�L�������$��	�A������	&��D[>>S�=�-���jq_��W���	)�]���N��|PB� _	:1%f	��ZB��dt���5�k�7 �Z6�62�VK�[@90���/�S#�y������p���9������X1���+�.
�@��0���Z�:2�H��/�J�#�4�E9��S�)�6�5�ld��������Y�����iP��|X�c6T:�����W��>���Z*|��N���_��!D����?��f��pxw�Zf���r]��`�]�
l9����.����3\�o�[>��r���~z��y!��%60_�8����c��'g>��_�u?OG�h�I��_Mj�X0 �!`�l�O?V&A`3:~yx8���<�]44�����y�d��'��&�������@A�}�~��c�>�.?��E�~|�c�P�)��>Z/���$�J��.�������`Ww�(
��$��4ta��$�t�3���u�[6����O1Sr�����|���}J��R1�A��.�BH�,��-���O����O���+����{��?l��h���d��b�!�_e���u�;����g�+�����VJj1��������AQ�/T;�6\;���Z����vP.���eM�V{�e��������=TW��������2���e
`�g��G���3P����4\����pn\8D!���6�F��L�>����S�����J!V�8��v�;�7u*��v��>����U���.u#����x�UEQ��3�+5��u�dZ����1����&�vz9���,
���3�� |-=����d�����T8�Y��*T��K��k�>�L�������w:Q������ky�^��l��
���o���@��E ���X@����j�Km����
���D��[��7�9�r���%�*f�:�`U��Z�1�y(����{�bd���"rt�@"F��><�U�YJi����	�*]<j�.*cJ,C��Da��v*�1_���2�����>QkF��L�0���'���#�������Y:����0p�ar(�o4}�-m�`�6|$�!������3<�j�#�y5�EU������Rn�1�N�3��j3`��}�3�>A���z/��'����@e��r��.������ng1v�;OT�Q�t1.�t�XI��I�-�/8-�U��`��/X���2}�EY����KeD�ODB^9~r��E���2p�i/����E�@����r�IOc%Q �����H~�!9*�6b�j�lU�q�jB�e����_pN*	q�N�J��������9��&�]>q�
�V�	q�M���R�V����q���	q#�[-2�Z���hy}���z����,��������hFHq��(��!%��p�C���������������G���5�n:��^(-�y�c����e4������!����n�����G������}��C4{~} i���Z�0+K�V�&Np�^0_j_*��B�e	R�J���_�Zw�(���������&z�:��9L����������m����B�QH[���o:��Q�d����yb#f`fc�X�)
�|�bt,.)8�p�W
�Ub��L4��f>�&�kS��0,vK���[2��W�d3���{��o��nt1m,��w�@�P�}���4��s��~�����bf3K����x3�x*�y���q�DW�%����$q�<|�	cr��wZn
/��IJ�����N����=u2�����o�v1/�{3% {���E��n���|~�����AQ������a��,cX	��;P�	6���e�s�v���������d~����L���2AJ���u�[��#<��J�y�������j F����#A�c��zk�y�7&@����^X�-�y�L&�8U������t]���]v$�Lp;8=�$5����1���f�����\���X����g�S)�W������R����t�	�
��@1�D�J�r^U*���gzi��Sa�L�4V��-��:�	�*���R-.@���#4G����#��9����,���(wkdvQ5�%���[_�xT�[���B�����"���������[���������c�:MQ1�k�a�H|5_�?
���=���?������?�����-o�H���~��3r\k�^�W��|0������uo�L��?������������{��h��������@��c�F�?�1Hc�9�q�1<��(�HfBp������=��k�#9%�����l�Sf�����_e���$�������w�N��K��~�������^�z
��^���-�k��*���5b�����k|�MX�����Df����:��T�������N2xp|6�~$�gY<�Te��#�t��,�i��T����`�j�_!����C9�^K�
Z�L�W`j�{�
��A�QlZSBs��X (�^
G]�z56���P�S�xKw���7R6C 3AU�	��������qk�������~�
�Fg_+�J�N���P������z��^�\�S�y���nc�NG���[R��
n���	����K��Kdu��8���{�~1��ow��vS���)�w��5��F-qz0S!����$0��R��.��6���,��S���\'a�x��e��RP���]0�����0D�
����Zx���ia�!Z����}^��V��`9n�1�������vz|R��]~�����s�����\�=5��0��/�����C!@@���t����9��F}:?��7������q�Zv��9"���w��~)��r����F����;i����8���&t�	���]5�' �����%�\'�)�����Z�Y�P��@�]���w�)�3����W<n������[?��qJJ��+`�)E%W��q�����9��r���KuD��?e"��C����h0�OG'��0���gTTSD ^�in���Z�D�4�`�a�s,Z�e����)� (B3����{+�v~�z�S_�_s��W|��\��������-���6`�����a�����l����Z������y����1q�b�}����k����'n����4��T���`\������Xw������?������?U��w�x�x���OE���~B/���,���7�E��$�y������s*���lc:�>���q�Rg��vV�p~>X?������������(.?�����`���k�
�4B]�y�%T~.:t������%�?e�(�������+����~�}
LP����<c��E^U�2Zu�_��qvn��>:|��W��Gu;HR��:���6e~B)D�������zY�t�����T��fe�����;"D�#�m#l#������j����Z�f2�T�2�dh�t�����q��!oGC�/<�����A���l�8%�������/j���hg�=���9�V
�����^�[���J��
��5��PR*4a/aAqw�C��,^�x��ZX����>0T���5�(���������~N[�e�Q�����j"���U���'�D��E�?Q����G�������io� V�n��S����������V*F�Jp�%���p����_�_~?�8��0�/r�!��I��?����yW���Nj��'���m�V�3'G�N��o�0����$��f�l��0���v27.0���E����]���>=����fv���G�Ui�W���1'�o��K�{�{){�q SO���UU��-��R�]�?�-*�]��Q�rQ=(tEu������w��N���������j1*��:�_���I�#7�G|���e*����
<�%��Aj&G4��9�O�#G�����
�}�n�!��(<`vi7�r�^�������
�W��'�pX��m�-�s�UhA��acj)r�Z4<��C��p��7�+p����/0��K3�.^7!=���=r�,C���-K����P����)/�}P�t��j�������Vl{��mn�@��d_��t�^�u���9�>B�6'�������0|���%Cv2�?jx����5�
t�N�������Y��~�m���A��������g��9��>|0�`����FWP��:5�����~�-
S^���Q����������^�6�>��Ai�,a���������9�h&�����-��6]�Sz����z���R��Wf����|����M�B�������|��*~�C���x$uI+�b�k��
�)FOJ�DJ�;��7q���6�� ���C
^�3�48^G]J��l���U�r�@7�G?J<�������
��A���Z���J��EsC� WD�d�K�\�������jy	�rS
�N01u{b
��N�@�] ��>��;;��E�W�0`P��GCs>���k�4�P�yN%������]����I���N��+��������G�)�d.�'g
��������C�p���q�9n���t�M��`[��,�Po^6/���XY:���-��I����2+���k�.�w��p 9�I/�@��Qb�](�8E|��%"���B��+��A��wz���je�@/kU%�K����T�r�~�L9�4�}M���{���eD�Q��K�������!�%�FL������G��c�`�����bLub��SGx��u7
j@���s���>\�������3z�����c<S���"�4!�c{F��e��v�R1��v��~�����`M�`�&�����
\h��Q�Y;9���r
�7/\��)�%�~m�����t���_g���[ L2�EV���a9\�%��(6�������$��J�%��'��a?�]�jn�6dw��o�����m����4�
\th[�;�<^VM_OD�����s������^�W�����~��)��9"'��2��*�����$���!��!'1���II�@�CJ����+G����0i<� �le�Y��#Pu��ll����pm�P���dV�k���`�?���|x��5l����gv�` �6�
'}#�.)�z�V*����T����1"C�b���_���+�.l_�#g�\�=N�����G�X�B�g�����{�,�(���?�����t���Yj����������zg��c,�����=�0�:�b�������{��h�:�����mG(����-�>���>���|����-����J!����$���4�{���;�[����"XW��A�PR��bI�v
EE��Wz�W��\��������Rr[��%X!7�T��\!���O��\��tL��+�l����<�]���xD7G������'��c<�}�iL�Fh��~{��]��8��}��i�����-B�[�B� �1(����z�q���r�Yl��K!�<����p��
q_Qt��{q�W=p������!���Q>��T_b�~[�M?��(P{�O�����[)�rB5O�h����_>�M�i�7�r�r���
Y������g]�/tb����9DA��{x-DN0C����Z����S6p0�&0#�;��?Q6K�p���*�����%.
�{{x�SZG����h"���4����05��~��F�W�N����X�������g�`.���H"N���-l"m�f�Ao�9u��L�!`�6u��txy��V�*���5�q�7�2���A����{�V��%�B�L�B��1�I��#���R����luh������2��2cx?���9�A���oC��xQ�-Z2P�cO�-1 �����rr�r�]�NyZ�"z
�u����
 e[	'BLI���&J�w���v*w���-��j������G�Z�L����O�V��<9�W�'���^3���#^20�D������N��������v��0�g;��"��Y�H8����F"��A�v�s����F~������R23kwb�������[����w��`"wh�A���JL4QO�V���`���C���GDPp��x9N ���J���6���6�2(��1���ZvF�>�n�u�W43�d�p��<�+���J@j{kW�mi�	�QOz�'�����(��&������rCi���V�U����F�xr��41Fj�10r�#����Z���^���[e&���Z�?m�-�R���J�PT��c���9Z67s���i���v.�i�f��m����+��u�[��.S{���ul]��������:X5,'|�.��)��������e�R��������%_8VV�nG�=H���i8A���ug��|T�^����s���m�$w�.���4{�kl^�������;�Y=K&|E��{�5����K�sm�&f#���^�����8g���q������t&ER����0�����x��j��aZ�����"Y�S�>�|�a�Q.�@�>|��R���$��9l��*v��q�
nt���v
>���7�o����U"��w��y��}7���,)Zu�y��'XO���{�����<hj��U=��O����.�����?P'��_��G��$���M}4�9Y\Q��Z�)��lE]^�Z�TA����//���#���Vt�e�G���SgP�-W��t7�L�xM?{��G/�o���w�����Tm
P4��Z�& �N��^B<�C��J�%��0m1��
�.oM
�A��H��X�J��7��F�k#9������o����>�����4J��x���E���b,~ �?=�T�7��`�G.�g��*
��r��?����+�/�������,������'�������;�|-w&2��4�����k��J���/9�$������n4���d��$FC�6�&��2H����[`�.�����8x[�q��������w�M���knUj��Z|���L[B���0:ppdg���V���*�����L����3��Z"l��J���8��iS:�}�lUC��%G\�aQ"jg�)�V���&x�J�W���m��
��N#kr�Le�4��t�	�������'��������B�q��T
��\W$��}hb��j��]f���y1i��H&.�����_V��6�Fg+���pG��U�c�7E>��
�<��8�GP������S�����)p�Ln��i��������tn�6�bF7���~���������y����l���*�k3�E'��m09K3:����9	{w'�j������N�U���.�����F�u���������wo1��ql*B*h>J����I��'�V��(C��I���o\C���Y�IM5�+�����RI��o�����u����u<EMj�����x��0q�i�B�{L���W�{H�u����)gG�_��>��A�uI�,.EH`'��G��*^D��3j�k`cQi�����yJ*4��y���y�R���q?s,a��s_1
���0�=�����GxKh�	L��ja7�h'E��B�p:��@���	�6������
5��9�`QW9�eC
�����LB#/��;h���{�m~Y���������m��H�+&x�Y�J�(���J�vz=@��wPlf�T��9�>��$�������	9��l)�
�,���N=��c���G�kTEa0{�8�6�ez:��"�p(�I�
�lH�Q�s.S}t	(_��FA��p��tXt��W�i��-���w �K�M���<�(�\�}��_��6�+Q�{�a���0N���`�e>��P�5�'�A ��3"�o������D�?(���x!�&/��1c�;�J����iQ�q}~�S��pU������M��$!����,�f������|��(Az��n���5=��%-���^@B�na]�����2����E���Y����� �'���T�a���T�R�N?�fF�y���w$7��s�4���i�Wn���Z.�������5P�T�������%�N���p�	�8:k�5�H�)[��!.mS������A�L�O&���4��������0���bW��9�}���,�g(��Pc��d�b6�j���-�Y�`i�h|5�.7��6T��0�:�)�t�ib���;$Lo����'��
���;���U���~�o����WBR1�:���o�M} �89�.�("�m,���, Q����1�*��A��H�` �2J1�>��[��y�?N�P��G�!.��@�G�aWf	��Ex�}�Z�Q��x��c�`����nYI��,"|�������0vv��3�KjH�iN�
��]$b�!��\�����'5F���E~\KUp��r�����<�����`��Yt��B�iU�_Z�51%�Lg�~4�BK�-BvJ��?9\���n5Lig�A�����StSO\d����4d�
nRR��M~���)��-�y��j7��#��������0nf\�������$/���O������^������q�VN�S	�:��=��q�����f��}�	$����9q=�U*}
���Z
e������tj��
9�������5�7~
�	��\�y`���%����!���]m���1���h_��F�c^�n|5&�	]��(}�����I����:W
�`�H����6��Fo��Jh���J[Y%���������<�%5�[jcoo�\�;������5J<!�J+�1Ba��X�����g��OP:3��R�!Ko���Q�{�ya��
�����dBe��Z��k���"�]�I �	�w���*��K�d�
�i�����y���*h(�����_R��\vq��d"�-9�UBXE�z�::��[�t�A�wt�s�v��'��[,�C��B���9��'���/��?�ut:����8is�
*l�~�������8���/�3���!g����33|�����}���'�������I�U�H�tZ��FVD�u��e�e��2<r��(�Lr����f09\�P����{:v�P���$M��H�������y?HN�[G��t��}!C�����(����a�X�;� �r``e��Y��U �%6Z����
T�	��Q�� p}Jp7t|s��U������K&}JSk�d��/�Q��� a����^�9]�^�����'�����SS���f��hPC��D�������[<&��b�B�Z�<U��D��=Y*��p�I{	"�LJk�4?��`H}�w���{���#r�W���X��������L���t���q�m���qz��6e�1��7X���)��$��w�c�"�Xey�N4G25�+E�Z�n�����a�'�3�D�A��Z� �o����U}VQa9���R��C���������\����R�RWbhs���>BiVt������������"�����3������{a&`���.;���
�?c��W�<�t�sI�����R\}q��$���������c�3K��D�
xx��7��wyL��������
*��<�}G���������zv���E������$�ht6~�������Z�O�q/�%��G[�-}CY�ry���mH�E��#������EhvQV�g''��R���w�5����<��l�az��>O�N�g��L��g���o[��="�����5���]���|��F�	k����x������y��(
�b�����������qx/�$`����<��c���D��J�����M�-=�D;��>y��?|-�I?zq����C��_�4[aS���]��t
����`�������J
�,�T'����B5~�UO*���-xL �L�,dKF<���U�D��������.�������������$�-�������zx$���$�b97�O��SH��gY'qL�7#��@/D���������O�1�e����U_?wB>1���W� ��~^���~��e�����������{���,~���v�a�X0�~���g�`�
�S���Y#N�����}o�����m��R^7���z�|�a�X���{Jt�������d�J#V��c������*/`?G%=Oo:pX�Y�������������l6������z3�;7|��8�S�!��(G���y�C)�������)�2�=���Sj'Kt��s�>.$@������Uc����$�l�k{�4�����3���SbU�o1���'��;�2K��Sl�Q�>
����J�X;'���_'��Pw��o��qj$��`�	@�E�\�Rf#Y(?	��'w��`����|k��>�YZ��jp��}������������d0~����`�����i�w�|/jTa���b�}�D��������J��wGhkr������3>�z1R�k��	��J?:.�d#Y���X��Bw�H���N�������V*�^����w��V
� ��mF1:T�~k7�z���_�_����_G^�������|r�
���Q����]���[��A_(N�B�������P��G�G8���)R�AB��|<��\�G����O��+���
u�A�T�%Tp�#�g��0������w��>��V�����f��ll8{|m.�i�������m��u�7>�8M��J3>���Y&1�� t1�rT3Fv�XEN��k�e��l�Z	�+�����j�@;i�*O�zp^��/vWCG����Y�K���s���	3��qv���l���y���=�&;�]��
�&��fM3T`[����*��eU+�o�W��|{=f�X��@�ZCf���v)+jS*�W/S{�>�x�
�����V���z�`p9�E�U/� �������9���G���9a2E�U����v�pS�X�Fq2��o<�;t�.��l�����4�������`�sP���;�����]]���-��l#���)�)p����"�&�t�������X����+�I��5>)K��'��=�Z���l(�7���g%r������h&�(J��$��F���b�����a�\2o����d��&]�Z���(c�*�`���,O���ifKR-�W�)�������_���Z 2M�I|���s����j��2�8k���3s�-q��d� ����{��j6a<\3����9G_���e�Kx'9��b��9g�`��h��M
���v���jSh��?\4��Q��:�h�D|�~�$����ez�:#G[;A�x=s^c7"��r~U�MKe�~9 , $�=�I������q5���Y~`��A��!r%`qFc��J�m�������>�;�������V��3G�'�T���g@�f)m/'[M��.b�\B������j��9�?�.
�pi^z���oY{4U
��$�<L����������Yu�w��?�:���tG��K��z~!�� N�td=G\#����1�8be!FY��,�H���FIoDF
q����0l;)��p�c�p*���@����mh��Ld��s���8=���	f>��ojri���Xd�Z��XG���\��g�.;�Sb8\G�
��iF���
5M&e��h���d�0�C��;�+��@�~�f�O���4���!��L_����z�������MX�5\���N���pct3�m	�����;���k?R~j2�*��a�4
�R�v�*+#.����(qq�]L��Q��r�M7����M��O|�\l^��	������
���k�f����_�3)��:���fA������=;�mq�;7�7�i<�����X�v���m6l��/���;�>2C�-�a���q���c�O�0NCS�����D�+.��FjR��1�b���3��Or��~��jy{����$�@U;��L�S�|��b��K��d\�h�����	��
mnm�K] 2
�3�@uk(t�Qp��T�#���R7M���b�FG�`;C�a��1/kM���@���i�I����&�G��l����h�������C��xd}�SN�+�5eJ��/��*�����p�xJ���@��J�+sx�`��wU8���nwS�e���)
�V���?=��x��M�Y+�!3����<���4�\|��|R���\}9�eg��,�@4�Uir�p�m�g�=���84nI,�}�M���VgXp,W[��!k�=�`�;x����t&���lA���,����� -R|b�9��������4,�iB��rZ�n<�J���c��Y"_��jJW&�������]��m,�3|���mWX��5��u��hO�6�.-��=��T����;<�<x���.���y.��N8�1GM���Qt&B�x9�3}�'3.��e��.���&��x�=| �4��
��!#�0��,���S��1S}��2��$��~���<B�0��;�:K�U�)��t���OaKna����.$�.�T)ql�^�.��el��SN8�_�H.����fs-r�i���	�m�U	�2�g�_8=k5��}4D�$]���I9�����	��A*X{��93�r��I��&W����X�_�92����y�:��������!S�����t���/�f��S��V��@�1%<V��c1�1�5��*q�5�%/�j%I��3�b4-1�/�~�p���<�a��}����3�����������mo�Pr1�t���xrg�~[�6���������E���V�?�b��`�5R��|����P���D _����,�0�aqA]q��WZ�w>b;M���H0.�o���+��'���Ic����+/����{7!�,��7u�����%��[<w����H�E�ea���a��s3��b�I4�����q�C XA&l2��$o�����#�?���y}�3q�e��a�lC�@�bm� i����5N�H!6m2q�B�5�������K��]���d�b{m�+�T����Zq�H�
^�oL�p���xH9�����6�������o�P4�#��s��
�8���9����<d	v�t�����X�y�(�W5��D�;SL��4��v��<�:m�r�c��.��In�bO!����3���\�S��v��U�U �
�j����.���������n�.I#�������y��u�J�S������u�� ��u;ctT�4�����D=�|)�&��#�$�8x�~�n�T^t��7�b�heXE�����X��+_�	���Xlr�*
��<t+�|p�d����%�P�g��;�������|#A�Y(��c��W���(Q��Z	�9W�J�z�������]'� ��
:��Sq]��w!�J~IJ
pC�J����g	�@��{|�h���l�`�-���/������#��c����0������&V:�8:q�����_��V(o�y_�I1�����L��������d"�#~�F���D_��A��Suz���!?6�X\������������:{M���Lrm"lh����De�+
&�C�{�����I�>p���T������'oc�c.C�S��,�����5��{���w@�{�	�>a��sp�k�"T���-%���R���-�w���!GE���o~t��S�]		S��t����!�]�QG.�;�T�=�C�3l�����Jc`]fW����7t��uO�Y��;33�i���k����^W��g�k���������D��g��\AN�����UA0n9�����n��8Opq����w����&�,�.P��h��'���&I=���x\+��q!���K���E�6��lwt���?���;�\2\�����.���u��x��J�#���o���	��5+�3�T`
-�x�`�u�/�(��LQ�m4!y]�`p��'��Z&�;F�X�����o����.�*�]��J;@78�Ww�$]V�� �7��[ed|���k�Bd����et5��"/
��>�T�%����(��k%��d�<a��v�p?���j��wJ%/_���2,�m�Z �K��az���J��;�#�&:ma:����wR����l [���P�6���4�yUm;��d~iZc���G�E�������Ibm���+�O�B�~������<�7&������y �<j��������7���yv���0��&������UP��=��%}�����d�'q�����n���!'X������p
<z�_uH���Rd|Y�����!���Jj���x_�-9�I����l�?���E��e�\�����92g�%&�L�@�n��}XXe|;>m&����#S;e�Y1�	\�;��:�V��`�y��vj.:!�|;!K�R���W!�Nx��/��+Wga.���:O���;[����G�5�B��p���VHR1x@���[��]"��\��>�������g&V}�� R�*����K���X6#k�j����G�O�+����|��O<��.\�)<Sz��:5C���N������X��qg�\��b��2��O3^�K<������C������#d���j�q����J�0ld1X-Ni�q�^���-�����]c�s��>���I�(����k��"��%m������;�\��h���'h*:��
��/tio2U���4^���\��D�������A(��X:�V�y�b1kU�1�����nnR���%��Q
AL�h��&>#l�}aGpM�$
�����SRlT����"�jnm���1d�b��_\C��Q7��Fu����F�XM��[�$�j��I1WY�����~��Q�9�����}������\i��qc���!V��>�s��t>�jm�~�5}2%O������������&�VdLv(��1sa�O��PUC�C	�������%lIB%l�@TA�Q�OU���#�;D�HI{L��2&q&���>m�{�D���3���`���A�������o�o�~wzD�����rUO��S\3�a�9�EI����7F�
��W�?
��6��}��8p�_9��0}�P���V��B���HH���������Z+��x���������_J���:���������g�{p��x�������f�q\��=��|.��c����0V� ��+ =@}���3H"�������q������q!��yV�����8��Y.W"���??���m���n�����m����j&1�wI�����:Y�5�=�l�Y��)�����F�W�UW#z���5:�0�������R��K���T*:�`�WC��,��r~�^�k���+i}�r�'gG��?6Op����y�H��K��f<��V�)1w���~���fw��}���]2E##k`��������Ir�\a�5z���R����T,mT��T`�a�@-���D�hsV�bj`7�&j�I&�>�8��j�����E��������W��z������#�o!������J�//U@��8�+�|e��-�)T^7��K!0� S�~����1Q�BE�%�i������i�������6����z��Wc�,�G��nl�p+�J�s�b��K���&���F�D������"i�(	p:l�����;G���%�����\��8�xcq�_�����u
�p��LeM��-0��"�����"E�+�����\��R��p��$�������e8�G�m����'F���|OW9(n�8(n,�
S�>�G��FE������]����I���O����F����g�^]^D6i&/k�Bjw��R'����u��&����(����Y��p��N�{g!�W�fQl���j����M��,|�[p-M��i��<�~=`��5�������E�������'~��5����Sl�h��Y9���n\}a�����gr�j�Q)�H#�� �������;��d���tN�L|_&�)���:j	IG=	[��f����w����*�����x�p"G&^{c�"&���[-&�Dq?�B�e�������B�F�3Pd�����7&oE��2�����=/+��{�s��W����T�=�@�5P���2�����0!C����m��go��]1u!�
�*<�0sjPx�e���~>�r�8�Zn:�U�'f��������|���z-����z����n�n�7����E
��P$�dx���u�����������*�q�%�qy�U���f8�JX���r��Hy;��10.�A������Z���i%5�T7�.x�l�V�~oE��M�+JD>��R�5��=���x���
��>f���We�������+M[n�������H���,t�"����|)��FI��i�d�`�y�d�I\1���a�y����{E�t��2�n����v���<��M�����h~������V8j`K�c��e%���y:i��Us�q�H��{�f���|�:GM����F�k�������/���KbYTS�3�G��_7��hrn�u�E���`N�]��Mn�>?�������2�7����
k��h]�FC{%0�S ��I�@��$��b���%7�/e8��D���.����3�"������#ej�h$���L4=>��|�>��
�R���>����mG�t���]� �����f�@:m�����+�v�GI�K �����S�0I;M�L�$���h�;o��b�M��<{�8�Rg4��x@6�x���|RLm��`�C�����^t�d��!q(��=���;���:/����h2�����Q���W6�0<�{O%	�9�@kJE�R$U�9��N�s��S�)���i�$
���)����AR:�j���8��S��J� ��$�io>)�Q,����HHu���9��3��6�Z4�@��Z���N���hxi���e7-�C3���#�-���	�`YW1<��2i�V����G��K�<;�I�m"���r�����3�J���3l��R���n��{��(�h"yE���\����{ �i��� GR4Y����'��	��m��v�
5��}���x� ��Q�f�����a����?������%�3��k���Z��r��WPe*�b%�8��<0��&Vt��~�.zGs����\W(�����a�P�B�IE�����;���T�]�8�x9�|��B�����T4�_��x*N�t5G�j��,�c��!�fHs��M���A��7^6�o�E[GbwF�E�\�^��Y�b*,���i����Vf����v��/b,u��Qf*
qA&l��~�h���"�*��zp�����.3}2�
�"����u�Cj}���/���{���v�������� �i�\F�����.T�l R���4N�����_^�T+W�-��v���9�QL�������g�TY9k��{5�9�������QQX������_�w�/���B>4o��z����.Zxv�X����h<&!��7-���a�8Co�6����/�����,C��eJKA4h��	`!�k�^NK�f���Y[K��,�d��9�%����Ba�r��4SJTCP/���:L[�]��Q�U#�4���`];?p��D�w�5��&
��u}x=����?@��6!-�g�+'R.����5�]�j��)����fq�,�����"]�i�>���I��c�h�\ ��Kt����<�����=���a�3�Zl���������2z�W���}$����"%W��
-��?�I��}>�J1��P��b4sH:� [KL}�jj��i��{b��g�et�p��e��f"9���e����g����~$���"z ��7���<��������k61�"���<����)k��mA':g/����\t�����_�>�PS�2�����1�2�(�-�zE������H}
�b0���Q���)%�'�Yz�I5pw<R����ZG��3��e*�������	3����<Uf�����L�v'��f��L�Q�q�$|����4�h���584� 8�_�rH>���������B��,N�Pf�����qw�HJ������%"&Y����3$�����	9���e^�NcM;CT8�L����s���#����eD��S]������"F#-����F��H��|�X�b���9e�d��(T��/S��"Cy��;��ST �����3���4.Zg��l[.��)��WIY��(pO���l��/D���_.��g�#L1��g��c�����@��>AL�����.��j�V��T��
��������61^������]��'4&&���9�D/5�\
~�����Jjs��v�B�k���s��e�Y����	'�����Q����c��M�u,:����_���:�����j�>��������8���]��j�������]F5#������U*�_J*y'y��,���N�]��u���S�����mR��/�p��_R�3������y-I-2���[�;�ur���Y��������J��'�o��8J����Z���� �$Y�\c���D�p�����E���K�w�9�n@���d���w���)�	g�!l�:-�rQ���e���b�k�
��\#�R�1�S�����M3�>�E��R�P�z��N��O�K������u�f��F�7;WQqV�0�KjS�__��?G������%*�������P}=W���0t��=���W%�������������.)���M�w��W���i��T���u�����:A�S�D�]�`��������plYSB��iIG���i�P�F�6��Y�l��\r�	.xb�R�}��(^UpK��"r@�I�����d(/jg������4����M���/Z�!����I��/<����:	����/B��s���rO��RJ�4����qB�����;���,hH��
������oYvKL/�m���L8���%�\v��c��7S
I�2R�����"vn�[���wLJ��/�c������,S����p��;���B�6�{��������L��3�^�?M�a�����������������O��_�|��3��{���3����W����3�3U4q���q��������h:J��4�8VL��������S1����Y&59L�t^,�����x�dW��NC���7�
.�i�]������u���5��0�@�paq�����#!�Z��I����r�56]FTt�yW������I������3����g��c��^��%Jq�:<i���2u����1���6�����4vf�:i=oc5�?����p�r���|��|��$[�.�7�����V��;n?f��a��?(.������u����b&�7.����w�F�|1��A�����L�0����9���P�Q�$�q�t���s��Z���a����l��+��������.q�.�.��,�U����fx�E�D�/sFq���g��sR*~NV�P�r�ok#W������O��
������"��p'��9wg�8=PJ�$>�c!��l�'�"��i"%���K%r'/�����Q�
���y�l��Q��Y#��7_u�����aI��"#]M����kF`�`yqxu���@K������|��>6(�84$�T��Nc�.�+�����0���4�������c����FIhh�Qz���
����ttB��TA�_�8�E�ibVW������2�w;���m�G,d|���P��'g�(����^��'�e�=��bgR�Z�I!h0�w�`�u�b:�V�����&!�q�q��c���F#���#VL�]�%���`�R�S�i�'�9�l�-,��ti�����G���e����g���=@[[����O����x.���|(�'�@�-,(?��V$�����������\�
��R_���eZ���	<C�e}���]C�H�����P��lzs2�i']��r��b�+:r>H
�O$5k�5<�~�Y���e]��K����e�6�Mi�������v{�4�(l\�l{�u���
���}�����G��H�N�����h�#�Z���(��<�
f>��17�j��<����y^|w��3�>�Z���]H���Q�����r�f�M�u���1(qH�&�b�E�T@&�:pN)��T���C-��l�6����PW��$�t�+�Wr�V�dRK-�YMR�E{�.S-V�,d�aQ��y�E��S�--r����i��>2�L�3[wK�+���#cH39�����(&�h��S��.�3����UM�n��T��g���jwO:������o�i��fq��_�6��.�PS�m��U��y�Hn�+�����{��p�rd8��z��CL���r�y9�u����EK�����@�������?��8�����2���B�qF����+�i����\:����U�yY�`���=,�c[u����*�����4Fk'e��gj���6�Z���N�;o}��:��1����������w���&<�b�����#��������`��T�.��=R)$)I��8;�dU�CO����
�B����d:�����s`�]VL���m��4�$b�q��yp��)�V�A���	����E�q.����f!�a�e�k�v���{�$}��F^R��0{���Y |Es����,����|��x�qMb����
O#��?����S��9���^��y������O~�0P��X��3�j�;�1s�R����c��y/>B�
s��,���@��.�Xft��)z2��kA�z�Pj��a�4���5-�}��y���!|]K����Yq4D��t���W��D������XUq��,��,~��C	�d]1��^�`r1=����M�(���c�%�2V$+�"5i�Y@������
�i�����@X���������#���}w������+�:�h�8'���:R��L9;
&5&���P�����m�j�Ut�+��A��P����d�\��OMi�>G�j�_$�ee�Z�������L^��zAn"J�O��������uK�
�����N����
�pNs�P������A\����I��,��
�,��P-���C/w��]��y��������,��G�6�� ��9�y�
�\�Ke�F��f9R	F���#qNU3Gu�h�/y�A@l�����9��P������S��YK��K����	�V��*N<�<��w����,��',��s5�]<
~��m<��9���Y�����>�raa���>���\u�/l����ry=W��pFZr���\bx��E�������'�
;-��~����������j�������Qe	��(���7=�=T~!��Lp]\(�)]�v�����T�&�~Hk?����iB-����y
��]�]QL���x>�H�T��ETL3ZQ������6o��w��\�J����?�����������Z��W��/�{����Jx��+�[��n���w�KE���S�O�y~�k"$�>��1�b���}&9��?��C���<�^0������W����������Z�n���l���d)�k������z��\�P��!�Y�z����_o:��oz���,i�G�r^{��9��n���'��7`����1M�������l���f}u�If�"�RvvE�|�0�H�+;�\�Q��vX��{�=��:�GAN$f��&���>�����8�>��:���B^'v���P}H�b@�'3�XH��,['��B�5�R�Nz���Ex��?E����H��z>�����Wn������<o�c����������S��{������\��-P�#]\Ov�qO������U��UXP(��CPn��=p�rO��M���Z���c�ND���7~����a����+����dy���*I���u-Q�����*@(f���S-�0����aI��X������I����LX�4R���A�atF7m�~e.����`��0T�uv7��?3Sz`-�G@�` �0r����y��a�rR��@"�bJHOu8@��8�Z/
GiB������B��E�[�����s$Q�y��@����d�������~��%	��FD2T��
�6��	���9�+ ������'Z��g�5
c�+t��6����m@1���F�&�L�F�Y<�/FJ�Otht=�Si8�[���[���u�O���f�P�V���m��h7��>{{nd��`~��Gt�(��6_Eo��)o�$,{'����M�X��7��N�����cS�Z��*h:J8O�m���y[���V���D(H�4��]�u�L�[�z�6ou1}-��4��]�7i8�dKL����)���l=]|.��5k�Z'@�fR�y��hV���ZV�k��
�T��ae�����&����u��.{�Ew�e��
���7?��z��n��l�6[mr��U\4����P���\��+�����wi8�/�0�����$�D��.�fsa-�f(��W����q>]t��}G��u�zw���������`�)�E*��������
�F��kKX}�0�E}�MG)��]K%7�Qa������Y�3�$�Z�>g�"��N�DI��:sK�PX��[F>�e�(@@W:�����l�������nn��`��/��lC���E5��*��UG�!�@h}�xs�c�-��b�9��Y��t���I��=��b���g�
��kV3�Z���-$&/5]��<�(��$�W������p����pL��d�y*��������$���~����+��e��M������h��TV���3����K�����l���0��@:�1
Iy%!]�������MS��-���������r�7�l(�dK��m�=m��� ��b�c��G�+�xa'��z�eC/��g��YM��I<t����f�IN���@.�h���`BJ���^�����M"�����|��=MI��SM���pF�`�����9)�x~���c<���g���B�U$�����t��_�/O������}!e��f�Y$�h9AL��j��3�\y��t������dApj�1�3��9#��I2Ch��VZ��iG��
��i{�}���n*\�����h��L1�h7�N#`���Seb*��'�C��n��*Z�&��$)��Z���i����?I7���&�>�]��G?�5,�$*� ���HC��y5����d�}���@�|&�h�g4pw�K*]u�����F[�~��Y��T�����^t��T�juw{{msss���mll�����/��}P�U��=�_����C�O�u��Fw���z�5�w��F����F��py�����8�1��{��<N��z�jUZ�V�V-��a�pr�R��(@8�������;�����0`J-�4]� ����0�F�`�@i�l'x�[E�C����3��q�Cy�1���D�x���76R�c���;c`�������+����Y�}����3`��3���QUQd~��.����)�2�)L�D���$\��TKN��I�w(\�	d!x�@�d[�b���`�lvQ_�1�k����}���v�"?�A�b���4���;Lz�=���r���[��N���V���w���^��wv�����A4�oW���_<����x���K��AR���K����������89.J�1���[����8%,��NB2��#�����u�{���I�r�a(��U�l.)����@�����������U*�������v��l{�����kD���C���S�v���������o���^������F�?<���ARX!-W��x>[�	�����Nu���S���T����;;�ex���N�����/���1�Q���y7�P����j��~n�~�<m���n�`�u����NB�n�X),��LM6���o8h�d+1����t��fv.�.w�v��N���z@���w�m�;N�n�mp;k[����/o'6���5�����q�N��EE
����mJa�]�������Q�.5����ew�������V*��z.�������������veg��E�B���m����e4DW��x������G'���_�������7���������8n�����0���_
:���jf�<��F������nm�N����\n,gf�\�fZ�%*���A�����0�����.!�S��Mu\�U�?`>q&{�,m��4�e��>L����P�$����w��+�����V�����6�=��r�@I%��=������$?�������Ib���e��V3&:!>��nx���CI[��3P����p6;��<������2
����,��F-��o;�������,3���r���v�XZ�W�'��	�C��M��'����������J���������WQ����T����&+��/�)u�U��GT��U|'bP����(�T8r����V�v�T���@������V}�N����Uch�!��,��_���	�!F�o�5*������0{!���������q���0@�'���,!X���_��>���|�B{�5	��MGg4��]���`�����<�k�t����u�.�7`�&��1���Jh�����,/-����`���Hm#T	mt��)�2��(��~@|��������{s�E7�*G`��j�E���B+��ZS��I��[f�C�r���T�B�W'�	��,">�p.�f�2C������m��|�]����z_-L�Lc�
z�R6����A���^Z�������{5�G���������p������%��;S�gz
��T��;S�4/�.��"�!gw7i�uqa�-�!3r����8��O���!��+oCHx��U0/������p=�x��S�=��e.��pZ�,��R�dN��c�(#E���6�w����"�Q}��S����"�F�Jy-�w��&���G�#U��|�G�|H��������m����!�rA5�;\'M`d�=L����d����Sb��X�#��x��HJ��1�}����0�%�3�������U���������nd���TF�O��(���?�4��g��7V*!�����Q8��,�	�x&7��2btt���^�B��[��:���*���eD��eg7�_�F����1�0�Ny�j��V'!Ua�v��x����� ���O��q���N�0�&����yR�L�o�������>�����"��`�&j����CU���g�I��/#�)=Ao���4����O�&F�c��n����������^H�#�o�{��'����w\�y	��hk�o��XI����T��X�m!X��,�V���]�l��v����vu�F
`y�a9�Z��R�`�T�$J IJ���	�.�$�s��f��[�������n<y�Y��Z�'F�*�����6b���*.�w��@)�E��a�5�Zp
�d�\pn����6!-;�����U��N
�bT�r��������I��VC|���w ��{���AW��N�L|B�n�_�p���r��_��"GL�
��kl��1���>0�b�R���s��/�L�t`~��~~�Y*���j��3����`hSm����t���p�vk����h;�X^0���g����6Z�I��"����c��S����s������D���^%Q��zG�N�����r��0�"�	��6�p�K:R��8O�5��*�d��4x�A,>f��9�]�Q����t�f~�!>��<Vp��g�������R��f��F�P���[�V�9ntg
���l>R�{���{f�><�����r��E]ik]'x��~�@�$7[;�0�a�P������}�J`��iC�:��d�J��g��<�\V�N�
L�n?������]��@#"(�Z�H
�K!dn���
x�M��)�DA�m% ]N)x�I)��G�^E�TZ���x_`�C����u�.���i�]hd����1L����+����&A�������|��5]�+aI�I>����U�	��o-E>�H64QI��EE�E+L
 ��@M����3o�s��|o��u���J�z�F����R}`�fm��@~�Z~���(0dC�b��.	��B�G``iG������
��}t���mjp��`�Z�Wj{����V�A�2A�5J�7��gU8+�@�7M2��4�@H6�Mh^��+O?�*@����Su�����w���m���������?T�����T�>���9�S��_����v������&'����A�����������A��wp���������������z$�"�����V����:���\�`�N;W'������d����u��/�Wkp�E�q���m�����A��?��>��W��ju�bN\�s������Tu�Ym�oT[����D�C��4�46��sU�T����9�mb��sR<�����[*�f/��^o��v������q7�j�����`��v4�N��������|TV�������e��cs7T��
L���.3RX�6��n�`����wo�w�:�^����5$�n���kW3!$@��y�����;N8�1P�c������n�iYgw!/,xg��Z�x>[�,��v*��1=:{���P�:W��S���d
fkFy��(C�5���b�j��uR%�z?1�$�e�U�Q�����3����5�
%_�>��W^;�[�������
������������u���H����[H0<e����5�fg��3��#����T��kMr������f�o�����Z�����5w��s�T��,�V��q/�t�"��H�����I���qZ�(;C��
�<�����M�#5��<�G.^�T����w�	#D��k�\u���]:����#��4ZSdOf����-4�5��90�E{*x��������Lakv��%O;I"�0Pb������&�A�C���f��(C��;�h]�(gF�q���qv0@y�R�!���3��"a������n�Hp��!����������/
�P�@�z*L	�	I^��vW�|�KJ�I��:by���H��Q����p���	'wDh��!${���v���;�����af���xwF�\���.t�a����R2sV��N��`��0��jSn@>����~���P&G>���v�UR��_l�S���#����=H�7q*k�y�<�� ���|�Gz*#�i��iKr�?��2�/��z�*3}Z���:��p1T$�gJT&���YZ����L�`l�Q�v��J��[j�~��k-TWA�H;�>������We�������
�7D��)��7Kk+�J9S�	\�-D��F�������v�U���[���[+DJ9#-�rZ�0�K��n5�G3 ��@���������������}�"cT�g�_�t�R2�LD/�+��8��j��u�0xu~����+���;���>i���!�3�n��0zmWq��}0�'wD2�rBY��f�Q��:r7C��rr���&��c�BC�&(����p��(0�����k5�����J��=u����}��Q/)��U�E�`
����������������mN��n��h�0�f3��+�TU��ll�53��|
��$��Gr�����t�����W��/`�wj�?M�[�<����h�xg�I��,mxS�A��8�$qBN�`��}����7#0Y��
�����'I}g�<j�Fxxg��S������|7!(��}��CW)����h�
��v./,��#����2��5����Yfy�	�.Xe�`tAW]O6�%XG���t[������
o���������W�k���ik?y�%5l�V������f�U/���<�>���)'�j	>�PR�2�h��d��b����]���V�DP��?*}���s��KH�]��l��5����fr��XY"���C����I�_��~RR��xgz�WW2{$4��reRg��s��|8*w����0�Q��<&	~��J cL)��G�t��%I�H������������t������~'����i���-G*�Y0�:=�zYX%�i��:����H�YIL��7�e������Z�z�
�������#���!��V���A����������G�6\�'��f�}t��i�f7��=����^�w`���^K*��
���'����v7��	2�WI����Dr%����l�2y"�cv�f8�bS2I#�&q�f�x-���,;�O�Vs<��C���0�3�Bg�����!��/t�l������{6�x\.��
H�7���l�g H��N��b|�����o
>Yuh�v����&�cD�R�����HL�}A��q[|R�_��+��b����j"���D��%�����bO���v'Q��"�Q%t�O��[)8�wwH�9��Y�v���Y�m�s`�@��K(;aQ�����K0�r����]���](��Q��� g��D�@���l"�~����G�`Af ����N���3�T�]�p�W�6`�,�,1�py��D��d��t��`k���������q�^AxgJ�QE�t���<�p����3~N{��@n����L��t�1��������XB�
y\�$
E=M��O1�[��1q��[1O��4j��,?�T�u���8��jH
.Tc������E�yz�B�;�%���x������������������v��;�R��D���>��� �eeE��>�i0������7�������D*�_/���������$^]�W��:cf��v�u&�s�y�W�������&����y����8eM{�>:{w
d��/�3��\�({�.���T��������g�O��-�if�7g���m��xx�~~��M��yd���[o��?~���<�<����/x��+2OC����Y�������)n������v|���<�<����5����37O3���?�g���������fvR7�h�y��";Ph��K��V�Y`���wo1�K����.4`���O���}rq��j�CjW�*<��*?�<����wvU?�|&���,���,��4���4�����@�]�.�]�g�]���]�of��U�B#�)�������,H����s�9���j��8�rj��v���\F��j�R�������Z \o�@���5)��U�:�R��e�/�v������L�dP8
�������}���Y���h��� ��B����4�d-�Sd�����(�3h�m6���Eo�j�ji��T������C.��w2L�~�T�Gl�nr��HN7!�^��2��KU��@}�4��
]�����g�y�����$^�0��&����:�;W�"�x�/����pp����a�|!�����@�L�����}�.�������s	�#�x{��u O�z&.���G� ���]�z�'�t���
Q	 ���[v�+dq�����wT�w���^J�n�n��� ���h'8��6�.<�������o�x�9
�x�B�y3:��U������}
���i�'���:lC���7
�nJ���fg'��f����f�1������HmZ����
;��`�J0���K��
:>�A�c`�I�sN�VsG�)�-�C�`����j���y���y����8XT^k�R���J�cc�`m��V�HV;���^~R-.\tAC����~�t���=`�/�j��s�1�����`��~y{�Ak[�9�E�����U�;�+����a�-[��a�*h�*
���$>�Eg�������A�1y��n��^M���vGJ;6xA\F���������^�?|��tV%\�o�9$�<g��@�_������G���%���&��3����o��'Y0�����L�`��C�|�**]0���^�2O����j��!;�����N�^����s[<o����i����[5�6�n*����O���v�k�����" ��A��<�$MN_F]i���\�����+��I�Q.��@!���4��������n����Y��W�T���[�t��>�hzf��_4}��z������U�L�Tr3t�m�a��\RKF���ysZ�����sH�Z�^�JBi�R�����g���q��
C<e��iH�tx��9�T�z:V2#�,����d��E���k��6��Yy�3�����&��QE�;��.��
�*��a�sjam_Gp@��>�]�a�F���/�Sv���B�>u�������|����"�V~��o.�xs?�������z��-�Uv�D
)g���2�~����i��E�Jk���E��;�e�i�,�b!j%�yO���l����7�	�������Z��ia34�( ���(�
s��$H�+]Y9��X���C����j�V�!*<�`�Q�6��+'����!����V���
�JC�V�s�������9���T9p��<�hU�N8�i��k��:�q5�������~����3^��d����n�5gBGl��G����d��q�93�[|��;u��p���9�R�;�4���O��������g�O����q��S�xt<�����3��g��Q����d�Q����O���dR����i�tJJ��3�8�Jz����G��H�9��sr��*���1�wG���FBH��q�@yZ�}E�4G��
o��2Mx���cf�V����	��04���2�Uc�*�
z�}�hE��b�iQ0Zp|g,��&����$�[ We���Pt�*�^�����6C��/�<p�_Q
W7��y�(<o��~�t�?���=B��V�\�y�M��ff�o���^^�6M*>,���Uj�m��7��O�,�W��Hm�=�4/&S���Ux��R-C�b�	~+���F&���������a9��bq+`��5P�:��T�x���A3���Z�:*h��},	� @�-��
r�u�jk��f,�����������4�F�I8+����##��i���M
�����3a���W��g�MS�=��q�����`��$�&��t'�+��+����!�.��%����Yr���0���k�9���:���rC����\��#$	�G��}�i���G��}*���j��u��H�&g@���w:O4�b�����\b��E~��;`r|��\��0��h.���y3�.��fB��������g�q�J��!�����1z�p>�.���-A��Wx������(g,��xNrF�B�B7�O�����Uat]p2K:��,����������pyVU��
>�!=0]P�3��>��cD��Jb���|l���:�����	G��$�+e�}��T�d��Jv�[�D�����W��H*�I(�(��b]5������)������?Ym/��b��,w�fQ��J��8]5��,7����4nF���G�����[�,=w����7&����(I��}�k�Is��n�b�h]|o��;�#��}Eh�?C�,F�]���m6�������qz^���W�,�����7;�U���(�<&h�#�	|&��g��-%����l�\I/��<�+�7!����1J{[�q�&)��p��GQ�U���M�����|��L	Q
,~���v`��h����DGG�C�o`��1�3���)c�� ���V��1gU%�����:�EY��<zG*uI�_�� ��f�����?����<P[H�������s��i�^����?W�����GZ]�-|��iN���.���sfB�s�@Mk�B�.iy����>�K������xO������-,���Mc[<�	�u���Z�;�a�[/i��-�ML��!�qeMQ+��9������}6�Z)%c�]�$��)�fRt��GRS������cr���3�Z�QFwC�m���ME���f�w&S�&G��Mx����B�W��1yh�����&m�T2j����}�9���E	����A�r4h�:fW�[��
������f`�N/�of��#5M�g�`�z|=Eh����	rE��(%S��:��$��3�}��;����^?�L#x	��<�V'��v��0�pEkI�1 �T�W\���z���8�U���=L��b>�:y�(Ti�V���~�)+Z������7�V���,�u�tj�Iw�����t�ss����$�\t�w
�s�:Vk	�t�Lp����%�{�dK�(=.sW��]����IX�����a�������e�Y��.q��w�ai�o|������_����wj��p���g#pG�fg���4.do����o���UY����/��eU~;v��k��u��q=N]����u;�����
�Oe�������=���+����U@����zh�����r8��-���(������
�M�����v0�UG�}���?�������Z������������)���i��xO���~s��.�Wo��(-��r�D�~��'2�<�kq�8�h!SAG�n>���K��y����fh��f��eh��s�Q���I����n����!���Q�h��s���|R>�-o�Ug~��������������7��g���������������2|���3�����#�8P4� ������R��K���~�x��� 4��x
����8�O6��M�v�w��s�*�H����s�
?N�Un��+{�����#?@�|�u�Tb���+��_Fq�G�/
Tv���
�v��9���]�K���&��@���^v���C'}?��������t�i������2�-��Z8���Hy�3���A���7�7FR�r6�F>���s.�u>���M������U��E�t��-�f�E�{G[���cU[}oS$]bf�6@A�����	!S�
8O�{������q���X��Z.�x^'�M#�.���p��Osi�o�V+Hs�wCD���������,��W�i�����f�s��eIU���q����-��5x|cI��QqY����R��?�R
��=��eRv\S�������A���4���a*�,��G1h���!��K��R��Q��W�Z�w�P�X�An���y����;J}k��
�y ���s\�*J�D�@1	�|=H���*2�x3�|O�F:��"���fh,M�(�V#��(N�����X5~n^�.��:�k��y�Q��GP14N.<I������ ��4�s]�e�iQ���Z49�y!66�F��}�[[�CKv�)~3&DT�#r����ICce�i\�7�S@}e�]����h:!55��<���fi�<�.�����:�.K��F��q�����)��k��0r\8�p����"����;�^09]���!6��I���&%��	l�]w�)�H�����������$��,���B���P����B2�x���Z��42������-��Z@�\��#�O��9���]_w_g2�a��L�(�C�%��M�@�A�rs��k3��`��0A=S����&za�8�d�*7�X���e��	+�L)ub�� �Ya���-:��$�p�� �������zw~�<�^�5&s,Bw�����Dwf1�����%b�������Rn����t
..���������%F
a����������=��������h�n�+�nQ�3��}83���n/�h��o&]���[����A����*�D[6����='���p��K�99
�XC���J��iN~���4��TP�	3C�����2��������[G� �������7Z�vV����
��0���,�Q���u>��
dDFk�������Ke�uoO{b
EXM+p�y�wo���#���d�� �YR����-L�	u
��H�)7n��L���p�6��-���YKo�����%LS��C�z0�dA��9��S�d.����v�w8�Yf�W_�,u]����3��$<���lY���$���KH�����*t/nCU�2��:�-I��V�u���j��
����W(la/����P�k��
�Y�� \��6/��u��G����������Q��(q���0�"9��i6I�e`p���Om�M]�t�.�L�`�v��
	o�Fb�eM~r�"������7r �'����N^SO��z!����D������7)P����q�{��^��������z �Z�{I�K�Q�9W��gJ��\C�+o����}���ze�d�g�����
�_�s�����.���(E4���'�I%��f9%u_Vi!����:�-�$|�?i���Y��g(�4�������*��8���v��l��
�*J�w����!Uz���L�}/|�^p�Y��?K|9��������$\[�����f�(�k/�2$����c3���`��X}>��B��c�5��S��N<f�9�W9�e"�Xg7w?%��T�F�~�S!�X��B�}��Y.R9�[Kpf�[��E�f~�/;�����<���L:���-�&_�������^���f6W���l�hy�����
��S�����.�7�!���5W�8��<D]����{6je�T[�CO��!���.{��M��;5y�k��0��+��a-�pJJ�T$�S7�\@�
ya�x����c��K�_�}�����08�� Y- ���[�u�{�Q�b.�b
�5��lM��\��N�����
�3�U+]K��!K���:��T���c��������[W��V)�<v�R���(����#����_U�L��G��y_�&��|����1�&��,Z����)�� �����*�S�������S���)��A���6�����!"���p"����_�%��������xF��R�'���}}�� ���Z�05�'���c�X���u?�[�y��+9����5�g���T���Q�����JZ �RVu�_KE�D?��������t>:M���������?v��7�%��B@����P(�-uJ��	[��~f����[����m���>��c�'�}������\��L:���m:c��7���T��\�W��u1Q#NZ(F��I�N��s��*�#G!�L��h��L"��[#���)���?�MN"���6����LB�G^�M�����!iu�E3��a����@��@8>�������G�D�����-����
l}�.8����G-���hk*7Y��c%��A�g8[��Es�������t�Y���k&��nk������2�Ng�[�c��ay��������m�Y�L��[�1�	�V�^p�"�)2���0]�<
E����
�;s���V����~���
���/#����VJ�Ml�����U�~�-������G���
�����d�DZ���&J�P&��Lk�W�=�tmN":���<�\�2D<���c�L�v����7�u�� %��u�Fd5F�<�����LR�J���4���X9�C
q����14@�\3�J���p��
�m�����1���������e����R��e8���q�i�Qb|T�OXj_���Pc���a�'By��
�L��0���������,;�f6���M;.������q�X�S9����8�	0vMr�|��W�d:����1
�t�K�Qz������!%5���p�Uw��!�'~��A�}�q���|�?jkW��"QF������}�z������ :���p���I�V#��2����q���sVg��
�e����K�b�J!!�[�}9jB��4o�Le�\#|�S���F��]E�I��8���e:#��-c����+����[>�j��{��3�-C#�!l������D���Vex�L��o����`BoOO������#��X��+���J�
���D~�*E2)���&������*Z���+z��������{�
���fq�+J�">@�)����������(����5{-I���P
���2(��i�L�J�R�dDJ�,.���`�'+�F��n<�;�g�W�H�E���Y#Z��UK�!�U��v>p���K%�h��@�?o�a��*�
����k��Ix���zQi!�[#��^�&/U�?����/'����Lt���
��F(<���3�{���Bz��������2���2n�����e=����!�E�8���~;:���@���j<GRi�=�s�km�o�������/�<5���(PC��F1[���0���b�	,h�/�5�����o���[�Yr!U����w�D���E�@�����6��d]Q��J`�p|+Xd[�m�I���.��q!"�\5�cJ(s���w�)u�\HJ��
`���e��Y��c�
X$lw�^���1|UOIC_����a�x:�j���gzv�j���g�
of����aT.�����6O��p]3�(,"&���PQK�3��wa���JX�b>
�g��+sT����N7)FK>����<V�����P"7rx�\����j�xx���"g���
�TJ�T|���P��v��<�����GNJ�Y@����r|��8���G��]2�%�gF��|&�t+�{���8��<?��h�:F�Z#uj���������nq��e
��E?)��i9��������"X�b����E�����}N&1u?�[u*Y�U��.�������#�_$�wRDK����px��Q�X���C�F��n�xPVUX��flq�������R�u��i���9�p.T>��j7m�:�@A�5��]�E��o~�Do=�
�k�6[�?�k��Wr�������o� 8�Q������}wA{��,W��{F����e�����h�.��g����wP>8X�=���so]��m��q���H]�fI�)-}���F��g�{b�|�W�y��v��i��`��G����J��N���|bne���&��L`!%F9�-�N����.x`S�h_^7O��6r�"�v���l�j�I�g��E���?f#��J5��R�����?�����r��fd;�qhE�.�i�P�~FJ��9/�Lg<����C�,�xp���*"�;���5�)*=�)a!tN��{&���8M��*)�d<6kg�0��0�I�X�hugj'���<6����	�_�Q���w����s������mK��<���������$T�8�C�[�0��
\��^�!g[l0,W�2���W�&i��o��[y�A������"L�x��������XA���kY,WX�q|[���O����L��63�w������\�e�xU�#�Kq]<dE%}�����l������M^�C�L�J_�e�sd�7��X!�������	��i�Wv���E-#��{Y�����"��u������7<����'Q����dx���<�+�/�E
~�8m5�e��&�f���`�P3�4>�d���f�z������t�>����$-��h���M���a�����"�m[u�������@�I���\�q����R>�������_�Qt�\�������j+��)�Y#��C�8�2�
��|e'������~o�KS��3{8;���h#�������^������L5��1�];xV�V�6��m�������O�R�m�� �����%�h���%�
��������v�����U���P���������;��wwj{P��sd�����*���[��_��5�F�^���_�����.�Q���S����w��{�������n����_sMTmOU����Uvs
�y�Z���QG�'����3��r�����3V�����1\w������c�7��z�^U�������m�Q=�V�.�d�z�����u���n=�m��.�H��=���4U��Jtm�E�L�&EEdW��K�O|����������X��O���[{#�g���qY����Fx�bc�������!05J��|�����w������i<��ysssM%����po��3Y�3^*����_U]mlp���;���P�kh�
a�:\�hu�^w�W�
��Y���F	����.n���(�os�
������\�dm�}P��^uko�Z�*��zwk����\�
_[���X��(���R%`�����0�ci4c�h"�����4J�����)��q�m���qz�����������w�����i7~n5N/��g�X�
��F�.^���nd�F%k�����~pP�����%b+�T`*�O�������-r8��%k	|A�]������&JD�����Y�\x.�6�<��z;��A�W���h{�`������CY�\��n�����@5�*t����:6mQ70	��q�F��'e���0|���H�M><�@x�Mz>y�z\���6����5�15����_�S���	�{�����L��d�-r(�3z9?�����vu���v�����_���������wP���;�N���������z��ho����v��;���N����:�\����1��_��'	p}�_&>�WU��)�}�������;y|����,Z*AK��P�Z;����
�N+��������Z;t3���FC+����(�j�E��99����d�mO�0����0�'J<���DM���~����OuOG���%������z��Q.:[������S6�n�����
{^�MY�M)�JW�J�~��2�g���/b5�����ubI�9�8�4B�;�k��-p���Cnb8gn
n�q�4�2kt#ww|���t�&a����<��|m�I	�7.��i�LG�+�1��,i���d�b�4qq��o�1�{��a2
���h{�<c3P'!���f����%���<�n�������"=�5������[i�k��������O�����^.k!�U�������V������S��V�F
0��V�Y�Q��o���V�on����}��pA%�/���'��))�����s�G�����15&i�H�z�I��d�p.�6�Oq���������lR��`8��9hOI�[�y�:8��uJQ��l>�����ot�J 4����I�	�:�c��������c�ee��0o ���dt$K��^T�vr=����q�@ru��c0�k���������rz�}���y3c'��� ����W��Kc~�8���;bxo;��XR&�@���	����D=��>��H�q���b�����m�v	�GK�W�q��a�Qm���v������S���:R�&���i��y:��QV^�����������B���S���m�������S��
��?$l����q�([{4�Hs^���[��i��W
1\���$����q���q�zw����x����y�qlr)h>J����IsDuzO{/�3W��F�)c�[��q��/������Qg�F=�����;i��7f!��`��+�uN��;�����L��U�3���0����u<E^�(�y����0b�[{0�@�n<�c�R�sR�����;���[-o��A�;��z*�cBd>G�g����1o���5���(qP�����?5O�%���:<���z�1�}7y9Z^=���',
:�F�j�C��p�~rd���W�
`�O��I���8���=���1�P;�?7��L����-c<��������/�&�~��
��N�	�Cf���2yL���&@}����Y��N���f�A�N9>��KDg����f���QYC�[�@� d���������d��"������'(�L;:�
��`<��Y���S�,O���������������)�u���p�����C��d��9���?v�E��2�l������Y���6kx�������I7�' #�~^��g�R�B���"��{�����^t9H����y}���|��#���n��Ugt�u;�����T+����3.|���M0�%��F#�K3�	�?ub���z�������Uk�Z
)��8d�\t'(d��q���\�S���d��d3��@���U����G�������[\���#��H�-`���(�O)�M(��!����;�3��Mi_���U�5��j���r���+D;���[q���g������������L��$5
Rv�R��p�y��Ln���"?�wts�IqEx��~rNS\��}!����1\>��#��PWTr[g4��������*y���&��J�UfhY(v���J����pp���N��-�	�1��Oo�!�h{�R?��GD�{���$]���x�������/��)`y/r������������/L���{��)+"����m����p�kL:��B�&�|S1bE�7X��y���N�E���@�$�NB0mgU�Z���������i�gP�Q)^�������e���:Q�q��(@l�g��z�&va���N'�����7�3��<�md|���uQh����}q[�����~���
))yH~���<�O�K����ivY�4M"&V����	Zm4]�6?d��
2��>	��x�)��kd�O52�i�n��
�P-���s�2�Z���w���v1�����o%�����
0����������~&fO�cO��a�S��{1X�0gw
@Dc���a�s�O�5!P�-�k�����'��G�[|r��y�OK�9�����o1'r�T��j��q���A.���w�c ��E�l��.�b����@���)�3��A,*A�����8�f�@��a���V'[
&<��O���$���k���V=�n�
�9�����B,��n2..o9�3��Wx�u�����#K#�6�	�����_CA��y��G���p�����5��l�I����O�68�q��e���� �;H�=�D��P�������NE]4>D�F�]K�a�������6o��v�_����q30
�������{����Je�]��.��{+x5��85�Ft�m�Q����U���FQo�V^��h�w��������������D�)��"�NJ���G���2�mP��)��C�$���7Fw�hSr}[�����*��.m�b�Z��U�1W��s��~@�������;ur ���l����O���?�e^/�w�
���PF^�]�7ho�7f][t�3�](�fOR�8�$n������1#mn�x��}7�n���R���K6����dP�
�{��w�(#V,~�����=��n��=M�HK2Nl:q����[/�����;Bn=.����=���XC	�<3�|0����Po���l�Vj����6�p��M%�o�Qi�`@+���V��=�^5�o��Z��!5�}�n�,8��T5g
�yM���~���)7�?��H�]���F��<��%g���9+��B:E���0U���Qxq���e
y,>:�����!�F��������� f�;�����S����<,,%%���8��4������$4f)%�0����I�nU����MdSB�%Gf�[�U���]xG(��4�$2�b���%7�\P3��<6����-�����l��?�i>����`S�j�r�vC�6D��Z�����N������3Lh7�d������
q�F�������0I�F!��B����\�Put�\d'q���C�'�-�nA;��h�D��{�XL�ZuW7S<�g���_�������+����5���6�{����-�h����PP�����N��iYj���R�[-2E3������������&r�$��i�
VGE	+|?.U�X�������J9t@����ik�t��f���������E�?���wg[u����YX���y�w%�����a�Tn,N�v�uJf�B�_�b�;8����������c,T>��B�y_�%�G��'���<��(����;���w�����c=�R���s���6��{B"�������I��g�j.r�<�����P�5��E�r��:��2O��r���\�� Z=������Q+<��Z����f���Y�����P�;��$!\���K@�O�'eut�8?j�?�=i5[��C���Y���o���G���?�$h��6��}��w��V�Zj,�y	��*W��jA?^��8���^d�X3��������+r�����
�d{yh��Bl
&��O�:W�o��d� ���+�e��F������[���w���0)��<`
�&P{dh��b���4��bf8Q��<���g��n�0�.��l8w���_r����S,�&�P�6_�*)�����J� �����d6���x���k*���O��\��10���e�d������}�����"�+d�)C���j���e����R�e�;;;	^J�	�
�����f�|��!���o�Xi���so���"�]��:n�F�l�4���<>����� �tv�i���	*�t{�PS��I�Pu�cFI�*�N����j*��`�������i`n�����^8-��j"d�k�q�F�9Q(��y���Sy|��SOi����I��~��#lb�����a)se*�<�j������)�p���0����������7$d��j��D#D��XDW���m��w@p��5%�����V#��o�Uw�����n���Xyp>uB��<$��4i�{��e4Q@�n0:�?!)�����$�����0T��A�I)%�����^�
��S�r5CZ2f�#;rMjP83#��X���w����"&K�
�������P�M[8�����)K�E��������\��?6����Jb�|6?>�9V���-��{aY�r'��@�@��)�K�M:�KDW�c��6�}�&�JG�"���Z�����Q/v�@�0c�����I
1q����S ���9�f�2_�K���z|�bO�����B��S�;���C�]��UF<(G4��{:��f`j�V��M�R������[��B1 ~w�j��"�32�hn@"�0���)�d�r�U���7u!c��4!s�du$�`�jVTO^=�
u��R��bp�qGv
JbCWU<)��vd�`�gLR1�@
9�����+������[��y8}3�>�E�9Zu��;��e��~����	T��TN���)y���;������,wl���
�S7��n��c}5�L6�H�y�q�
�K��[Q�k�H����g,r����Wlq����)�o��4�F	UL�^��5$����m����?��q6Do��h9���MPk=�&Y�����K���4�&<�n�p��6&�V[�;;��.�Y� ��L�����M�/!��9��S���m�q6pb�_��KCD)zQ��b�5�m��2����Uw�z�����S�\���<�d�P�pW��N9��}��+���E��fQJ�1��,��N���b�l:��&����dll{��`�F\}I

���Kv������,T�K���nm�z�moW*����^����]�s3;P �f�!����)�H��!q��7���ur����}��A����N��(������A����^]	�LJW"�+�|�i?MI��$�zI������������0;m��A���`��Vz����h�����3�9l��l����T���C(�g�����.	\�d�s�g��Y����iO����C]�������w����-~(�g���m�v��������V}���}Y���������Aw��;x�����:���O�(���������5���GKEh��e����!�k����m�\��:�rR��T��W����,�R�p��%�`�e�����WU���d���S������)w���l�s���7U���}H���������T�;�2=.����r����o���/+��N}���nu/�{|� |�/h@���.;��������������������V�L��_}3����3gJD������o�������	��}��o_}��x�Wk��4�}�|�Im�;.*3����u�{�}�����oqH��n��M��nR�rJ�� ���.���}�>U����l��>�_�4x��O�/�B��������j������a���gC��A�P�� 
�����FC���].8��W��;��>mP��*3y������i2�M=��0���o���e<|�J��������f;|5����%X>��C�i�����j��D.�n;:�gsC��kR�J�^�(��(4Yu�m[v�s*S"f��Ye��G�U��TQ�asQT���T[(C^4�%7�|<����m�9�m�4��>G���[��b�l���/���S���lq,rO[��K@�rTat���S�������b���%�s�)�fK�R�Dh��L�%d���N�L�C�ku=�X�QGo�E�L�������M#�o=j��8=H���"p�����A�=�����g}�^v�wj���{�JT����������v���;xy'���R��m�������O|��0�����I2Hz����O�����;~�����I��
�8�W�|M�����P�o�`���!#B�S��M��ux�����>�,�\������TI�Mw}w�<���Z����:��(������^j03�O����l�dO;Wd&�G���\6���{�M��xM�;�2	���PU��U�3����[���0Q��'�9������tp������A��?�*�����Y�~r���|����.7}�&1���8{|����b�^I8���@:1?�^p
z�j��H:W���R�C)@��D�;����!�p9s�Q��A����N_���P�uu�F��
���h0���SY�t��B>�G��g��u���Z\�k]I/s�{����bL����������&�b �
�0�����X�1��*�n�
���cR�F����DJ\� �&2<_�����SZ�DY���\����%��s����g���%�<@��}���]��o�
}��:!���M�J���cR�:f�\B��}�8l��������i|��f8����{7e���F^(v�Q����kF7m<G���������5O��Ku��N����
���1���]"PG��/CW��
�R�{�Y��6
�0�<����^�8o2:�����Aeu��)�W������B���Zim#�u��;�]�J/m�]������G ���>P����b�|Y
�Q�$�m,��W�~�f�g����{�����Kh�e@�_hT�we��J6�_��0�,Tz$�9�N�x>nN��'��m2�
��#
�{�u����y�C6����-�Y�� ��.�&��_(��#���p�!P��D�|;R�}H4,��	I2��>��|~�����/f0g��{K�����7~�.w�~����M����r�3T��O�V0P����k��������jk��������T~��������/��,Yy{�Y��<��X�!O]�Bw��7�����Pl��[0�������
P��I��]|�!A�����[�r���	
�s>i���'���1�R�/�ltf�3�������B�������lV�>%l���e@�eP�H��S�+���/>(j��A����\��O[B��62�����@��p���a:���T�g������e��car���J��A(����(�����
6#��;d�x �������=;�������
�G�����b�r�6z���+��K�@m{��	'~#6��:7We5��?�%��*��l�~�����4���A�G$�9x�qV���6n-�F�e��(� (��h���-+�o���9d��G^�wqT�9����0��A���X�\����s�p����1���!��qS����~j���TV\���N�o�-Z���������#��3���?�y���m�5<�!*/�UQ�����0�]�@6;��1�'h�ag����;J����u�������Sp���;�������3�8GAf�F<lq���8Kh����@���.��E]��m�+{"Q�T�*TZ������z�[�>q�Kw~�������i<����iK|�	��Jp�+f
�������A��O��|^�����a�!��@���c��X�������y��=���{�*Q���e/�<��)�����g�s��g�U��-�j�:~d�-������v���n��V�?������=�X[DAv~H�����P�����K1{�cc�*�-C�s��=R����q��p:�H�E-~	?����P}+w����������.�ou��+$dh�6��J[�\�I\�	K��$b��r�S;����LM��+�b|����#m���-q���-��#�.�������{�J��_������-��[2r�+��^�v@1n\V�>xJ��N����u?�K��|�����[t�sO��q<�U�����7���a��59�|���
�YR@H�L��1��R�qR/��{D �f������o"C`��^g�a��\G_����{�(d�O�?�����.�Lg ;X��tV+�Q��-��n�<�S�=w��;{��}�*q���������K��%��~���c�f�O�'�Z2b�gq8�eu���l���<��l���k�^���T*���j���W�,� v���o���>�wv8l����x�cn
K���q�������[z�&��d0��OD�U)���gI���(&�[�U
z/�i
��bX��j.�0���u|K@A>@�_<�R�!�:�����H�
�����?����
�{�'b�}�T���_u�61C!%,�w&��Mr�����o;�i<��i��g��u�^��� <V��>�����@
��=��[�]�+��2�K�G�d���<&�&���F�As#����=Iw����w	�b�rH���
����8q�
�}c�B�?�`J�fo��M��/�LF����2�����X!��6��[i�Zg�1�mw0^��Yl�t�r�x��pQNT6�=������)_.c����,+v\�=KNvd�a��]d�S:]>J����\�?��r���PV���k�}�yk;�8*3����������(���H��FS3���<�������$���`L����c0�������l��wg�����$=���;0��h*���(J���s6���{[j��e��:|
�u�����m�G�X+�S�q6|����N�*zws�x�����|�&�z�b���%�N�Y8��rpz��4�4����}�8�|�!��i����)�JG4��\���g�x�8��*�{D�H�
F��
��6x�',y�j5Ov;g��Hmy#���5��
z�P5�_�=�[GR�#��n�^4�[�(��!�}U�F�����0>����Xv*g&jf��g���9�������H]z�hl��6��fs�{��]��v1�ru�1�+���9�s0%"�+�`�h���h�zN!eG�j+�b��6K����A2@��'���9c���9�5�mL��9;��W�0�V�'�u��������hT;����cf������A_3����'5�f�M����!����C���m��7�)�(>��rJ��Er�1�8%bE�rI��orc�dL����5��|��W�?���t�M���.���s�{���L>�C�l��	��"�}��>RW�>SI����?����"��p�������/��������������e4�^��xP�v@�O/��#6�b1���u�^&��~N����k�?_�/�6BK;��B����>��[������G���{�@��%��h��{�J���`�����B����3�BK���z�����h�JU����p'��;�Z98��Q����u��`��fz��������N�W�j�9�������d���� �<�Gy���(T%E��
E`�i�3�r����4��L;50��f�������W�7���V���O�z�Mq��.�t�2��x�^�������y���9b��3@��=�e��
�:9�C�s���
��z� ��4#CUvA�t�ZE��}X�,����:�|
��ac�v��w+��}tj�x�wtr61�����!�$�P��o6	 �>�&)F������[�OE5������>2�$X���������N����E�EZ#�7H-+o2�����0n����*���rK����R����C��o�`C��k�����4��8$}�A���	2��`6'F��XA���S&� ��n����$_���5sI�_�uHG��,�/-���
-U�������A%�I����K��<��)��
uGAm�������n�=|��� ��Ui=��nQ�Rz��M���`�F����~Z�Re(#����NWph2�i
���p(���g���q2v�����1���^�����v�b���Nh-�I�Y�xv�^��P�i��#Z��������\c�����u�����A�(��D�����Yh����U\v�
�Z������:
�����4��F;@g:HI�|�j��\��{�F����pn�T*� ��U*P&V��kp�']r�~0h0F]$3Q\O��S70��A�
�T�������|�F�U���[0�{�="�[� ����Y�0��?`��P�m8���V������h��[�f���FZ���v"q��<��������t�]v�J>=(<
@.������I�;��
>N����a�=��Ps�B�r`��
���K-���eDB1!H5���"���g����.���72E��ss��q�W���\V����m%���]��9�-��7����ns�p�C��19���q�2Ch�����}/(�B ���������`:����4��kJ
��������:����0��������B��-F��)Lz�Y5�"���A��x���1��'�'��.IH�qu`�������\��~u��r�9xB��M^(�����cF�G������,;=�i&���'O�����F����o���ph��WbR�d����?�;�myX���4 ������pet��e�u��X����o�U�5Y�����X0�]N�u-�����1��O�"��Y-�p0`�\�8I��f;[��P��+N�1�32w��x>�����-PIfQ'e���YA:��u�����f�N2iv,+�C�2�J��3pmoY�s�`�zc��4�t+�������M�sr���7&��.�q�p��b��#�2(��\"?x���na���4��!�z=��V������)L?��e���+>�\�V����\�� bn��d2|<�HDy�]�4O�S����q0���UV�0�O�m�A2���M��J�R��	wb�R��#k7���E�Z�gy�iH������{�Op��k%sO�Z! H�R�#�ew�c��o\D�jx���d^����k�������������}���,��X~��:�j�Z.a���_��\C;�^���	��?�q����������\�-#g7�k�8����&�|��n|�?��^|9��]����9;�g<\)��I:��V����z�1�����)%��O��dR�p��G���������i�q~�8j=k�|�x�o�o�N����(������'
e���)�@G3������]�o���v��9�X��HJZ?`���	`o�i����k�������6A�o���|��p����9,	p����*;��0y�\+�N�y��D+�,?e	BL�*���Y'�����Q��0d����o�w8��^&����8���
���������2���Q���~i��jf���j�:D!�Pw��fxg������-`��|�V��4���xze��9��x����Tb�MQ����CW��������^��E��1+�u���	F�����>��Q,�$#�g�V����<���4�����Ql]�nSs�=[s'�v��c���q�2������	C��S�f�P��������������j�V���Ki�V�1� ��L��))c�z�G�����-�K�=�"��������x�M�-���i#Yh��k"��}���+xMx�	W��4��U;^=;7�D@�`g��d��_�q&���@�����U0Mf��^�<�c��Z�����?�:�|�m��#`��B��S���������oNj7�ewg��Z�>p����yJ~�X�����h�4�9�K�&�KtmuF��W�8_	:v[����p�n����>}��u4��i�8Q�������X�0�?o���7 7��A[G:�j��u��x��I`H;�,�X��j��8��	���6IDR8�4������/96N��hV4��S�($�T}5�}Q�����DjOuC>���$H!$����-�t]���0b�i,H�f���rP��$HV%_���'�9��F ��)�D�
������&1Pf�$0����`(q^��'�d�����c����?�q��c��2-����z]���F�!��6D8���q�����E/)^�G���P��4�h�x���b�e{&��)fJ�Z��`	i��vka��7��{�
��A]Ut^��5����8�E�5"�����78���La^����"�Z�y�8i�������j�^��Ujkk��������M{�����}S;xV�V�7{�M�����M�>6�Wh^������I�����N��~������m���������?T��Zm���X�v���P�w��xs��n�����������.k��v5:���w��zo�S�������v�{�~o�$�1\KU�S��s�U�=\�a�����i�
#������L;q�T�q�����c�Q�q���m��������s������T�ks���\���a��U�V����T-Mf[L�����&O(��m�2��It�`�]���+���qP�e�F���-2��b�(���zp��+����Q��.�+O�)���>:^6�GK�#<a��5���*��}/�L�<
��Uw�5��=�L1���?��|�;�WR��������t�.�7Ne6�$�3�K��{��?�A�W���z�K�XpN#T�Qh?�=��]o��E?�����F���[����x-���������\�1��gVY���)X�V�������"��Y�g-���'��+�	����={�b������wut��m�I&����3X������kt��u^(6��'�Iy~�����j�Hw7� ��E�"�6)��G�����eE��v=�M����
����V��J<�z�k�?���bwg{m���$F8��`m3������(�e�������i�k��n��.�h�J�9E�6�U�s�nn�)�>�i��\j�1y��v��+�=�?y�%�����pm,��q��?�s��Y�n#�����g,��7Kk~��e��\�B2�v��������+������^og��gp]6gr]�
3��W��j�[�b2Wt��K��3���~�����e���M;��bM��z����0/��	�v�)vicjm>���{R�9G�M�����}�T�gZM�%>j^���&1��g���]�W{�_���a���(����h���'���1�{u���)�/���n�#^�f��J�3x��az������k�<�;���1��K��������y:&��B������b2���=��8wmM	���&��J=�'���=�����-��	����r��p���e���2��.C����0)��]`�z:�x���Z\�F���[�� �a�:]�7���m��y���?�6�������}�5�������?��������(�-�m��E��W4��
�:�"��(�v	^��@Ux0�n�%����^�ST���F*+<D�����3��;����
N���U���R<�Q��%������(�s��E��A�*��8��FM�X�@�
������|��y���x����5��R��HkL��f�NI�NCU?.��t��I�����F�u������>=k����=����.�\%W�u���{K�w�+gV'������W��KTP�R0�0����vNc��-�C�mQ��j%=�GA������^�`�q�#����n��T�i7�D�_'Y��wOAd"
��C�eq�$����a���cB��K�%|������Ik7��T-�����������p
�Yo����J�QX$�c ������x��.8L0�`6�)n	 QU��J����+�2����,����k�4��t=H`Xz��s���O�6_i�z�
_��g9_<B����K	��)��h[-�D�����P[�
����K(�!�a����U������|�)D����C��f\�#�2Z�}2���-�@���w���)���F�a�X�</�	���!�����c+�`|���o��.
2W7O���cqDv����wa�z�Q�_�`��|�E/X�����;��iB����;Xh�U)\Uw��Xuk���ih��
�3��^���O�#�%|!���8'��dA���w�� ��y���j~��Q�_�c�S������g�9��� ���vI7@���!t����jv��kw�nTLO���7E��^��Q����K���|���i1��\x)�o��0��7�Fg"�L�����QKB[=���a��F�+�a���?��~�H��D��}ZB�������w��yu90w���b�<�L+��x_�/`������X$$����b��E.���z���Z1�y��G�i�$������@WU���_4$����������z��/[�m���H�u�l}o�������������2�����7�?.�~
�3s��H�����=�p�h�e��#��o�\�mkw����H���FQ2n<{�����U�j��%?-%�	�B���HFf������n*K�����R����2g�^��q�h�+-(�P�6e�)]��<zI�@m��O����>�_��~x�1����f��.�C�	����	��Q�AA��_R���\2�l�����W/U���h{�	d����e�h�F����-���	��[8�wg��hFwF
6�����!>n���p��Q��G�Q4����I�`QCH��31�f��p������9�	���d�wH���r�Dz?��l�v<�wF�� J�9���y�~F�:�<>���3�|p2�}��
�Nkw��>�b���E�{����^��{���������KZ`����������\cg�����}A�i���q��u�n����i(��n����n��������if�]+������#�gF����XVq,�8��(Bm����+h4�gd�h��\�zgDO�e	Do���y���n�~
���a�4���L9$s�m,+�RtO��8.�&iu�u�i5��2W�4��J�80���Pa�@�KM��YWg�o�W����<U�H��u�3V��=M.;^�}�|�
]ci>����j�3I^L�z�+���f�w��#�k_m��X��}�m�����Kd0A�e��:��-0�Rg�U�?���8�1N����9�=��ro���Y;�J��4�a^�Y��8�>"���;��{d�X�?I��t��Cj!����M�x��'�����(K�q�x�����r|V~���������U��������W
{@�,���X��$�#�N���?��A�.�W�g�5�_.\���k[�3�9�M�rH�]���9����z3Eoq�@��G�i{�Ae���F����f0��#������A��bioS�6(�����0I���n,�����PO1�=aUj�s�.K9�y��o������%�8�Qj���!���.!wwmB��c��y�y�������*CX\����xg��m1�_j�f�r����3�^����cY��d�q�3��m_|�c[����@�~�?@Nt��%{�!i�?O���&�����>��=�#	���'�����;�C�����eM7�T��H��
���WE�,�|
��{���U�����}h����"���E{k���h�A���R,Z|8Z����j�{�[�{iP��N���g�j�Q�K�_V3��lU��>-��`R�p��#r(��d�9$���3@��o�����%+����GG�����o��a���'�2�����n+�p������ag�v6%�i����[�]q��z�7����%�L�'���?�`�'����F�Ga��
�
]L��}Z0!���k���\e�n)�P�o����xz����Op9���A^�s-�;�������_�>G���P�J�Q�gH��[�$����*SKyL��{��~�~�����<;m���k��!����">#��>I�d7�G�;�:���cG�-����v�6i�^R�O,���j[����J��V����v�v�c��H�e�0�m�n�^eU��Ny��������Nr-�2��@��\�]*,<�p!�32�P�#�� ��d&g�N��	�'k����*��_�W��#�D$ ��������q�����K��q�:o�������C���	�����n�w��R)�(F%H$�)��2^P$�X�2&t�D(��,qYV�8a��U�y���>B!gO�����|�|� ��m�l	L��5�XOtx�(�1�=�B��\�~�	���PAs�b,@��( JtA�b�R�;;[�y����)��O���E�������09�����I@���I�D�"��U��#r�R��9}B\wv����Z�^����~���j?@���G�r(2O�1�� ����vf�H[��h`\��=��3#���]�T�V��F���^�x�bU�I�
)�1����E��Q�Z���/�CQ��;|;���n�9>�"�tG����K���hd���%�S��4)���+�q�	�j��.�����I � �b�u�%�^�X�i�~������O�.��6W���L���Jj./�ee��`V��`�Q���hw����2���IS�"�zN���8�BG�[���`����Dfe����	�EA�R��z�[&�{���W���c4d�-�4�a��CT(`�{)R�$��b��
(��qz.E
��-Z�.�)�R���IfS��p�7�u)>�<��_^�#��7e�����t8�`��X�\E�Y��h0.)���_�����=�@Sg��K��z�O��;]�[�@z�-�*�3@4���?]�'b]au�N���N��6����@S���T���uF�p�u]��)t"<�U��KT��X*	`�U=i?���=I�YR��?J�a�)M��(���F,��6�>�E�<a@B�c�����f��`K$���*��=���y�_;Q*�+��1�y�|%����	�-���QN|��O��<q��/I���eg:DS�?��w4��5�A4�I`trM%�$G�~>y\�N^'�t0fs���G�k���Dov%v�����h
[�|(s��*��SVGeD��I��9������?��/��L!|����6����Nu���sX!���s���,����ts�"#��|�V���1��b��5�k�S�!Z��g2E���{����]���owG��Y=���1�yVO4������"Yp����~R+��8��������i
��J�xn+��������N~����X�.9JV
V�p����Hm�&,s�!
��YWs�{�q�����o"��?�F���4mL?��m�����Z�2�����L�pa�}H���KV������6�����C!�E��\��XIM���?�b��J:!�'��L"\q�$�
x�@a�
���_�i�����?���[�2��F�8U)��
�'!�]�p��Rj�(q�`6o�X��d���P�)/%�����d=l�6���Q�t�����/����DJ5�����+��4�
���n�2�)�2��<�3�,%���t�����/�$m���4������g����Q��O��=���%lV��p�4�3��3���5�3Goy������'@#v.�9��9���S�#"���of�p��,Q�@a&qh�T�����4'��B`��>����d%��t��0V�����Y�_*w'X�����X{��3�;3�����	+����:�U��b�C�hD���._����$
��vt.?��*J�%|�Y(�kF�aOt_�s�w�>F�L)�'m����tt�R�K
!�[#��Z
��`���0��$2��Af�*�W��k�a��M
]�����``\�y4�q����PM�DS�r�uR9h�q0��S�
$`���-��UNl*�Dpo+aE,�l���R��;�O-4�I�3x:m��I������[N�Mv-y�fuW��!k����c���G����H:_��{y05F��������,A�V�;Q�����z2����J�;���R�����=�j�4K�p	:��!�]�����QD����
�$�"���
#2��J�j����c��I~���)�q����s��D�������?I�"s�Eoc���e%�3�x�=��� �����	J���
b��,�aK?:|{�����>,s�
x"`��t�"�_�$~��,�IiI���q������y�/��H�~A��A�wQ��>��a��v��J�\j�
70�+��2Zp���k>������r�$����=a����^�t��G��u�V�6j-7��������m�W�Xi4?�42"
>��v�r��8^�,���F���<��N����K���\�e�����[K��s����vL�IQ$f�W?���Z�������b�=ab�fI�����F�gX!53B�qq�YX��|_��2�
���&==� �EO�������0'�P���a'�mX ��kIG�i��|,��(p
d���g|����N�j�C:����e�kw����>����M�'A� �����:�M�yz�nS��"�D�`�I!�U�c��h�/ 
o��m��L��_��P�Cs�&��ph�+X�T,������f���85X��jt{h�4^��K(�u�|���F`u~o����?�0�t�H��;���b���q�<���;�q�"�W����k�q���>W_�-���>;�N�K�=�����X�Y��,�a��ju�����u��C"X���]���3�<���LGhM�!����m|��WZ��ps����a��BO�	���>�����Y8�$�2(i������q	�~���9���Gw����F;�(@�r*<�O���F�h[,�7��(�`�f
���c����j��%�
&yiq����a���j��&C)��~r8����Dr7�i-]&���� ���3A��^�'�a�j~�>��Q���M+�mhm��n��9���/_,����l����!a�^[���l��]
�:$��������TT�������51X��������e7O����(�VX������s�E�Y�^2b����dW+���f��Y5{R3j��>��b�����-z#[+�HG����Md�Y�{�H���j�������wf���g3��a����f���*�%9�~W���r�s���9x���O�#��9�����lX�����Z���(�u�� J�C�W�	������(�[6�J;$k���{r-a)�d>��+K�T��I�"(�0};�4���=�t�A��������_�6�6�U�"D,�y��{�]M��N�<$:/s�C��'��0�t�����&wzA}�K��^S�1��/�9J__�D�jF��Z�Fy���I{��{���$!_�9H� 8����Z#�V1|<8t(�D !�%L����;q�U��^���_�7���P�*�JF ����'T��I�Oi8I�_��9�a�r#d�"pd9W4�pvrm%�����{$>��<]�Lko\�C=�6>3����~&L�
��w��������$'k�nN�tVVl�|�v���V���i��6p���7�,��c�2��q�3(�p���0Rby��*(�z-`q\�\����)��or/�Ip`{
sg����O�o=P	y������vi�mC�����f��b�):����:���_�P�P�x��\>��SY�
/�3*��2�q1b�4�E���0]�;mC2�/���d��� GuP���o'�GB|tX������#�p@3���������y�����n��m,C�oe�c�X'_��R�����Y������O�G�V���Mf����W���v�������E�I�����/��������	G[Nt�A)�:1��^_��%�W�;<]��4�f���������k�;��^���~JNh�c�r��J���V��^Ibg����*���Oi��o������,�"e��p�DC`^�K����E���
��P[@O����Z����1�I�qT��Y���=e���s�~D�$���@8�l>��iOaY�Q<��d��{ju�N.�r5)y���l�`a����]��d6E�?7m; �8s;�4�����?�������=;�=l���	vp��~��T{k�i6��u�Ukl�x�e{(t�P\M��(R���MnP���@���&��]�n)�-����kO"�ys��-�G�W*6O[��J[8�/S��"�o�K~sx�}4;n|$6�=�p m�
[I9�R����(��$HZQ����j4y����cSD(r:��#8�!5�&s-�G�.�)����g����.C��V�M~I�q�r>���������e�E���xx�W�0b���i���#E3������E�|��$��1���x(�V�[�����!V�Hs2h�E�iwK�0��ml��9^f���	��C��3Z�W��$�c��K��;<�:�j}��A:xqr2����.��a��L#^�
Y���h�u�8����~���iT�V��5'���]W�����9������#��?���
O)����8;�;B/�h�Z�o,%��������z9�.��������>\dyn�N�
��|<�I������O�O���{+���=:��`�wA�o�#��[��#IC9m����?�@���{���������H{�8*��e��S;���q������+��^+/(_��&���<B��Az�Q��E���X�3���\8�h��#����E�^x�2R�}
�����5���T��P���A}�D��<�����o�:��������rr<cyd���E�����M-h�v�U��W=�I�����N
����>j�R��J�J���d�wg���$N^^�������;���B�V�x
,�%�Uj��~��fus&IH9����URRbARThaJ�H�����M���H��T����l~}��ju]��16�xk(�n-%�&��e�N���IL+����-z��2Yy��;Z�c���C CN�|d������=���qd��k�;�����Glm�R����n�\��61���Ln/��=x��<~nU���X��l3����V{�lW���`'����n��v{�0L�6���`cf?��od2����q�b�r����y<c���^4����"yk�w���:�W;�.N�'����L+�f�p��Hy?t�a�����%�-����b�R���l�(��UrBo�h���v�^x������8������?��gn(��)2f���v�phI*9�q+J5�@�w�na2�K��J��6��?���}Ln��~L��M�vXpp�G7�eN�5�+��M3�P�@��)���,=���8#��:2��J9�Ji|����#�q*�{I����������w�|����x�:�aI�|��J�*�j/#�1�+]F^�/�L4;���V%vR���/;�M�5��%Tq}�\���D�u�U�Q�k7&����r=�*�����8�KJF��+�2�'�+�����B,�)���y(��i�.h�X^7^>���A>[�V�]��W��Kf�P&���E:����������
���'��ax^x]�H�]�C�1�����CP*�����tY)Ku���f�aO���F�5�H���������rD8r��s��u!A4	�����i����p%n44�p��@/�et���w�!a� �C|G{Z�J�J��H�&F^4�Q`�M�'Na��h����#g�/���/�@}������1�7���U�P������Q��Hi��lN`Dl��5z���\0oW�=�T���"����k�����������~0���������:{�Di�C]���+���9lLX3B�~����LSq��������oI���h�:�wj_���{��DY���q��kx�f� �'����U�=�|�&i�1������pvY>��X������g���M����E:���;w]�7��e���>�u��N�Y�:[���\~>%���y�B�����=�N3Sg�kg�>
�)���0m�9i�\K�6��#��3��yz�����yK5O[g������|�X]�.I�/�C����`P�+���1��)��#����x&��������R��e����B��wjf����#�D�]�T���i��I�)D�=$��x��}����
5)�V�O���6���-�
*���	]������-��x�YR7���������x���:��v���T�
�����[�����uo���m�{N���7�v&q��a�������usF3��*�~����cE[����1%���L�3;��D{���?YI��z6} J��-/��\M������6��Z#�ip>�I�2�0j� �����K[���G����6-��e��{w�E!��>s����&&����ER���Tr���I*���c`
[��i�K�0.��:���S�Nj��%4-�=����i� L\�O�_(���C��3�E>g��w|�cBN<��j�^��Ujkk���������/?_~��|����������/?_~��|����������/?_~��|����������/?�O�������
#155Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Konstantin Knizhnik (#152)
Re: Implementing Incremental View Maintenance

1. Create pgbench database with scale 100.
pgbench speed at my desktop is about 10k TPS:

pgbench -M prepared -N -c 10 -j 4 -T 30 -P 1 postgres
tps = 10194.951827 (including connections establishing)

2. Then I created incremental materialized view:

create incremental materialized view teller_sums as select
t.tid,sum(abalance) from pgbench_accounts a join pgbench_tellers t on
a.bid=t.bid group by t.tid;
SELECT 1000
Time: 20805.230 ms (00:20.805)

20 second is reasonable time, comparable with time of database
initialization.

Then obviously we see advantages of precalculated aggregates:

postgres=# select * from teller_sums where tid=1;
 tid |  sum
-----+--------
   1 | -96427
(1 row)

Time: 0.871 ms
postgres=# select t.tid,sum(abalance) from pgbench_accounts a join
pgbench_tellers t on a.bid=t.bid group by t.tid having t.tid=1
;
 tid |  sum
-----+--------
   1 | -96427
(1 row)

Time: 915.508 ms

Amazing. Almost 1000 times difference!

3. Run pgbench once again:

Ooops! Now TPS are much lower:

tps = 141.767347 (including connections establishing)

Speed of updates is reduced more than 70 times!
Looks like we loose parallelism because almost the same result I get
with just one connection.

How much TPS do you get if you execute pgbench -c 1 without
incremental materialized view defined? If it's around 141 then we
could surely confirm that the major bottle neck is locking contention.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#156Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Tatsuo Ishii (#155)
1 attachment(s)
Re: Implementing Incremental View Maintenance

On 12.11.2020 14:53, Tatsuo Ishii wrote:

1. Create pgbench database with scale 100.
pgbench speed at my desktop is about 10k TPS:

pgbench -M prepared -N -c 10 -j 4 -T 30 -P 1 postgres
tps = 10194.951827 (including connections establishing)

2. Then I created incremental materialized view:

create incremental materialized view teller_sums as select
t.tid,sum(abalance) from pgbench_accounts a join pgbench_tellers t on
a.bid=t.bid group by t.tid;
SELECT 1000
Time: 20805.230 ms (00:20.805)

20 second is reasonable time, comparable with time of database
initialization.

Then obviously we see advantages of precalculated aggregates:

postgres=# select * from teller_sums where tid=1;
 tid |  sum
-----+--------
   1 | -96427
(1 row)

Time: 0.871 ms
postgres=# select t.tid,sum(abalance) from pgbench_accounts a join
pgbench_tellers t on a.bid=t.bid group by t.tid having t.tid=1
;
 tid |  sum
-----+--------
   1 | -96427
(1 row)

Time: 915.508 ms

Amazing. Almost 1000 times difference!

3. Run pgbench once again:

Ooops! Now TPS are much lower:

tps = 141.767347 (including connections establishing)

Speed of updates is reduced more than 70 times!
Looks like we loose parallelism because almost the same result I get
with just one connection.

How much TPS do you get if you execute pgbench -c 1 without
incremental materialized view defined? If it's around 141 then we
could surely confirm that the major bottle neck is locking contention.

My desktop has just 4 physical cores, so performance with one connection
is about 2k TPS:

pgbench -M prepared -N -c 1 -T 60 -P 1 postgres
tps = 1949.233532 (including connections establishing)

So there is still large gap (~14 times) between insert speed
with/without incremental view.
I did more investigations and found out that one pf the reasons of bad
performance in this case is lack of index on materialized view,
so update has to perform sequential scan through 1000 elements.

Well, creation of proper indexes for table is certainly responsibility
of DBA.
But users may not consider materialized view as normal table. So the
idea that index should
be explicitly created for materialized view seems to be not so obvious.
From the other side, implementation of materialized view knows which
index is needed for performing efficient incremental update.
I wonder if it can create such index itself implicitly or at least
produce notice with proposal to create such index.

In any case, after creation of index on tid column of materialized view,
pgbench speed is increased from 141 to 331 TPS
(more than two times). It is with single connection. But if I run
pgbench with 10 connections, then performance is even slightly slower:
289 TPS.

I looked throw your patch for exclusive table locks and found this
fragment in matview.c:

    /*
     * Wait for concurrent transactions which update this materialized
view at
     * READ COMMITED. This is needed to see changes committed in other
     * transactions. No wait and raise an error at REPEATABLE READ or
     * SERIALIZABLE to prevent update anomalies of matviews.
     * XXX: dead-lock is possible here.
     */
    if (!IsolationUsesXactSnapshot())
        LockRelationOid(matviewOid, ExclusiveLock);
    else if (!ConditionalLockRelationOid(matviewOid, ExclusiveLock))

I replaced it with RowExlusiveLock and ... got 1437 TPS with 10 connections.
It is still about 7 times slower than performance without incremental view.
But now the gap is not so dramatic. And it seems to be clear that this
exclusive lock on matview is real show stopper for concurrent updates.
I do not know which race conditions and anomalies we can get if replace
table-level lock with row-level lock here.
But I think that this problem should be addressed in any case: single
client update mode is very rare scenario.

I attached to this mail profile of pgbench workload with defined
incremental view (with index).
May be you will find it useful.

One more disappointing observation of materialized views (now
non-incremental).
Time of creation of non-incremental materialized view is about 18 seconds:

postgres=# create materialized view teller_avgs as select
t.tid,avg(abalance) from pgbench_accounts a join pgbench_tellers t on
a.bid=t.bid group by t.tid;
SELECT 1000
Time: 17795.395 ms (00:17.795)

But refresh of such view takes 55 seconds:

postgres=# refresh materialized view teller_avgs;
REFRESH MATERIALIZED VIEW
Time: 55500.381 ms (00:55.500)

And refresh time doesn't depend on amount of updates since last refresh:
I got almost the same time when I ran pgbench for one minute before
refresh and
when  two refreshes are performed subsequently.

Adding index doesn't help much in this case and concurrent refresh is
even slower:

postgres=# refresh materialized view concurrently teller_avgs;
REFRESH MATERIALIZED VIEW
Time: 56981.772 ms (00:56.982)

So it seems to be more efficient to drop and recreate materialized view
rather than refresh it. At least in this case.

--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

imv.svgimage/svg+xml; name=imv.svgDownload
#157Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Konstantin Knizhnik (#152)
Re: Implementing Incremental View Maintenance

On Wed, 11 Nov 2020 19:10:35 +0300
Konstantin Knizhnik <k.knizhnik@postgrespro.ru> wrote:

Thank you for reviewing this patch!

The patch is not applied to the current master because makeFuncCall
prototype is changed,
I fixed it by adding COAERCE_CALL_EXPLICIT.

The rebased patch was submitted.

Ooops! Now TPS are much lower:

tps = 141.767347 (including connections establishing)

Speed of updates is reduced more than 70 times!
Looks like we loose parallelism because almost the same result I get
with just one connection.

As you and Ishii-san mentioned in other posts, I think the reason would be a
table lock on the materialized view that is acquired during view maintenance.
I will explain more a bit in another post.

4. Finally let's create one more view (it is reasonable to expect that
analytics will run many different queries and so need multiple views).

create incremental materialized view teller_avgs as select
t.tid,avg(abalance) from pgbench_accounts a join pgbench_tellers t on
a.bid=t.bid group by t.tid;

It is great that not only simple aggregates like SUM are supported, but
also AVG.
But insertion speed now is reduced twice - 72TPS.

Yes, the current implementation takes twice time for updating a table time
when a new incrementally maintainable materialized view is defined on the
table because view maintenance is performed for each view.

So good news is that incremental materialized views really work.
And bad news is that maintenance overhead is too large which
significantly restrict applicability of this approach.
Certainly in case of dominated read-only workload such materialized
views can significantly improve performance.
But unfortunately my dream that them allow to combine OLAP+OLPT is not
currently realized.

As you concluded, there is a large overhead on updating base tables in the
current implementation because it is immediate maintenance in which the view
is updated in the same sentence where its base table is modified. Therefore,
this is not suitable to OLTP workload where there are frequent updates of
tables.

For suppressing maintenance overhead in such workload, we have to implement
"deferred maintenance" which collects table change logs and updates the view
in another transaction afterward.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#158Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Konstantin Knizhnik (#156)
Re: Implementing Incremental View Maintenance

On Thu, 12 Nov 2020 15:37:42 +0300
Konstantin Knizhnik <k.knizhnik@postgrespro.ru> wrote:

Well, creation of proper indexes for table is certainly responsibility
of DBA.
But users may not consider materialized view as normal table. So the
idea that index should
be explicitly created for materialized view seems to be not so obvious.
From the other side, implementation of materialized view knows which
index is needed for performing efficient incremental update.
I wonder if it can create such index itself implicitly or at least
produce notice with proposal to create such index.

That makes sense. Especially for aggregate views, it is obvious that
creating an index on expressions used in GROUP BY is effective. For
other views, creating an index on columns that come from primary keys
of base tables would be effective if any.

However, if any base table doesn't have a primary or unique key or such
key column is not contained in the view's target list, it is hard to
decide an appropriate index on the view. We can create an index on all
columns in the target list, but it could cause overhead on view maintenance.
So, just producing notice would be better for such cases.

I looked throw your patch for exclusive table locks and found this
fragment in matview.c:

    /*
     * Wait for concurrent transactions which update this materialized
view at
     * READ COMMITED. This is needed to see changes committed in other
     * transactions. No wait and raise an error at REPEATABLE READ or
     * SERIALIZABLE to prevent update anomalies of matviews.
     * XXX: dead-lock is possible here.
     */
    if (!IsolationUsesXactSnapshot())
        LockRelationOid(matviewOid, ExclusiveLock);
    else if (!ConditionalLockRelationOid(matviewOid, ExclusiveLock))

I replaced it with RowExlusiveLock and ... got 1437 TPS with 10 connections.
It is still about 7 times slower than performance without incremental view.
But now the gap is not so dramatic. And it seems to be clear that this
exclusive lock on matview is real show stopper for concurrent updates.
I do not know which race conditions and anomalies we can get if replace
table-level lock with row-level lock here.

I explained it here:
/messages/by-id/20200909092752.c91758a1bec3479668e82643@sraoss.co.jp

For example, suppose there is a view V = R*S that joins tables R and S,
and there are two concurrent transactions T1 which changes table R to R'
and T2 which changes S to S'. Without any lock, in READ COMMITTED mode,
V would be updated to R'*S in T1, and R*S' in T2, so it would cause
inconsistency. By locking the view V, transactions T1, T2 are processed
serially and this inconsistency can be avoided.

Especially, suppose that tuple dR is inserted into R in T1, and dS is
inserted into S in T2, where dR and dS will be joined in according to
the view definition. In this situation, without any lock, the change of V is
computed as dV=dR*S in T1, dV=R*dS in T2, respectively, and dR*dS would not
be included in the results. This inconsistency could not be resolved by
row-level lock.

But I think that this problem should be addressed in any case: single
client update mode is very rare scenario.

This behavior is explained in rules.sgml like this:

+<sect2>
+<title>Concurrent Transactions</title>
+<para>
+    Suppose an <acronym>IMMV</acronym> is defined on two base tables and each
+    table was modified in different a concurrent transaction simultaneously.
+    In the transaction which was committed first, <acronym>IMMV</acronym> can 
+    be updated considering only the change which happened in this transaction.
+    On the other hand, in order to update the view correctly in the transaction
+    which was committed later, we need to know the changes occurred in
+    both transactions.  For this reason, <literal>ExclusiveLock</literal>
+    is held on an <acronym>IMMV</acronym> immediately after a base table is
+    modified in <literal>READ COMMITTED</literal> mode to make sure that
+    the <acronym>IMMV</acronym> is updated in the latter transaction after
+    the former transaction is committed.  In <literal>REPEATABLE READ</literal>
+    or <literal>SERIALIZABLE</literal> mode, an error is raised immediately
+    if lock acquisition fails because any changes which occurred in
+    other transactions are not be visible in these modes and 
+    <acronym>IMMV</acronym> cannot be updated correctly in such situations.
+</para>
+</sect2>

Hoever, should we describe explicitly its impact on performance here?

I attached to this mail profile of pgbench workload with defined
incremental view (with index).
May be you will find it useful.

Thank you for your profiling! Hmm, it shows that overhead of executing
query for calculating the delta (refresh_mateview_datfill) and applying
the delta (SPI_exec) is large.... I will investigate if more optimizations
to reduce the overhead is possible.

One more disappointing observation of materialized views (now
non-incremental).
Time of creation of non-incremental materialized view is about 18 seconds:

postgres=# create materialized view teller_avgs as select
t.tid,avg(abalance) from pgbench_accounts a join pgbench_tellers t on
a.bid=t.bid group by t.tid;
SELECT 1000
Time: 17795.395 ms (00:17.795)

But refresh of such view takes 55 seconds:

postgres=# refresh materialized view teller_avgs;
REFRESH MATERIALIZED VIEW
Time: 55500.381 ms (00:55.500)

Hmm, interesting... I would like to investigate this issue, too.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#159Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Yugo NAGATA (#158)
Re: Implementing Incremental View Maintenance

On 24.11.2020 12:21, Yugo NAGATA wrote:

I replaced it with RowExlusiveLock and ... got 1437 TPS with 10 connections.
It is still about 7 times slower than performance without incremental view.
But now the gap is not so dramatic. And it seems to be clear that this
exclusive lock on matview is real show stopper for concurrent updates.
I do not know which race conditions and anomalies we can get if replace
table-level lock with row-level lock here.

I explained it here:
/messages/by-id/20200909092752.c91758a1bec3479668e82643@sraoss.co.jp

For example, suppose there is a view V = R*S that joins tables R and S,
and there are two concurrent transactions T1 which changes table R to R'
and T2 which changes S to S'. Without any lock, in READ COMMITTED mode,
V would be updated to R'*S in T1, and R*S' in T2, so it would cause
inconsistency. By locking the view V, transactions T1, T2 are processed
serially and this inconsistency can be avoided.

Especially, suppose that tuple dR is inserted into R in T1, and dS is
inserted into S in T2, where dR and dS will be joined in according to
the view definition. In this situation, without any lock, the change of V is
computed as dV=dR*S in T1, dV=R*dS in T2, respectively, and dR*dS would not
be included in the results. This inconsistency could not be resolved by
row-level lock.

But I think that this problem should be addressed in any case: single
client update mode is very rare scenario.

This behavior is explained in rules.sgml like this:

+<sect2>
+<title>Concurrent Transactions</title>
+<para>
+    Suppose an <acronym>IMMV</acronym> is defined on two base tables and each
+    table was modified in different a concurrent transaction simultaneously.
+    In the transaction which was committed first, <acronym>IMMV</acronym> can
+    be updated considering only the change which happened in this transaction.
+    On the other hand, in order to update the view correctly in the transaction
+    which was committed later, we need to know the changes occurred in
+    both transactions.  For this reason, <literal>ExclusiveLock</literal>
+    is held on an <acronym>IMMV</acronym> immediately after a base table is
+    modified in <literal>READ COMMITTED</literal> mode to make sure that
+    the <acronym>IMMV</acronym> is updated in the latter transaction after
+    the former transaction is committed.  In <literal>REPEATABLE READ</literal>
+    or <literal>SERIALIZABLE</literal> mode, an error is raised immediately
+    if lock acquisition fails because any changes which occurred in
+    other transactions are not be visible in these modes and
+    <acronym>IMMV</acronym> cannot be updated correctly in such situations.
+</para>
+</sect2>

Hoever, should we describe explicitly its impact on performance here?

Sorry, I didn't think much about this problem.
But I think that it is very important to try to find some solution of
the problem.
The most obvious optimization is not to use exclusive table lock if view
depends just on one table (contains no joins).
Looks like there are no any anomalies in this case, are there?

Yes, most analytic queries contain joins (just two queries among 22
TPC-H  have no joins).
So may be this optimization will not help much.

I wonder if it is possible to somehow use predicate locking mechanism of
Postgres to avoid this anomalies without global lock?

--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#160Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Konstantin Knizhnik (#159)
Re: Implementing Incremental View Maintenance

On Tue, 24 Nov 2020 12:46:57 +0300
Konstantin Knizhnik <k.knizhnik@postgrespro.ru> wrote:

On 24.11.2020 12:21, Yugo NAGATA wrote:

I replaced it with RowExlusiveLock and ... got 1437 TPS with 10 connections.
It is still about 7 times slower than performance without incremental view.
But now the gap is not so dramatic. And it seems to be clear that this
exclusive lock on matview is real show stopper for concurrent updates.
I do not know which race conditions and anomalies we can get if replace
table-level lock with row-level lock here.

I explained it here:
/messages/by-id/20200909092752.c91758a1bec3479668e82643@sraoss.co.jp

For example, suppose there is a view V = R*S that joins tables R and S,
and there are two concurrent transactions T1 which changes table R to R'
and T2 which changes S to S'. Without any lock, in READ COMMITTED mode,
V would be updated to R'*S in T1, and R*S' in T2, so it would cause
inconsistency. By locking the view V, transactions T1, T2 are processed
serially and this inconsistency can be avoided.

Especially, suppose that tuple dR is inserted into R in T1, and dS is
inserted into S in T2, where dR and dS will be joined in according to
the view definition. In this situation, without any lock, the change of V is
computed as dV=dR*S in T1, dV=R*dS in T2, respectively, and dR*dS would not
be included in the results. This inconsistency could not be resolved by
row-level lock.

But I think that this problem should be addressed in any case: single
client update mode is very rare scenario.

This behavior is explained in rules.sgml like this:

+<sect2>
+<title>Concurrent Transactions</title>
+<para>
+    Suppose an <acronym>IMMV</acronym> is defined on two base tables and each
+    table was modified in different a concurrent transaction simultaneously.
+    In the transaction which was committed first, <acronym>IMMV</acronym> can
+    be updated considering only the change which happened in this transaction.
+    On the other hand, in order to update the view correctly in the transaction
+    which was committed later, we need to know the changes occurred in
+    both transactions.  For this reason, <literal>ExclusiveLock</literal>
+    is held on an <acronym>IMMV</acronym> immediately after a base table is
+    modified in <literal>READ COMMITTED</literal> mode to make sure that
+    the <acronym>IMMV</acronym> is updated in the latter transaction after
+    the former transaction is committed.  In <literal>REPEATABLE READ</literal>
+    or <literal>SERIALIZABLE</literal> mode, an error is raised immediately
+    if lock acquisition fails because any changes which occurred in
+    other transactions are not be visible in these modes and
+    <acronym>IMMV</acronym> cannot be updated correctly in such situations.
+</para>
+</sect2>

Hoever, should we describe explicitly its impact on performance here?

Sorry, I didn't think much about this problem.
But I think that it is very important to try to find some solution of
the problem.
The most obvious optimization is not to use exclusive table lock if view
depends just on one table (contains no joins).
Looks like there are no any anomalies in this case, are there?

Thank you for your suggestion! That makes sense.

Yes, most analytic queries contain joins (just two queries among 22
TPC-H  have no joins).
So may be this optimization will not help much.

Yes, but if a user want to incrementally maintain only aggregate views on a large
table, like TPC-H Q1, it will be helpful. For this optimization, we have to only
check the number of RTE in the rtable list and it would be cheap.

I wonder if it is possible to somehow use predicate locking mechanism of
Postgres to avoid this anomalies without global lock?

You mean that, ,instead of using any table lock, if any possibility of the
anomaly is detected using predlock mechanism then abort the transaction?

I don't have concrete idea to implement it and know if it is possible yet,
but I think it is worth to consider this. Thanks.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#161Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Yugo NAGATA (#160)
Re: Implementing Incremental View Maintenance

On 24.11.2020 13:11, Yugo NAGATA wrote:

I wonder if it is possible to somehow use predicate locking mechanism of
Postgres to avoid this anomalies without global lock?

You mean that, ,instead of using any table lock, if any possibility of the
anomaly is detected using predlock mechanism then abort the transaction?

Yes. If both transactions are using serializable isolation level, then
lock is not needed, isn't it?
So at least you can add yet another simple optimization: if transaction
has serializable isolation level,
then exclusive lock is not required.

But I wonder if we can go further so that even if transaction is using
read-committed or repeatable-read isolation level,
we still can replace exclusive table lock with predicate locks.

The main problem with this approach (from my point of view) is the
predicate locks are able to detect conflict but not able to prevent it.
I.e. if such conflict is detected then transaction has to be aborted.
And it is not always desirable, especially because user doesn't expect
it: how can insertion of single record with unique keys in a table cause
transaction conflict?
And this is what will happen in your example with transactions T1 and T2
inserting records in R and S tables.

And what do you think about backrgound update of materialized view?
On update/insert trigger will just add record to some "delta" table and
then some background worker will update view.
Certainly in this case we loose synchronization between main table and
materialized view (last one may contain slightly deteriorated data).
But in this case no exclusive lock is needed, isn't it?

--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#162Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Konstantin Knizhnik (#161)
Re: Implementing Incremental View Maintenance

On Wed, 25 Nov 2020 15:16:05 +0300
Konstantin Knizhnik <k.knizhnik@postgrespro.ru> wrote:

On 24.11.2020 13:11, Yugo NAGATA wrote:

I wonder if it is possible to somehow use predicate locking mechanism of
Postgres to avoid this anomalies without global lock?

You mean that, ,instead of using any table lock, if any possibility of the
anomaly is detected using predlock mechanism then abort the transaction?

Yes. If both transactions are using serializable isolation level, then
lock is not needed, isn't it?
So at least you can add yet another simple optimization: if transaction
has serializable isolation level,
then exclusive lock is not required.

As long as we use the trigger approach, we can't handle concurrent view maintenance
in either repeatable read or serializable isolation level. It is because one
transaction (R= R+dR) cannot see changes occurred in another transaction (S'= S+dS)
in such cases, and we cannot get the incremental change on the view (dV=dR*dS).
Therefore, in the current implementation, the transaction is aborted when the
concurrent view maintenance happens in repeatable read or serializable.

But I wonder if we can go further so that even if transaction is using
read-committed or repeatable-read isolation level,
we still can replace exclusive table lock with predicate locks.

The main problem with this approach (from my point of view) is the
predicate locks are able to detect conflict but not able to prevent it.
I.e. if such conflict is detected then transaction has to be aborted.
And it is not always desirable, especially because user doesn't expect
it: how can insertion of single record with unique keys in a table cause
transaction conflict?
And this is what will happen in your example with transactions T1 and T2
inserting records in R and S tables.

Yes. I wonder that either aborting transaction or waiting on locks is unavoidable
when a view is incrementally updated concurrently (at least in the immediate
maintenance where a view is update in the same transaction that updates the base
table).

And what do you think about backrgound update of materialized view?
On update/insert trigger will just add record to some "delta" table and
then some background worker will update view.
Certainly in this case we loose synchronization between main table and
materialized view (last one may contain slightly deteriorated data).
But in this case no exclusive lock is needed, isn't it?

Of course, we are considering this type of view maintenance. This is
deferred maintenance where a view is update after the transaction
that updates the base tables is committed. Views can be updated in
bacground in a appropreate timing or as a response of a user command.

To implement this, we needs a mechanism to maintain change logs which
records changes of base tables. We think that implementing this infrastructure
is not trivial work, so, in the first patch proposal, we decided to start from
immediate approach which needs less code.

--
Yugo NAGATA <nagata@sraoss.co.jp>

#163Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Yugo NAGATA (#162)
Re: Implementing Incremental View Maintenance

On 25.11.2020 16:06, Yugo NAGATA wrote:

On Wed, 25 Nov 2020 15:16:05 +0300
Konstantin Knizhnik <k.knizhnik@postgrespro.ru> wrote:

On 24.11.2020 13:11, Yugo NAGATA wrote:

I wonder if it is possible to somehow use predicate locking mechanism of
Postgres to avoid this anomalies without global lock?

You mean that, ,instead of using any table lock, if any possibility of the
anomaly is detected using predlock mechanism then abort the transaction?

Yes. If both transactions are using serializable isolation level, then
lock is not needed, isn't it?
So at least you can add yet another simple optimization: if transaction
has serializable isolation level,
then exclusive lock is not required.

As long as we use the trigger approach, we can't handle concurrent view maintenance
in either repeatable read or serializable isolation level. It is because one
transaction (R= R+dR) cannot see changes occurred in another transaction (S'= S+dS)
in such cases, and we cannot get the incremental change on the view (dV=dR*dS).
Therefore, in the current implementation, the transaction is aborted when the
concurrent view maintenance happens in repeatable read or serializable.

Sorry, may be I do not correctly understand you or you do not understand me.
Lets consider two serializable transactions (I do not use view or
triggers, but perform correspondent updates manually):

create table t(pk integer, val int);
create table mat_view(gby_key integer primary key, total bigint);
insert into t values (1,0),(2,0);
insert into mat_view values (1,0),(2,0);

Session 1: Session 2:

begin isolation level serializable;
begin isolation level serializable;
insert into t values (1,200);                         insert into t
values (1,300);
update mat_view set total=total+200  where gby_key=1;
update mat_view set total=total+300 where gby_key=1;
<blocked>
commit;
ERROR:  could not serialize access due to concurrent update

So both transactions are aborted.
It is expected behavior for serializable transactions.
But if transactions updating different records of mat_view, then them
can be executed concurrently:

Session 1: Session 2:

begin isolation level serializable;
begin isolation level serializable;
insert into t values (1,200);                         insert into t
values (2,300);
update mat_view set total=total+200  where gby_key=1;
update mat_view set total=total+300 where gby_key=2;
commit;                                                      commit;

So, if transactions are using serializable isolation level, then we can
update mat view without exclusive lock
and if there is not conflict, this transaction can be executed concurrently.

Please notice, that exclusive lock doesn't prevent conflict in first case:

Session 1: Session 2:

begin isolation level serializable;
begin isolation level serializable;
insert into t values (1,200);                         insert into t
values (1,300);
lock table mat_view;
update mat_view set total=total+200  where gby_key=1;
lock table mat_view;
<blocked>
commit;
update mat_view set total=total+300 where gby_key=1;
commit;
ERROR:  could not serialize access due to concurrent update

So do you agree that there are no reasons for using explicit lock for
serializable transactions?

--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#164Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Konstantin Knizhnik (#163)
Re: Implementing Incremental View Maintenance

On Wed, 25 Nov 2020 18:00:16 +0300
Konstantin Knizhnik <k.knizhnik@postgrespro.ru> wrote:

On 25.11.2020 16:06, Yugo NAGATA wrote:

On Wed, 25 Nov 2020 15:16:05 +0300
Konstantin Knizhnik <k.knizhnik@postgrespro.ru> wrote:

On 24.11.2020 13:11, Yugo NAGATA wrote:

I wonder if it is possible to somehow use predicate locking mechanism of
Postgres to avoid this anomalies without global lock?

You mean that, ,instead of using any table lock, if any possibility of the
anomaly is detected using predlock mechanism then abort the transaction?

Yes. If both transactions are using serializable isolation level, then
lock is not needed, isn't it?
So at least you can add yet another simple optimization: if transaction
has serializable isolation level,
then exclusive lock is not required.

As long as we use the trigger approach, we can't handle concurrent view maintenance
in either repeatable read or serializable isolation level. It is because one
transaction (R= R+dR) cannot see changes occurred in another transaction (S'= S+dS)
in such cases, and we cannot get the incremental change on the view (dV=dR*dS).
Therefore, in the current implementation, the transaction is aborted when the
concurrent view maintenance happens in repeatable read or serializable.

Sorry, may be I do not correctly understand you or you do not understand me.
Lets consider two serializable transactions (I do not use view or
triggers, but perform correspondent updates manually):

create table t(pk integer, val int);
create table mat_view(gby_key integer primary key, total bigint);
insert into t values (1,0),(2,0);
insert into mat_view values (1,0),(2,0);

Session 1: Session 2:

begin isolation level serializable;
begin isolation level serializable;
insert into t values (1,200);                         insert into t
values (1,300);
update mat_view set total=total+200  where gby_key=1;
update mat_view set total=total+300 where gby_key=1;
<blocked>
commit;
ERROR:  could not serialize access due to concurrent update

So both transactions are aborted.
It is expected behavior for serializable transactions.
But if transactions updating different records of mat_view, then them
can be executed concurrently:

Session 1: Session 2:

begin isolation level serializable;
begin isolation level serializable;
insert into t values (1,200);                         insert into t
values (2,300);
update mat_view set total=total+200  where gby_key=1;
update mat_view set total=total+300 where gby_key=2;
commit;                                                      commit;

So, if transactions are using serializable isolation level, then we can
update mat view without exclusive lock
and if there is not conflict, this transaction can be executed concurrently.

Please notice, that exclusive lock doesn't prevent conflict in first case:

Session 1: Session 2:

begin isolation level serializable;
begin isolation level serializable;
insert into t values (1,200);                         insert into t
values (1,300);
lock table mat_view;
update mat_view set total=total+200  where gby_key=1;
lock table mat_view;
<blocked>
commit;
update mat_view set total=total+300 where gby_key=1;
commit;
ERROR:  could not serialize access due to concurrent update

So do you agree that there are no reasons for using explicit lock for
serializable transactions?

Yes, I agree. I said an anomaly could occur in repeatable read and serializable
isolation level, but it was wrong. In serializable, the transaction will be
aborted in programmable cases due to predicate locks, and we don't need the lock.

However, in repeatable read, the anomaly still could occurs when the table is
defined on more than one base tables even if we lock the view. To prevent it,
the only way I found is aborting the transaction forcedly in such cases for now.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#165Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Konstantin Knizhnik (#159)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

Attached is the revised patch (v20) to add support for Incremental
Materialized View Maintenance (IVM).

In according with Konstantin's suggestion, I made a few optimizations.

1. Creating an index on the matview automatically

When creating incremental maintainable materialized view (IMMV)s,
a unique index on IMMV is created automatically if possible.

If the view definition query has a GROUP BY clause, the index is created
on the columns of GROUP BY expressions. Otherwise, if the view contains
all primary key attributes of its base tables in the target list, the index
is created on these attributes. Also, if the view has DISTINCT,
a unique index is created on all columns in the target list.
In other cases, no index is created.

In all cases, a NOTICE message is output to inform users that an index is
created or that an appropriate index is necessary for efficient IVM.

2. Use a weaker lock on the matview if possible

If the view has only one base table in this query, RowExclusiveLock is
held on the view instead of AccessExclusiveLock, because we don't
need to wait other concurrent transaction's result in order to
maintain the view in this case. When the same row in the view is
affected due to concurrent maintenances, a row level lock will
protect it.

On Tue, 24 Nov 2020 12:46:57 +0300
Konstantin Knizhnik <k.knizhnik@postgrespro.ru> wrote:

The most obvious optimization is not to use exclusive table lock if view
depends just on one table (contains no joins).
Looks like there are no any anomalies in this case, are there?

I confirmed the effect of this optimizations.

First, when I performed pgbench (SF=100) without any materialized views,
the results is :

pgbench test4 -T 300 -c 8 -j 4
latency average = 6.493 ms
tps = 1232.146229 (including connections establishing)

Next, created a view as below, I performed the same pgbench.
CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm2 AS
SELECT bid, count(abalance), sum(abalance), avg(abalance)
FROM pgbench_accounts GROUP BY bid;

The result is here:

[the previous version (v19 with exclusive table lock)]
- latency average = 77.677 ms
- tps = 102.990159 (including connections establishing)

[In the latest version (v20 with weaker lock)]
- latency average = 17.576 ms
- tps = 455.159644 (including connections establishing)

There is still substantial overhead, but we can see that the effect
of the optimization.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v20.tar.gzapplication/gzip; name=IVM_patches_v20.tar.gzDownload
����_�<kw����j~����Ko�$�c��[�����uKH#[���$�8m���{��L\;�]7j#@������{N�3=4.y0�����'88Z�~�������gj��RjMU�����=c��@f����3��n~��������7=zK�^�������C�<�=�s/��m�r0���g�C_w;�=��������92�Y����ZS�����ME{��oA�����{S�N�ZG���nL�
�l[��R�:�����j+�F�0����!�1��e��g�0�`��o@#��/�Pg�t������ �^���U�X�>{��%x�s>�SRg��`E��(��|���>������;���j�������b�P,�%�,��%b�P,����t��f.w����;�]���
=���7����/.��"�z�}��|�Mfy>��q�>���L>/+�
\��>�b��O����o�����-�q�P�pC�����X<���/�4�8����s�DC���������r6���j_\����{�����Ar����D���+��9�Fu�W@���M��kU9�b�?X[a��	�v
gn�U8��i����e#����]X1��/@r�����\��-���v���=�N��0�-3j��D��u�R��us�0
�S4dH�{g��������X�V���&+���0�����-�����r$�^�8��A�cl�9�����`���5w�@F�;���7Ip*���!��>D��W
�s
l�}�0�E� ������������`n��5]����	�
8A"��b��4.-�JoN=+d#�:�$Et*�4B��J���cRQDAs����E��j��`)~��0���U@����q� �t�a[;MbkCQJ�{��a�slg��#k����C04A�v�{4�1��!;7�^�����i9��,�5�z��O=���m�����=f��d�d����;Q���`�Y� f�!��� �	��q�L�*;0��L ���
bR��	�h*���;�K9`��w���C���3
�p�ZSp&�##U����9;;��j��3�9R�z�V����Qbu3Z�@]<<�@?>F���+8j�&�v��A��PH��ad���t�n�K�@L~���U��q�l��I��E��We
��<d 
�%\��9ia!�pmw���e�q��
���B��4P���%�?���Zo����8�:�gbO�D�}A1���.�&���I��K�M����Oy��a����3�5��3n��
���B��s!#�@���������AHd���,*aF�'��
��s$l/F`�.��N�i#/!D�E���k���)�B��5��L�~�!
�}��@�
�����Z%v45GH!C|r��A?����z���Op������/��;=��q���c���yt��c���(\�;C����P]��|Z$�J����)���0o��`p6(��M�(�.{�������X������k-.������;F�}���H �H��d�u$/0��� ����*���������!���������;�2�D�������,:�s2��N_hy��?��2�w
��]<���H+���Kg�c������`��"�����r�*"��<\��d�Q���d�jG����#�
yT��OaD	4�2�5��	L{f��"�T�}A���"-�/YYE�-���`l�wr cB�}��m����)�F�g�z�����`(�8�`Qzd��JA�#�A�f�^RU�N�����2y�@*G
�_��H��1�b����v�\���	
 9Pe��x�l4^>HD3%��x�����+i$���P��PY�<�4.G^L�(���]�����s�����h���x6>]9����)��z�}Yf�R@�b�����v���P�z�lVK�Jj��Z)�P�gbc�����4�>�K7;�w8VE�Sav��O[xfpEJ���Nq��.)���x�Xz7s��������v�����3�g���h�z���eR�4u�E�X�5�
����A(nZ�rq� Yy����#.VW\s8�������A�}�3nQ��O2���M!;��z���~�)�?sA �8���>0�Eygq(a�l�NP������8 7��,�U�VE����5����;2�?���i��F�r0��<?,�n�m��l>�������?J]����&����i����4�v���f�e��n�:�Z[�u���M��X��S��4�����������^������i2�J&��He.7�.��j�m\2��%bX�{����:����qo8���FTs����A@`OmG���p0�PU��A%�{Mu<����%T������Z���arHUBQ�^(���	��2m L%�����g<Zt�����Q80����	�f!���Wg��V���#����["����66�wD$xT�z}M����T�N	��6�r_(zn�
�`����H����r�0�!����F{���*_�RJQhM�'!�vSULS�-�R��u��M��ES)iCW)5
CU��(��/T0��4���c$}�����\q?��L�����f`��3�[���&�S��#�${����U@pb�r5�,Y3�j�I��"�!����Q��Fb|���@����
RE���Q�e-F^���p��Gg�������|���F�����g�Q���<��N���.Z[X��<��K�'�JD�O
�Z�; �&���BAD`�����2�Z��1by�k!���`�����x2�bl��o�������[�;t��P��q(�D��V�bj-�����1�����i���]nqy�~4{L��i��)��k��Lcd������LU���f�R�w��ZPpphN�s�E�����^,�r�G!"�::=��'a?��%��wB���:%��y�;�#*E�F�������[�����&��	���GB{"��iM�8��Z��qPkM������c���b/�>QKpW�MA������0<�saEx��OM-���]���@���T�LH,Z.���8�n��J�|��
(�}�_�|h��
EUkv�0d� _/O,^4��d��4
�g
�FIb|K�Q���uJ��g�=������a�z�{�cZE��|�n�������M6t�	G�i�I��Urk����t��02|B���(��y���{Qz%�%�������t�LxF�U�����C��b�����y!q���2jVF>�����R0(.���
]��8ee�A�p�$����q���(�w��jS��#����I�1����dF�y�T�m����%���-,- <�������3,%!���Q�9����:f@�-��$c\���dV����Y��*dU��n��^o�[dU	�
IU2H�IS�	|
5���o�\��hJ��3������j"H_�����}�[�Q
�c?�/2g>�������������b���
�6�����]O���
?T�����g�l�.Z��Q�:�#F��6���O\�v�C�I�JIL�8RD�d�)3�L�.�~�
G�����g����]�1��P.J�����f���?��iG������^�x<��t�F����mwt��m�BM�(m�e��v���Rii\��J��\o�6\�Q���Q^�m,�G=�
�����9uR!Rq���x��R{F���|�B�1�e��o1����Y{D�=�HY!dR� m���i��V�h$��^���n��@d���be���\�h������GPBo|��m�X���N��m�?���6bD�������b���5De�b:W��.��A�����w��V���V��02��)���g���������c���\Wx�Vk��
o�VS3�����v6���v�8���($���������n��xg�8�g����6�"y����^����}��=,�y7C���N�U�1/�#��]\�g���V�GS�s����\w�,��K���X��E������_U����/|L��-?��A��r/��������������h�T����_�����M��v�f�2�F���6�V��iz���eu5H�����)���j����l�Z�N���XRV�K�b���9���T*U�l�C���j*G��Dl6:��j�Stq�������C�"������m��`@�n�c�A�eM|v2���]m5$���V�����[���d����h.��ZE�>ne�B2�_��t�_�����Jc-�z��z�����Ru�$w^5�u��;N�i������.�DR��O�"�$��w��;:�X7�[H>	hc�J�����}���;$�	$Zb[t�h9c��_�t��z����u��1L+�O�����v#����R�>��w��"cy|_�0�U+i��Z�}�����F�� ���'��W�&�,O�����O�*�x~U��f����i�?:<a�pa�;<��o��}�u�i����T"Ydv�S������(��~w�����O�6a��^�m�.�_{�w���G�0;�����>c�;{]Q��x��l7�h�����b���s�����V��n�UHT��,���p����5�\Q�z/^��������7���R
DE	�#�!�R'���7�;:��)q?�f6��t��2�!�p���1�t�#�l�����^1��V�=,t*�}��B'~j�M?5�Y�o5��?��5F�����:����KJ���������^��Q�F�/�.�@�(~6`��d����R�awh�d
��]w����=�:������u,��D.!o��
X�|�<p1ia����><��/"/Kz�S7���E0<g>M9�������x���}^0�d Miw`����f�+�����'���}�
����.;:;9?��1����x�@�oooS�3�5�����;y<8{��D��kC"���Yz"X[Y��q=��~9G����Y��U)z.����<a�+u�-']���3-u�a���%�
��?
"���������9����(���|�`t>���qr�{~���^@`��2:@�^HS_����9E�_��|)i�B�`���V���,�����I-^}��
��j�)�E>"�;�w"V���m��0�Vh����D�}P�PY�����IH�i��{��i��2�'�ri��"h[��h����3��<s]���0	v0��$�
je�S
Ou<5��o&aK����`	�=������(��o��1y�U.�w�����4_(A�t����������-zK#�TSe�������9m��3���vpv��k�/�0�V�
�v���\��K�(.�S8���uA��"�����|.Q�A��(,b�^Q���?�e�����q��q��� W����5��Y�����J`=h���5���t�_��*�������Z��c��|�f�Nh�P���bk^l������#��i���
e�8�g��\��x����'���YY��U��{��F�,����)r��n��0h�2����Y�H��}���PHe�u����9���/�b����X$��t�
T�[FD�W:����["����J���%�`�4E��x<���j������J���S����z~���{X4��P�B$�2������?�ws��h��Y5���,v&�"���}�8���J�L04��Eb���t���9�`�G�<�����07vw���
0��q}��
F
�C�)�uWC���O�i��2_��f�
\6�?�1O��+)#A��� ^����1<}M�o��6�|)���f�,W����������$�����YZ�����H�����)Z��W~]����=�k�NcW�d_����e�3�gY6�<��&c���}{�XbE��Y�Rdu��U��g�?���Y���|A%z���/��g1�O�G�YL�~�T1��P*FU��8/
����t�
`DY�\���j�7��m��>�}=yS/@�Q��1�9\������l����{��%�+����K�����������6���Q�s��{h;3NWLUf�N�p�.g� �QPQ\Lu���
(�y��6
{o��G�u�&�uS���FJ-
�!Q*������@C7�,lB�����������a��1������"�Z@
tN�*�=w)���'-�&�7�� ��)JT	N�H���;�����$1@�R2��,!��F�YY$��'O
�;<&��77�,������)����q��-L��#p����g.������of�T�gYz��N�
v7���iu������mF�L
t��F����d�	pG�U��d��5�9����{�M�R�2D=��,�H�P�Aa��`�1�Q����h�-���w���}��\��s�:yq�/��@��YHX���T��C����T�=�3h��|��~?b�Xt.���4� r�3�\:9��w����m$R/�uA�.%�������L1��x1J�E)���]L�*~2U����%���j�i�)�k*�a�%V?P��NP0��{�B�E��&��x�%i��p^{_9�M���[��xn1�F:DO�`U�!�D��Q���r\�'r)����#����e@��������f��<7��q�$��)����D���!r�]&���
�s��.<7�b�����;."�v���X�$i���0�K�s����l���N��"��(��FW�0:5�@(��2A�A
I����8+@&{7e6���Pjm\a;u�Ja��*��h�����I�n���t�F���('�G�{e���i�
9�)v��X4#�:�����P�Fj	��ZBGu��0����H6>I_��GH��-�3�x*��y�"a�x�BLY���L�Z$l[O*�����MsA�i��$4M/����Ja����[J���-q��TTxl��}�����Oq${�d������X�fPtc.��~Yi��u�*]�^�gFRcM]���L����u7���i�BrLxJk��(�.@\-�!���5*����� ��b�Od��ej�.�E���%,G�;X�rRT��]�D����fr^�V���..K#[�6eX��1��5y��������
(��_����C������#/cU��U}�U�MQN�S��2"�N��L+T���bc[���(jq��iniEsZ��x��S���{b�{l�����Hr��U����fp�����:��w��S�r<�$w�2"v$�$7����<�Y��@��XNh�$�W�- �F_-;�_=�/�u����;n��M&�LzH=d���[9T���WB�A����kV�z� Vh��]��vcNQ\S����m�'p�K3��f��N���w�l���H�$�CZFMv��������!�0[�����Z���~������g$�[�tL��D:|T��o�kQr��L�MZ�t�:�&/���.�2	n�L�@t���`2$�����	YV(��B�Q�P�F�@f��w��f�>�#��'0N,�����;E���!,������+����(�~���,/B�R��^d���sB^�6���u�����W!��$jF�|���T$Z�XH�h�k��t/���Z\e0@�0S@�0�9fm�����X��^VNP�8-r�����(�N���27�P ���&�
$D
$���a�d)�*�$�=X�e�@'Va����e���,�g�gM�k)�Z���y�{n�8�A�������b���Nj�v(��&�N�;�]����#������3����O������8�O��`3%^�
�PN������A|*+	~��#�#�����~b��}�P�umd���.a+��.��I�����U���?X�����K���\�G��*.�)g���x�K�ln��}sSTO�����[�~z,5������C���G�uQ99��~�7���.Oq6?��x�Bz[=l�a��d`R�}�A��?�\M0������q��/���o"�w��'���cu=�p��@E#v�9��o��hC��w�������W>���pf,���f�qJ���T��N���!by�����FQ�O�k|���QR>�c���(��<��2�'�^9��jG�z/%=�N�����ZS�`T�|���t��
����wA
h:�S�+a}���D;��q.�xq�����U
���wbc�i����wZ7%��Q�[�L$���f)���< �2�U��H��<��ROl��Px<���G`��p'c���$\+	�we	��6@��Pd��HY��O�d���S�=�ub���n+����Q�	P�l-�g�P1�I_�:'�����!���{�
�/SHp���aC�KE���8�����S�����mhe"���z�^��U>Xrp}�-�0p]r�]�����u�7e����0���������qV�����wD�vp'��O��X���-�w����������vi(�p#�wk�����n`[g�~,����=|���.���D
�]X+��D���4��@�;�2.
B��M�i!�S�>U=[�����-��v�;Y�%k}�6�Y,vK����u���i���T��W.��[�<���j��_)I�W���ri~D+���;�&3V<�����r]����9g��-�iqQ���!�;��i}���G7�.�X�2�F��`6��h}Z��)B�d��2���$\-�m�|T7�����gt]����'f	=��}�2�.nK���e�N��O�nyH��,Z��&� ��1��%n�{@�|w��tE{A�S���c�[�L�$kO��3��]�e�1lm�!��>)/����Lt&�#��$C����rMkOb��mH����s��MbOu��0n����s}lS�,S]+{X�SfY���1�����=-����e�Y0���c������(J���j��ZVf��fe��p���fY�z�y�?_X�L,��e�x��=�AcQ3{r����P��b��X��ay���5��!
���4D��V�4<��� �)V�U���
�{rAp����~�;,��)e���=\��?c|������(�>)/3V�{p����������&��������
�e��|G�kOfO8��qa���CK�z$��N�����#��g��j�[e�Z��W#���V�4 -+���6��J���;����1v��!�����p���S�wH7���K���s��.LoKC���������Uv�2���Vb�z��LS�/��L�������~������LeY1\O5�o&���g\ZZl�3��ary8��������O�7pS��w�t�2wd����n�
�������4���|�s]e*Z��W�����������LE���C�AfS����(���X/����,+������Y�{�:<�]Jl-XV:�����c�i���_����^��Y��y����#�N�z�y���`j��'_!8�!���~|��1B)��9����,�,���%b��du��V���Ou���,P����n�=������''�}?v��!TL~�����f����`��e7Y��Cn���	��������3���3��-JI3�uYtz�qU��Q/�������W	���?�-��W+;k����AO����8��ute�OV����Z�=g/����?�^�gOL~[�@���3>���waC�Ns*�2�e�k��
��������,t�i��i�
i��{�tH�9=z|\�Br��-+���>$P�;�W�N���ZO�P5�Yji:���tGZ���[y�=p�'���i����/�
�$f��6��M�;N�����7[��W���������/V������������,-c�S������ZYbV��'fXy�<9P~���y������b9�'g�XVF�6E,+��U�����
�1�eE��U������5�2����H����h���4WwY��-���`f����%������cw��'�k�T����;����HW�=�H�'��K��s}l��i|b�*�t�����c}{�I��H�IK���a}{Z�W�`�Y������2�b+������n�����nab����}��I.�{�Z]��guzj��9e���Og3�.�Jg�D�=N`��F��������LeY�/O5��!��;���,+"���"��<<J|�V��6���f�f3?i�s��z2>+g����^9��)���V�Y��J�rgK�a������[]{�;�YA���d�=g��y���(|����Bd.��F��T4>�CP��������f��a�
��D.-v�e9���%����gm�5������9-�Mj��=�����2%�$i������d���<���'����=e��1��n>I_s�O�&���>
��w�C�C��r�+&�X^qF��������KIsI��]���!#���]�&r��7���i�,r�>����iL�6�$�������������~�A(==S�4x�����8E�6�����'������p8�F�w{����;��/Fv�������r|�Lw��6v|�lx�0X���������K���vq�����8��/�Ok�oE!���~J��I!TM��p7)m��4�i���6��%gQPo�&��8�S�Z�!m�N�����yH"Bw�	�t�$�v���qEW1�"Z� ��[�V�E�x�i4�R��l�*&�3
��5���K��}vz��2i����`��w�Wb�h;��bt;t��~������h_��CU�n�]�����Q�H�/n?��G����s!_��n�~*��MxyR9}{Yy��F
���~^���.���O�&U�������Ts.
t�[a���vS�.�_�1�a�AA��Ge{Wc$3����`W��W�+��#��N?\����$><�����(�����w~�S������AW���}�������Wwv�����
't�D��!:��a�P~�.�>T�+��s6�&�������|�z���{N�F����V_����(�������F��u��o*�'u�^�(wy���9����nD�Y��e.�eI~��V-�����_"�B�@P�QCa�W+2M�hh"0\I�U���"d#%L��QF��Mk��0Q���_�a �Jd17�&*���3����D�G�qc{>���F��E����?�R�>�C�1��4qq�b�B=m�&JBP�U�$�����9@}t
������"Y���E|X�&p��x��kc���q�������0r�r�g�����}����[-�O{P6��6���F�3����V9��2��k�P��@���������(\c�U.�(5������_,Q=��Q==��A*���(e1�����>v��t�\3���@�z��;K�#�-����W�����$ �`�}��DP���OO
"���tLB�������O`%hC�i��0rf'k�A1,���]������Xf��
!b�3>�����E
P�����+A����o�w�?�e>������g�����Z*p��
�5C�/���f&&�3 ��k5 @Y9�i>*<&������������heA;�	�f��r���\B1������#�?������I�7��e����=h0Nc�����:���j1<����B��_��]������
�r��Y9�,�B/�Nru��wi�y��Z������/A�)��a�q*,++�9��
R��N(��DPX��sl;����nv�<��������av3�k/����"�p�qP}d����B6�����G������Z���<
qq�_�;����e���@h��(��������� 0����l���?����pl���U��q�v�]j �����l�}?3:�eF{������z�Y6Ja����8�@�l.��8>N����9	�0Brc���3����\��9���Y�0���6DimU�B���2��7'���ClJ��,�$����,#�']������e^����L�+�����2��C�1��0=�4�$S}�z���>�$H���������������1E���������&4�Er�c@=}��{N�lL�h% �����4�;M�������I5
Xs�-,n�m�f��]���3;!+�C��T���q��c s�t7q�����q�i�A���Rd&���N{9*q�ss_O������N���C�r��
�dH�(�Q��(E���r��Yz�5�������e�2�����~�3
�w�g�6`��O�"'��GwX(n�Q��X�O��>�z-����� `%�eB�C�}N������0/V�j{�������YCa��/Gl'F=���E������7�s�i]��v���]>r���S��5�;�1�w���)�I�2�dq��������)zn�#(�K�\��"�qo���	��1*����.�����G��`��"�����7Mu�*��Zg��LZ��z�$yf���	No8��YA��:E������`0E�T�������v��al�/�U���(s�ew2��S���q��^:6N�I>.oF|"0�H9��y�����=B���/78��9��l��
j�����Fc#a�0����(8_�����y���r;.�hVLZ�=:��\�����1i-n��RQ���3�����}������/���1<�������L�%S*��v7�����@����&����3
+6�����g&�����^��������;>M��f,S2M�h��7VL �z�	4va�%���AG��,6��J��l���TY8g~<�[�>(vyN����k18����e(��k�u-�1�����:7Nhck������E��F��Hd�{n?h3��6��
�8<g�+7'���p�6�M�%Jn�LjQ.<�i�.�\�������)��wg����P�HBeV����5����Y���}���H@��ko���Jr�`�\��mv�����&�a���������M�7c�_��~��V��t;�;F_�/����=G�-����������oj>�����n)Q��/�E`.�������JF�6�IOk�m[N/h�
������3f�E���&P!cSh
E��~���"N���]����{^9��V[TU��F(H�&#����(V�1����}Wd�����6o�kh6j��de���E7�������0sH�3�
�/�NU�N����;K�	�/aw���}�5�;sR��F���Xk.$K���B��t�hg�|�����j���
|��aV!�����+�d�RW��	V%��bB�[�	1lI�+�7��Y#���MB-�0%0v�2����@-�
���F1��xH�FAb�a�{T�8���a��`Z������Aj)��Y�A��}LAS��N���qB�g}k����H��#
$���*��h'�g�s�>s����0ivUi�9CS��%q&v0�������+������z>r��s��PO{"=lh���[�N{r\s�B�����c9�=����Vi����NAX��n����'`�4���5[���_b{���[�/��
��������A����1����`Z PBG
��k����� ?�n�E�%X��'��*}^�+o0J�FE]��
�<��
�]�3�.�^���}�����������><�o�-�n������F��>����:b���������8?u�M���A�[�zrC
=��GHz�����yc2!H��+=1��-A�u(��������v�MSbP���|�_7B����}��rJEg/�o6�JV��s`M���R6A�n�����-�06A����I��S�>��Z�-�J�������Y}��!��n�m(���z�D��S��{�yBgyx�����}�/\T�mG�].����:N���-����y��y�!:����%.C!W�E�Y{;��
l*������l�94vy�'1��
:��h:9�������Z�^���aG�(�W����)�)8�B�U��e��u�RA��e�u'�n#��7u��'u��vn���BQwoJ��@��g-�FQ����t��3Ra%)*���Is��7 ���0�9���^�i����pFO2��UHL{�����0���6�n�	�y}��7�9��7��q=k�%�<c�]��^�2g���e	�d��vp6cLk��zZy_Gg'��O#@��_�~����hIMs�w;�v����f�������q��WE{P��Lhv�YkE�������K��`I�Y��}��k�D'���(eT�*JUd��^�II����(��fR���V�������\@*T���mr���wQ\
��o����9��cF�l!�Lc�	����9W��"s%azB���jf�ha
����jP��I��'"����!�*+:|eU(B�������-\p�����.��c����9�q����6Ul:�5r�ik�BK:�SZL���B!��w�`f����������^�:G�\����^�	J��Q1�<tMxh�9"�WX��6E�������B�v����X"~�Q?�
�7x���c�P�r��I�b���c��19���t�����Y�`i\������������Fs<��V�2:���Bi�u)�.�V>�:w�3��ov�t�a�����S�L�+�.���~�g
/1v�I��)��>���V������Y�zz��v]�G�Y�|z��@��G��x����N_b����`�4��{���/��Qa2L��,�]������^n/}����2�qj���N���pt�������hr�~$�u�>
%�?�M�9��R���T�G��	dn����]6#+��b���uLz��2�E����ht�{l�%P�q3��t���H�b��H!U�C	���<��!(�2����sZ��7F)9���������A���a:�K;!Vw<l383)sB�H�����HV�-�,��N6\]F���L�{!���I�M�H�I��oo�m1�*�����g?%n%w��H2�z,���`3==xY������&��75:p�Rr]������x�������i�-&��?���X��gD���s���t��������V�4\�������y�}��7���o1l����b0�70��#�*4DyTNe0�:1�'�I�f��$��.TL.4+_���[��n��;���J����m�]N@h��94(?6@a����K��WQ�
Md������J�gbc���o�n9��nR�Fr�-�u8�@��{�=[��FH�����#>M��Yf�b�<���Bx KG��5���h��R|�7��.��]�$�q$������B�`3(XPi?�/+�0�.C��������n�.�4Y��G"���9v�+^#����w���ei��g�	����'�cZ�Y5��������Nk-��5�����R��2+�O%fxb!�y��B�\\���m!����J������;���hR����e�yq�3�M9�)�YB�LbU�;� (3�/~�dq-s*of�x
��L�	5����*% +���k�/3�Y���,�7x5�[h�Z�HP�J	��5���%I#e�rX���aK�9u�����(��������I3KG��I�zO�L��
��b�	"����;A����pp�Qp���������WF���o����@q?���?=��.��(�Q.������.��.���D��;���&��'v��#�b!�����$N��C�k��
���lk�]�����"���s��i�!y%�M6t�5�wad���V�)����������a�s�;��������7)N�sT/����|kh�����/c�;���Sz!���0��!M��$��T�<|S�4|�)�f�G����H?	.���`�\�1Ub�?���T�A+�J�_H��Z=�D}E;�����}&1������<���&�
��y����2�[k�)��2����O�d�����/v{g��	m��1�m>6�(9H��w4�������;���*�+m�o�k[2�q�+~����t�� ��o��m�����`,���f$7n$�1�Ss|/b��O������=H�k��=�B��v��{�7��dR��7?��F��Z����Zf���T����&������d�~mOf������������&%-}Z������i�q/�;�?���p��E0\}��9��n��W,x��W,x��W,8���Ng����������0���y�W,v�bW,v�bW,���b=���������wO���2��]q��]q��]q�0�u�}�{Z\�pz{:\v�A���������������J.�v��p��X^��S�"��b�+�b�+�b�+f�~";��=���"��b�+�b�+�b�+f�";��=���B���+.���+.���+.��";��=���B���+.���+.���+.+�,)��q$�
����B���+.���+.���+.��~*;��=���B���+.���+.���+.��0;��=���bF�b�+F�b�+F�b�+Fa�6;��=���bF�b�+F�b�+F�b�+F+-1��8����'q:��A���������������F�����Ngh�p:��A���������������F�����N�h�q:��Q����������(�5X,]��Cku�����d����0��kQH��K�n.��@v�46�p�oIl��8�28�28�q�eP�eP���G��v9E�r�+&)�8#����CiE��^�.����
`QP5�8����I������)�q^���w������wGv��}1��]G�������A�n�k�����D�_j�w���bk����8��/�Ok�oE!���~J�0SUSc[ �U3i�`&���R�\l�������Fr1����#|��H�_a�,Xo��=�8�63�x�i8
��L)W^����Tu�����?���?�������y��t�����!�v?W��N��D��o��w�o�xsyzT�����������m��%.�K������&�<������E��9�V_��k0q�e���I5���jv#{��@�y;2&)2X����x%�5\�����:�����g���S�v}+k�.:|�w9�����9t������h���sC+�W�7����\9D6�#��������C��R?;��
����]i��� ��
�A�i�='X'����x�/��jw�M��I��B�������7����x�M���@��y���V7"�,��2���$?_	|��hm�Z[�/�\��>G��QC���"(4#+j�
�
I�T��]w�����yxt%�A+q�f��n\��&
���3����/�#)���=���/��mw����sEt����EP����-l��A=m�&2A(��ir��z�}�}��7B����	B-��0��4�t�5�q�u��D��|��U�'>�n[����rQE�}*>�.�:~�D�D���N��BV�7��toQ�A���}��'���f6a�
��\ �>s�s������}�>�P���S:���$����m���
�zN�m�#gvd\(i6<���v!NA���m&�K���������\|@�K�� ���.
��M2R��^����x{~v��_�V`h�>v����^�gb��'��#5Z��W�]�/�8��8E�(��O@l���E���o������qoVhb�M�`sA���
��2+pT��p�Q��l��Wbs�*l���l���	"��������9�W�e|��R�EQ��%����Y�R#��]w�9��z�]Z|7�E_�pA����z8�����%I2	�`����G(5����?$M��>�B\\�����Nt!#C����\���+|��4T�^t��������Q6���P$�Qp���c(5og��u�[Lo���T
�.O��g��V
�!{�Q��;}T!�N��;d�N�w����aM)����4��>��zc6���|�9��N�w����L������(Nj�k��go�\T�1Id��La��E���/�B������C(C0�E���Y.*�?�h�z�q�9X\�0{����^}��	d�/.���#��9��n�p����q�i�A�����\#�+�y�������6���������qO��d< c�(�Qx�R��2��Tb?��F%�=�k��o��'C>����{��X�8��O1�HS|�B��
�6K.�)�
�~d�[�"�G	����!�,�q�2�7���j]��v��(9_GY�$,g
�m�+AMB/Un�E��vy�^��K��\]O���[��d�t#��(�E��QEd���d�X�C)%��)���
�(��pt+�A�������(}������3ih2�F����ch����
n��-��������=��(��#
��
	��*�]�#Dho���l�(6�tH����L�5�G��F��8_���������r;.�P�2Q���c{�8m`O~�a��u\	d[c��'Gi���	�����I��z�f��;w$��M+��s���v5Ky�p��4������c����k����$�N1a?�f�H/�z�Q��_������B�T)
��c����t��r����5z����un��������<���1��F�k�{n�E�{<���
����eq�/���.����k�S��P�r��{]�1jv3n�y~����sb����v��z��J[��"c�6;��fnS
h���Wv�i{�b8�G�Q���D��Bc��,UD���"�������@�*��k��o�8��g�����v���Oc8���[��3D�.���g(/�1��"9MzZ��h�rzA��L"��U���1�c��4�
���5�S�y~��8�~���rt	B�oP��y��NR�����6Z@A�,0
�$��W�
�#NW����6���H*��k)��s��xZBt��<��}�5��w�\H��j�dTK!��m�B����0}���k�&�Y�I������M�e���	�_� sk6��M�.}E��7��d�'���a�>�\U��/B/�`"/�d1�0 �FK�O�(`�5p��b���[��Y�}'��4�������b�y�`�+��v���H����]���{}���CoK)l{4���w��Bj��-�����)����*����*��J��U�r���R�/����!�_n�W��~z�i����7��'v��U<(�v�;�r��������S�v:�����Bio�S.���/����D�pH��",�:6s(~�S�
I��>}��������O�������7���z��i��u ��a�:�)���A��~1n������J���~n��%-����Rh���G�h���5@*�;��g������mx����h8���C�>tl{]�^�y��?�f�����M'�"��?��[���M� �� ��?�U����>���v_�{-�m�&�Rf+�����<��vv}��v:b{��U�y���io��~��
C��nv��|��Sh������i�m���'��_�%���A9�+��sO�#U����Qc8��p�~TX�\�v|�A��A;�.�������tQ�����~z�9���&��zK���=`]kk�E�����������5�W�~g���=��`e���qG��]��	�va"[���+��mF���>���u����q��O�+6��,v"��?oA��r@�<g�
*�=_�^s;"3��Q��ApR�~%�"S/R^���#4���Nh�}_��R8i��0��g?�?>�����
?�1`pNl
�W#�������*�P>��rB��fh
��O�
��j��A��G`N�����M�	�x�Q�A�<_	�a���.f�����U���;���W�g���GZ��
|U�V��v/o�#�	Q��o>gp�-~��>�����R�J�Z�qeH\��
���l�����?h�F��o@:y�����|�a�Yp�9���[�Ll}���h�`�q�N�A��0�Q��4�5�4��&��*:���v�}4CgFf{Y���&���8��Yc�V;����b��M)`���]'���>����Y��U�#�]���,��t���0��W��,]�-����Y� H`mMY5nH�i���I;jB��������h#+^���o�?�E��>���ul�!���R�XD�J���U�+S[42�V���b���}4PKD`F���P� ��F�{sZ����%��������?��"	H��@A\��~�)�;"���z����qTuR�C���f�Q&���<(���� ����?s�`k�p�����%������VJ@%���A������R�vvv�	P��)(R�6f���~�$��unK0
��^ct
D�1_^�o��b b�?�x�d��o��������o�{�����+������mhIn:
}H/+�Q�FRIo����oL4�D��)�T�!"�����N�6N�d`�#�M=9&�����[�u�DT�J�\� f��+�����8����'���f�94��\��!�>�x�!�
�lW���g]�����g���sE��@�U��?.��G8�
��l"�_�$���#
��'go�V�7���7���
;I��D�=2��B7�����IxG�^NF��<?T��5����O���*28���E�����IS���~�
���l�p���]��|�q����rV	��`���v��(l��� H�=$A�3a9�*8��9J�,E�^$�4�.������n��4ub/����U=`�y�����W��4��t��>=�������r�Nf���_��h~
��
^x����R[Y���f�a���������X�L����5��������L�S���{%+b��-J+��C�������.��7ae���Vq���J��^��<���
E��,/���Z7-K�n�z�����N�������h�m���N �[h�k��M�Oi&Cp9��-���������n,6����x'���(����Q��U��j�e3���������j�;��N�T�i�J�;)zS����{����K��$a��a%��T���{�`g0�������o�dd�p�����6�~�|�9�T��l2&�
��y-��v�+�r�?:W���]��#��~^����Y��E`�6�� [D7��n=',�Z�F�.��X@������F_l7	z�vk��[����{%XI5�l�0�x��!��}X��lX�@e
|��h�`�|L����������!��d��R�f���*51D��`�0�+i#�]k����GG���v�tF��d�m}�!l�!�	N�u�6���������a�X���d+�/��GdL���"���A9������&t�~���������nq�����&��	r�$U
@�Y�~$�D���m��$�
�7P�Uf�7m��w����f���6����U�!'�h
7eZ�-Ml�����j6���
d�-Y���;0<4�.�S&N36�(c��2$��zr��Aj|B�$��\B�<s2���ww��Ed���J�@2���T|2[�cT�m�a�C�U�*p���
��n���WZw���������,,�sw�V�]8pw��o���+�.�;��]<p���iw�xw�~�4���
������\4����t�}����a�x��~���P\�S����P�_���4}$v���tVCU�=����>�b����s`b��B�������~�`^D=4p����z��8����]�}���+/�7��W�
@��9���_C����2�qB����u|��L��
����J6���;v{��B��m	t�����j�E��7G��?T�������i��0s������_�9�#����h�����{{X�-�rP�Yy�@1Z�j.�\��F^%�&B�I|����xx��m�e�P�������]���������A�S�e�jmt���-����M���9}������>{�M-��������0����:��
u$.�f��p�L/7��I�5�c��:W��~���f�=����c�od�����{=� {-������^�j��|�9h;������lF��)F�ku���?�p���m�'��5��B:���{�����d���s`'S�\&�S�t�3
�F�(	������V��JB5 ��M�BF+���pU�t[�cbz$�����m�{+=�`3a��E��i��_;��:��ms�
�;c�=�7J
d��������W$>&���(�#��ZF�(/s��:�w����[���f������6����'V����K��>�D�z���������S�}���z-�-����h���R
I@����#����m���K��Huj�l�d�n�6����-���{����3�P�����D�5�Z��[n?�/8�,�I����IC��+�������Cm��?�1����V�����[��9���<+\&u��bzz&�+������MC.&~��?v��n������e��}��CV��jo��[����B�p���bc0�"1�9�q1B�%A�.�	�������|�g�<"�%�{���6��	S����R9z�_�-�������K�Ri�n�
�B�l���v���*�ww��������T�[f�Wa��PL9�/���<�G��R��R�h��n?�D}��)]f��.2��?���c�g�<��2���B���>�48���u��5���U��%���Fxt�^�
v�3���A���9H����������I�����������o ��
�����JKqm�3�\���k�5�5!qF��w�5�S*[6��n>_,tZ��=� A�Hl��3���Q.��|��3�)�J<Cu���@�sq.(&EUH�<'N0�3�/�G5����8+�?Rp�K�z�l8�;R;���=����aa�P�����~Cj��A�F>X��uQ
u�>V����daP/^K����
@����h{=����m��{#�[7��Fa�^Sr�f�W_�z�b�=�J��[�iS0�
�S'��`Yp_{W9=>���l������y��xwvr�\�w��k���k���U�
�C))�BT�k$;����`��:z�������?G'��UPl���i� �w��z����b��V�p�|;��VaX[)�Tj�6a����F{vY�6��E����
�c��1�������95��)}�H����?����^�����.��YF���b)W*�F� W�pD.F�0�@@�-��t�#�w���D�����.���N'��$�gD�	�����9>���[r����oi��px�O���F6jB�epdbE�5����k����^�9)��x���>"����U��rh�x��$#19CF��\���}
����2�t����|uY�-��a�b���0w����*=*�5�r�T�|�������
C�������s��f~������;�~P&\� T�-�?��L~��X&]Y�S�o�����q�h��Y]g��w�}L��{��4l�
������������#o|_t������hY>�������@h�x!��n	M��������Xt���~M�����y��5,�!��"@����vA��Ax��h�s{�<u�U����Q��?D�v)��.j��@V���[�:\3�6!� ���o����������=7�p�\���=\S6�5�x�m�>Rj�wP[`���y�`�s����}�=�_����-:�~�0d�����7�����)�� ������7���x�c�+������4�v��>��b:O����^Y���;���S���W�����	�����Oz��G���-���)k����|��6l��l
��C��i�@H��U�[Vn��M|�W�1l�4��,��mDI_L�b�0����������o��HYK_������V.���n�;����|�e5�Nk�.��1�J|	�%hx'�~x��{��_���3n#GJ
�r�t��z�^�����j���s�$��E�QU�99qy:w}��[��6LE�
�����Jl����TN��&�{�.u_��^�]��^S}�5������VS���E���x�m���P?:������u:������+���o;�����!����:h7�Ayg�Th�N�i�,�)���b��s�[.�w�K���Ki�����+��i����#��r}����i�TUP���6�f�[&�#���E;��|���\;}���Z�����r|���&��F�'0
'����5`/?�,�%�+�S����D��O��56��^o�-�M��N�I�, ��Tey{���R9|�0�$!����R���;�rg�����*��V3�,!�H�0!R1��~� V��|�:B������9�����i����������_�K�5���#����c�hW��o���q/��J��E����\�������U�u���6��'��kLl���{F�����_�����dJHk�I�l�g��I
�?�������}%2T2+8J0�,,;�_:��T�S�9)�[���]������������~�mM�=�J:���}eR�������C�!����J���*�Y#^���68�e�zO���N�v�=s�5�#�����&+J��N�Nc�Y�#�����nj��{����F������`��7HQ>_�8;vk��n�cCZkq�H+�J��.k��YR���F�7qh�`�9������^[�-a�>��kk�T�'��^�i
������vQ[\�7�q�c��#�_���`�m���na]�5
�	�}��^����?BX����Y
��8����*��nN��h���6���z�������g��j��~~yT��F�R���&� �&;�L��Tp��}G�_7$�B��/�Bma*9��n,T�N������lA!�����q��B���wvZ�f���f�q���(n$��c+%~R$:{{�_C	����)��(zr�N�9��3NQJ�k�v����b���h�e4�u�K!c��6�oMh�?�\�����VcD-��NRJv^<��4�a���={�Vy�9�edL��%���)$z4�;�^�e�K��^��u�-'!��������Bl�d�|P��Q�^99{�Q�rVq� w����PV���Z���3���������o�
 &��WbTq}�E1/a�)@��a���7���z�##b-)�e"���!�c����xP�����d��������e@�k��W.�9Ak<Z�k23W3�/$;��k���%Fl�5v� "uv�vv
��UN��x;q���!|(1,I��+��}�k�N�	�I����J"�h�H*G���`4�Xs:3�UM-f�)C���h������Pw@*������]*
@������;���<���9[� ��I�Io$��v:�.:�e�e���v��	d�l!
]�-'|)r��"s���Z�8��:������i��A�����Y�	��1�,��xk���gj�S�j�4�)k]
�8\�-�{��h���kku<$����P�PT����P����o)���^Y��/�������
9_g{s��xC�4so�o���*�b����e����G�����S(�v-g�s`[��Ak�i5���B������A�>X���=F��U�CO�+ww��������FK��������k���2��������Fo�������O{>��n�q,��������Kr��j�)���o����#�v��7���9��0'�������N�+�\�
����l�����a��^,���O4����P�����#��6)
�4?�t�������~�
@�����y�n���q���a����
:&�Qz�G�j�9,�Q��
�r0�
�9@�B_�
c��a�rX�r�_�jN�&���f@R>M8I��N���ne�v���:?�Ibu@b3n��@S����(�7X���YRs�$���GH�m�6AU�
�O��O�7�N��%H�"���4R�����$�
�	WP�94��(l��� ���T��A�/��T�x������e�F�s^�������1���VZ_4��x��w[^�Is&9%�T����8���b�L&$cC��x����������Zxn�[����Y���>�]X�[B-@6X{�I�E���[Cx���XK��ts�cA8�;7��0o09s)�b=Yi�j�~"�_��E��z�kB�90v$9-�`w�2}{�	h��E��
�qt�@n���$!��#2M���������8����4
����mB�����Js"@�H/��BCp<n'�c��#��":b�v�m������9� +�R(�C���u��F�n
k�����.J����=��g>�'�3r�id0_����CgH�L	���u������U����������'3[Qm ��-x��w�0�"�S�~� y*����b��=H��q(��q�8�� ����F��`J���U�d%��|��i���
��El�4�u��,+����!�#�����Zdp���-�m���L���\Uw
"|�W��4��!�U�/L�9,�0�T��FNK�FN%
�9������~����-���Q')h{BK	'P	�85 �D$�
��R��^7�.p;�P�����5n;��a���8�6���k�J���������0|��j|��T�����=�v����X�Vr���5����K��r�6�E�.���!q6�����:{U��f��.�Ye�{Ds8TN��9~M���z6��#��Q�C1��~
{4�|��4�-�06�V�gd\�P�h��
�����icC�P���V�������6ZH�pt oPb���)�;��d��k�'9�K3%��d��y�@���O	i�M��v0����L>6�98���
�2?P���
�Tu�o��YM�Z�����b.����K�E���3R �!i�t2�FZ�JA3t�S:r�-<���p3����t[9�^W��s+�6x�u�,+�<+���G������X�xN*������X�CM����&�c)c,<M�$�<f
E�r��?sJ�5PNI2����s��%�x^�B����q���Z�_�W�4�qq�c����
_P��_e6�b�n�����N^8F���^PD��I=6��"���b5�qm���+��L@:�MZN:R����P���Y'���U�:f���8
���������b������mak���;e���;��>���>]�5��$c���s�$f�Y�wK1�"D3��zw����o[����
��"%6��^�N�T�F*�#}Hl�_�BO�d/K>�C<M�e5����1�`g&l�L����+�����/s�e��E�@�oBm?*q����v7C����QrA����nl��_���[,'*H�;Y��<�5~FH��0�$+tU\d�By9z$4S��3���k���x���>1�;�M����R��B���]#�Q�3�2h��
��\5V���ot]��_=�����}�i�����y~�Q;�<-��tD.�#�n�P�f����������#��p�d�3	�����od��J&7Wk����]�&y4�1�X����vTm�x��Gg���j'�NU�4m[�b![|���l�FC��
�4"���������}F��:��yl�Rq���`-�0��
��3���3x {�q���$�����:}I#���'HL��#��v��`�z{Jh{7��*�f��L����3�b(/t��X�����0lH9��a��2����!
���k��!Q���c�����M@2��U��0�1�`7�Z�Y�U�@e>e��T�|����S����.��HL�������$�_
�e��Be?���`�BD8=���6�#K�T7�X��I{����?:���o��Ss��
��UW�P��%c�I�P�o����-�@������P��M��k�����%����e�O�)����hTB�L?���{�0���qOT�&�����eyg��'�J��A�����/)-a��-����X�hX�X�&���Lq�d�k�<q��)�F��C���Z��[F�CS�;�p,lv�	��b2���8��R"{)4�����$A�|�bVp;+�������h\e*���w�
��gAJh�D�>�T�k���Q��'�/u�����&{v~��e��D[�r�?.`U��S\-�H:��G��lY@Y����q�����S����PR��=G�K�kN*����I+���M��H�����2�}|/�
������Wv��][��s�c�����$&��7����S �������Q1���|��������	���goN�*���4������o	�R����������Q0t<!
���ml��Y9�Y1�,�!�8>#�4�+5x9J<%
��}��r~^	��GG/������j��G�����n�+�j�Y��>�Y��Oc+��^�+�?���7���T��k=�,a������U��}���P�6�C������qrqj���5���_�W��V��������
�VU=�u[Ux[U�,��4���h|U���W�DW�%�*<NXU�3��U�!Tc]�U]�:2�d���*~9���G|np�G!<�P������'s���<P�m�F]K:D)�����lR����MP���D/����?1������>RDy!��F���1MAI�<���"|�����#��c�l�o��3������L�,��
eL�;����h��]���}��Z��/3��.+�r���RkRF4
�:�����8������m�^����F�Z��e���������_
����(��$�J\����o*`%2�q��7��q��*����6���9�}�s?�P�|��@���`�����)�/�5�����t���%��CP�;���A�m��|3V��K	��&44��$(��n�dr��)]8(T����
L����e���w��w����Y�,��k�\�l���0-1�8��6��G��#�+���Zo�����q������ r�w�V�x	�g�V����,P�!�?_|��!�.x�3{rh���I�Y��D��0�~��q��N����+����`T���x���F�Q4�1 �a�����_�to�t��9�&��w0"�������6�6M�]��V	�����aa��1F���]�F-���[�N_����_�^J����������\�Y�	ABA�:�2_;G�z�+9��F7@7&��L��j��A�T��m��<Af�o�^�_���������
{�4��.�h������u)�/�{�t���1
������8�q���S*�t>�_/.F��nzgC	eQ==�0:�ET��4#�N���>�WP����*6���Tp#(vvr-6��U1N#X$�i?��;�H���	.T&������"p�<6�
B�j���}�{HA�nX�F���8p?�;�������c��m�aw��t%����-�!EE�U%V��L�a��J.6���p��Xh|`��gi[D|��n��4��:U8@���s"�	��^�s���t5Y��T�����i|��[�Y�R!W�7�'8��99�w�W�U��$����.�$��NH�"9��D�Q�0����ZK������<��c�3����P��>�s� �@�����mbRd�����R|4B��5���=F��(�G�U~�vI�0��{"�� �>��j�c0�DNN���b����K5� ��\�4��"M�|����q`Oa�~Z�{RT/���kT�Q=�`X�d��%G>M�����L2���-�:�n�
�/�(>��"N�$	�S�t�I���v�����E,B@���&�07g�e����#��w����A|$�(�3��|��� #��1���e��R>K�X�����&�cK��4���s���H���KE.sgX�P#�k:�y��=�wFq#�/C(��l�����SpILiUVg�H��>��y��P��'���C��%��@�L�cX�H�8�B�&�r��QH��!�����P;3f�&u�e�k]�����1�h7	�#��"��)��:
��+����iP�[��k���%�����AD�����E������X�\���Pg����H��8,��6�i���SwFXF*a����^�	��<v�mU+�cA2��T��"C�yB�a�M4�4���I��Qk�{��]m�����`L��������=���ae"+p��2�v
{�<���zN��{�������7��Gx[N�r��"�W�@�������WR��*��?|1/ �|��(���v��ru;���}�1�g��A5y�Zy4Fh�3��^}FH{��-3e��yT�L����G(�P�(���V	ipE�( J8Y����(�^t���W',T��&�����	3'����!������M�+CM�R0���
�e����`}�>���������0������z|�� ��s�����#WRW&����Vd�
T<��Ip���E#�,?Hv���
g|���a�*�e�����,�t��#�.\j�����h�K��LQ.�H�DO�U��*�e�r7���	u�N&�B'��_�b�L$�%�>���Mi,��.��_����\HyY������h�hY2�<�a��Z�#$��H��vQ������@��H^�
���z���1[p���I&�V�@�/�8�u��D�*S���+�*��[F�5m�w��U���0���(U���5e�[�v/����>W��F��G���zEU(��fz��a�Lxk�>�[�R���aCF�������E����f��������E�I�'hT�*2L��7�|)����Y�`���La_�S�	}EJ&��h��W?�
��=�����E���bq����\�����K�W�h����-v�c)����h���v��!	9M����X����-��	��%Sb��
(���P����sj���s�3_�-[��mj8���+��k;S�+y�yt��a)�y}/�u��IM2�Jr�l����qv���"�����v����	tX���)I-V)@]J6cwA�o�r|����+Uc�RS���
2�	���#��5�l�nOj�:	I4��W:M5��$o
-A����E'���o��2���F�����t��}FD�L	�rG����$$y
�Jy��!D�J�70��A����CIPC��C$���n�m\t��Z�')�J�D7�;��p�P�0b����#q����
���'�
�����ZX��50�X&t��P����6��A�bc��6�C��o�7*�q�"��k�������L�����<z��������H*���(id��u�8���R��M����P���w	��y��ku��3�����9!~�����>�����C��N ?��������}5��>�w��!H�VFD$E�EZ]��1w3P��gP���3/8���5��'t���7�7���UO�s2o�^b��|��z%vvrV����e���9����R�4���HQ�����
��-S���'�_�|b>��cG�����q�E"T9D���C5�Q������O1����.S��&L~���r`��1
r/����0�o��q��h*a�6l:�	�E�v������c��FNd�l��n���d�r[A$/*����7�	?f���<1P������>���TO��Jw��l������5�-��JV�=���1�.���w����B�}�@�Z&�Bcj)iz���	|��5�:5�M���n�|��R<������%$9�n�:�_���v��F6�La�n���*T��5�YA�G���p��@��I�#RnJJ��������[��C���'g@7���G������VV)��Q�n��${7y�c[��w&S<'��(��?�|�R��~�C�%�������d
S%�s�d�o*�eQC���6�D�[��/��4����M����P�Y@~�KX���K����HM��N�j_
�c���(�T�}�
J�@<���� ���s@��cCw����4��0��^l2Oma:����
}�x���8�����q���{2e���#u
���JS��q)�|�)g4uA�1���f_�8�5�tj{'�;k���cv�m�\�F��Z��E�5����x�TMg���@L����!.�=<�c��]��f�$���C��R?;�[w��������qQ��X���U�q���2������xj��0Y������lo���B�#��=���/���If�JT	D��W����E���&��=��#���C0�w7�������##>�FsHK�����op*��3�K[��s����7���-�#�K�b@���vB�'v�M���������SyQ!Z�;#��2���i����U.���w�S�Y�Od�������9GJ��z�$������a'���w4$����I�rQG���.q�\��+���Z�We1<��b���44����h�"���]6wJa��5!�����fC�
���
c���qs���?%�D��5g� ra+~�Aq�W���an�!2�Y�z"�e�F��c�����#m848n�R��v��z�����zk�a����4b��$��y7�7a�nnmJNu���x����TE �xv�QP������\=���{+M 0\j���=���J�Ib9�`
>�����;2_h���9�Ot���IQl�f��T9'$�iM�9�mt�Q�I:�M�o�LM]�G��LVT]6���Z8������3f4��Az%s
�o��
��H[��c��Iap�7���?�����'F9u���f�b�I�kGK�~���f_�I�13z�@A#�CPB(��Tp�R�A��)��Q�JmI�7��I�5n5�H��p�u���T��i�i��7�H��#�
���,�\�iN���f�m�����(���q���Kd���x�����G��Jp��*P�LUS���K(9����1�������z��m	:Kr��4,R������H�A�(FBz-�S�2/��C�b��r�2b�	^b�\����td45�G<�2����1��(/D]�(&��n���u������)��5z�i�SS!�l�H-
'���?��_k����.��:"z�6!@^�8��d_�v��z�!(��+�rR���Z�9��Bl���K>�ehn>7-������a�1�a�j&?Fj���4�V<���yc`�1%��������1jM#D�T@3���QRO�����W�iWG�e�1����$�N��}STl��02\8Us�����Z�g��KN���e�9�/'5�����x�:�)���v��d�6�^b��7HI2���{��&�0p�D�9-h�DH��C���1M#���8KcRD����$�%�%����-���
�u,3��K����`h���kP,u���9���)%X���hg������^EL
�
3�D��j ���c{�:1�}��l)���[4\�I�P9�A��~����Z�<?���Ut�����.SFw�;������%bl�������"n�y���t
j).����!S]��K:���	s7��:B���D�K��sm��q�8���fH+;�g�y��=)�9.��t�2m��/��;1���X���.N�F���� ���4^�����
kh|���(g��Y4M~�����	#C���h��3V��v��2oM�8K�e��x��	_�A�����GQ�����7��C���{D�5�"��^�`���7�9\���H,��IpP���%�in��^���H��18I����Q>���l�ja�������`F�)�,����:����"�o(�Q��(e�1�m����8(�5�r%@2������r���(���OWNuCn�
eg���<��y��0�D�iL0���Nx(����2t�`���Ml��%���TsC�g	,���Ai�l�5*[Xk��C�NR�k�w%��<�D����8J��������F=5G&��F6��c(�#3�f�6��G�o���`�T7�T��)����e��o
��B��L����bEX~����q�)A����k���v�QtR`b���L���oR�^Aq�{�P�{ff_��-�]�?�v=5�V�Su���`}�.�ZWZ�Q+��4E)�*��q,��X,)z�d��~�����))�J�����b�VVI�qII�+*M�����F !I������@�� �3���x���b�@����V�)������H(�WF����ZwX.������2|�/q�q�$���93�KV�@`�s���L�6��A���Gi��\&��iG���1i[e����	�>��!�PG7�O����1��cA"3��b'�J��0����OI�3U���w?��RN��D��8��Haln�e�a�nF��<,r��������H������#{Kr.Dq�N�}�E�Y�9��6g��������N'/�lg�d�"9
�Mo�vsj�]�r� ���E�+��_R����L��4�a��Da��)��m�|gM6�lF��V$$�k�)�����a�()��F2 �OU�rA�B��!�k�_�����F�����4O�X�$w7�����7�D����L3 [2g��9��s�p<jJ�li���6�w���_�<R�7�Nj�:`���a���M?������h*�q��Ll�ky���ti+�m���d�;�WU"�gc��<�wI����$�DY��XX�|JNZ�=�M����(���|/��:r=00-���/����!��6>������H2��	����1%t��:�5��gD0Q���3|���4�8���C���}t�����P��ug������u��h���k��Xp���\j�N������(eU��k��'i82~j�R=�K���}�'�����G�,zc�Jr,�|a�I�l��Ji=B�R���\=�� �Kh&�<����r���[gt���i��/��d�Rr��=0�[��4�>Uo������c��X/TEG�i�q(�&m{�5�}��[�{�A2D0
t'F�da��u`i`D�4B:��D�pi�?Z������~T��y72)!��n�����?dZ]�4nm���o�P.$�O�t��f�U��D���
��t�K�
,�N�6�������������L�+���_����&�cFo=�������	n&�e���+!n�+��[hIPf���Lx���}�"/�?8���M=K$�}����$������g$����" t��P��N����LBI�������O
����B���/y4����JQ����8T@�����'X�-~b(�������Aj{so��MY2�-"d�$�M�i����bj@s��$�#�(%�J�,��1�`_J&��CN�f���@� #�z���� ����`Gf�j���:gc��9����R��@���H<���f'P���p�����f����?�������B=����IR�Ma
��)q$b]*D��Q6��"��_56vPr�~��D(o^jC4�q9�\��`�i5U���3�h�d���A�H.�{3�5�	�2������f���A��Ka��4�%�M����j����YO�G����/�����#���
Q���!y��G3mt^�~������7�bm�"$Jk|�nG���z����\AT�5���3�M�����i�A��������:�d��-[n�\�y
W
�H��U��b&�6IN5� �>�)D�9���6/���^l���2�C����`����]����y},��
�)bN��&)X���[<�U�`��b2V����Tw��`@N*����Q����3���o��Me��b7��jS�d����y�?�B�I�b@��FPFMu���k!����F�FK�2�
�$����a���z)������o�����-���Kzjm}Ye�l6�UJ�,]=���������t�J����nF��n[D��C)�M��u>a���q��~�"�lGyU}Z�d�������l����(��Kx����i�B�"����"�D�x����BKb,��M��JS���X[�L��i�A���Z!;^�v�5�.���M�o7���7�tN����iH��]�L�j�
�uDS k�C��L��q���v�����>�
��%O��qj�I��C���?���!k���F���3L���<W5�6s=���
����1�����'#�R������
:��#s���`u�g�����MD����Zx����$�g���;�)�b�$-�[8@�(���VT~d��)`����u�<�^��D��g�ja���t����t��9�d�����r�L��R�!�4o������
���8f�0X�RM����S�3e�7a!����0��r��,Qb{T�Jp��Zj�������$^�]��"[��U�z�c���:�d5U�J��������,Ph�U���e
�Jf�9)ia��2�����~�G?�������a��BD��[f#�
~���������hs�E���c����>5f���+��E���x����L�
0x�\HO��(z�7�h6���������,��tei�����J�B��`�`�d��3x�)]`�U.��O@du9������f~�9Q�9����8a�<r{<\z���T�x��b8�>���nDu
��Bi��-�����%M��!�
������\V���\���j�I��(��������/h���������}yb�4��{u�x�Dyyv��<�B)wp0qyf�E��1�e�3�Dc��6a�zJ�M}�\�x�3�=��|����I|��M��u3���a�����U6���`�y<�\F����"�I�H�Q��D3����r��`���w�.oj��� r�"�6�������fp$�??O���^�������.^�o�� ������t )��jFg'��(�b_#EC���`���=����7���>��<��Y���`[�UDhu`Dv``;��MQ��^#��I�z�e@����	UA�#������*�l�f?)n�����H��r���\6��9*V��rY����Xy���li������7���WW�:�!/��f������b9��&��JVF���Z�Ij���O��P�u���y��0	�����k�`�_@�����s��J��?�����x�C�h$��:f&���Q��V��B�Y}'�R<��C�(�8 v��g�uL�*�9�K�x�? L�^a�1���
"2����Ho5aI��I��Q��3�����F��r����"�����7�(o�/�x.�'Q��qc�{������V�_&��6<����� ��D�4�����-�!�4��16A���m�R�����������6��V
k���^X=�#���6P]��Td�n)�����{����4[�'�����6.{�������/�f[:���������.,����An������B��KS�GD���*M �
�	���b��������_V�	�qk�����Xx^(J�@����m�L�X�="o��m��m�YT��o����V:$����Y�-�����)���W*Z��X���Bi���P���S(�Ec�������j��O/7��w��E�V�>p�;����g���{-{��n�J������~���.�Y�=���PX{�P8��DVs�9u���g�w���]�rD�������z�������� 5�7������v�P,k��P>��b�pP(�_����P��C�~�NJ�������������Sl�6��]#�`A���t�����R���hF 1r�t�s���8 *������c"n���|�#�7%��@�3x�z�B�������J���M9fk�?������>�K����!�ooo��k=o� �������Tat��]�!�bk���Vw�v�
^C��&� ��}���_�Q�$��3[YX�=��]_�@���+����S��Zd��o_�e�z�Z;�|���=h�[�}a����������,=�=`w���A����,4�f_F��/�9��}����c*)g�q�C���zz�[����}��0��@g�o�_�������1Rq�k�i�	oX����2����pv�}f������x4CysRyKc}�1i�((�"$��%�Uwz/*��*�����8fN���EfvZ��]:pZ���^a���m���1sbSfN,�kW��Kl�_��izR8�qO��hb�$��z����p	�a����
#!�����.j�|�	���8~���/z���[d�/d������	����5e��:�����U��^��d�o��W��Ekow%�=��{���W,w��� ����n�n9;��~���v��i��o#qj_����>}������/�i�������b�B����uX���4�oO�}Z
��(���~4�<����R�R)�8P�WC�j�b&�a���e*�e�A���!��I�S�d�,ONE\�O�a8�B:�����~����u<�ew�����]�i�)J9}��+���{�M���C8�E��Fcc�N��`;��T�$����k{���eG���yP�\g#��NpZU[O[���9v8���F�#u��W��R����������>����c��c�Nz�>6"�Hx����C�/����:�(�sn��(���b[>�
��A4?�"��X,M\G#���Mt�~zI�����5���i���tn��|�K�+�:P%��B�n����m��(�C�RT�/�d�R ��S����6���V�l�;���~>��{�B�.�N��ZJ�J�|�o�h��!���x2����������o��5�g:�~�0����$��cp���F�$s!��m����X�}���+���!�t�i��DB���=P���&�P�qc{2��IC����0tO�^�!P�Gn��{���1+N�|JPT�~�I5�;0��L�|z�vW���	�[�LG���!��ujk�*���Q7�!�|�(=�B��W�j���Q{���9��;-���2���1��F�
nSj���Z��'��
!�#�����u`x�+����d,�f������~��~m�<g�P-7I����+��,'�Q���>>�#O���1�49�:h�gw���hyF�g����[�
D/���o�	(�Y���YF�g��zh�9��e�0�$��4�hH���\iY�U�? ����E$
[��������y�t�S�3`k9L�
��D����#����G�H�����d?�U��a�j@�L�W���@�qZ;��~�CF2�Fo�@z+g�Jg	�Yvnp<� =p��C��<t���Gg����j�~y^m����>�����:����=�*�!���Ce�
�0>3�f�g��b��>�1i�F@��a����.���O�����+S�)_���DT*��+��'�k�h�5N\���C�f��>��y�A���s�Z���n�nk0�e��Qck�!��d�)�������Ox�����
�kJo��������^��K����~8o��vz,C&����j��q�U��9���>$E���e[m�D(�$�VJ����z���PN��g?����/�Q��7���P�_����2'�a�1�@�7d����t�Q*��F�w0`#��N^�RJ����*���<�y{��H}�@���*���r�{�����=��0uJ�5�9��O!G@1��q�EC��??A���U�$%�?�#p���w�1=5�`VNx�_�o�/ {g<��&m����C5�2]#�3 �6<�^|��cL�[�������Y�8'�-�E����zAg�U�-���^{��S�"	���{U7|o:_ ������������H�b+*�!�U���G�=K�P�����)�[���M��c������y�sz����J,�eY������-�P,�,)��/:���$�����~+P��9>�o��8	I_�g�����$��*�������5��b����ss���]�^G0rw0)����H�N\1Q���r�R��Z��Y��������9��b�+o�������w���a������WS������&�z�4�)�M�
G��KY�52����mk^HlE!q��vz���J����1���@2a���+WW��f�d'Z�,��\h�)0���:�3<AA�vz�o�E�;�
�����E��#�o�rs��M��e��e�WB���7�$W��o��@�K�d���b�k���!q%'�26�D��7��(�������"9�����4���A���U�A��!6B#����l�� 1mg-&�U�����h����0�@��L1L?�����yPO�pE+�(D�Q;��2g��
n�)���nI��5�/�/Yq��On*�&Q��^�q����6XDs�OC�W�m�6*�k�n�����+�[����)�j����7��\L�`YHc�����t��Q�Fc����X������hPP��E���F�Y��/B�B)L�B��+TB��',�zI&��{zt�sM�+D
�+(�3���W�J��w����5�r�b��/w���g���+�!m`�p�.���2���s27v�g(1�/!�4rw_r��~��H�7�qV���#z �O�X�$�]���m��oT���{W�1�p�(������^#�xdw�K1ww�������A$��Q(pX"�z��q�%2�$2���L�=���
���
���3'T�����#j��9'�E��_1	����v�����%"�g_J �j���vsw��Rd�����������J:�ub^DW)����Ll�9�����������B<p�sbt]����h�zHRG�`T��d�x=@'D9!��&,�� �{�0��y�8�*�����%�*������5))�3L�!j�=j���S��Z*���=�C�x_8��'����� (Y����08c�����v�4+���
�&�Dm�zrQ5�
� �����m�#���"�	
��
4I�/����6�$�4h35F������P�'a	P	�6��������d��(��Bg�	7���&�Q�G6��w�Y����# ��`��Fhh"����#`c�f&����ln��:����������,Dv,�^��sohb:���}i����;���N��� ��l����i���A$�fw�(�D��d� �O�v_���4�`z_�Xa�������	�������H�l����}|�nC��{;t��s�����F��<dLW�����p�	���b-pVb����'�t���'s���G�`2����1[/S������r�@��1����y�9/��'�qhx�$GH��y����4'���VN(l�#�`tm��
$�>���}�~
5����x��8O��Ws��wv�%'��mt���R�5��f��W�����]KG��)c����9m��`8}��r2�6�\�QL������	�-��������$8`L�7���C���Z2�o��C��myL�T����
u�J����T�Dj����Y���x#�����N1g�;��r��kG��y�m�]��3qRo���xZ��W���!��;�%�n+����I�����n;�]�����RHGvM�)M&����O�����X��
�A�����QC���kt������ |_��Pf.]3�������_���dX���l�����`�7$�OH-_��^0u����nT��sA}�%���c��SBea�y���^}�vO
��Q����������c�4�����k�#�F�����I���G�$�����wt��O������)3�������Yt	���RC�����k�<>�����k�F7�1�-��� f���\q����@G�D�����z��CT+E�9��N���a.�x�t��@����w�xW��������U ���Y����^��	�%���dgS��HG�L�M6��	_�C<K'�|(6B�����Z��gM�x�
 !�H5������A�0t��&��y�I^v@7��z��+��������]y��p������f���c���u������r���2�E�a"���uU�#��=VUZ�s!��Z�{aeE�<=ui5�^��qcKX1n�\�o����r�;(��������D��6e��/�P<B3�����<��H������<��Y]��)�4�l�5J����=�L��lJ3��z}r
�m��e���I���`���I��JE}[���b��x���V-��f�A�`V'������w�]6����Cl�~��n1W*/c���Tw���N�;��!%m6GyEF69%�~��=���Wn�wL�o��c�������\���P����)�;������~�7��;��u�������O��KH�!>�gqg�i���+|"ZKz���z�A��o�Bk��[n8��n�nUcSw��Pf{�W�����6�Y
������Q�Q���I��VoU�Z�m�6[�����>P�9���>�
������i��n�U�P��������mO�	g�#�ke��HO�L���q�{�M:!������|k����W��
�%g3���T��."Z����(((��D"��K�E$.
��q��`��1���"EAo�Xs�`���"BQPa��V�#�V����d:E~#-f2��"T���hJewjAe"IS�������TN�q�**e����=�!j����;F({�����MF���R�;u��T���P

5Q���*�>����q�����D�$�����D�i���/mP���i���%���yh����V��/nt\�w���i��>�1����I�0�32��h����o��0=1#���]�O�M3Jd0b�G������*��;�����Q"��IE$��g��q���)���s����[p�'��0�#<	km�+}cu
}���}�HZ�L�������`�w�
���	�`���=�����6f�&d��wae1�F�^���tN/���?\�����u�j0�=�W�K���_�F��I�ppc�=��]'�|c���M��������1KP����
��ad��*��j�*i��d#�[�jl7��=1W�d@��4�&�(+��5�`\����i=���#���k9q���l7AC�oR�!���y�g��kc��OE"������fR;e��������m,Y�y(�yj�m,P+��o���X&�-UW�cAuP��!4Hs#���OH.AK�����f��j�q�8���M��	]x���1����m��X�����P��S�S��.�r���Q�^�?ds�[���*�e]���@v�1�s�9�<�8���n��@����7��u��`��0�of��
B�Y�X��I�",�&���FK6G��W���|�	2��2��y,����k������U
��l���=��se��B��������`�mm�����2����!o��J�����Y�E���M�����*�(!����}���l#�����
��s�A"#��T�s�r�����
@�c�L���_�����F����v:��;b�C��Wa�_n33��
��&iQB��K:
I0�o%�m��lK���8�?YC$F��H��V���3�d	����dX�/' �N���@��V*��	s���w�RVt� 4�C�;�Aw��7U�d��N,��8���;i��s���K�KC�R�"/s��Y��v�@\U�u���t������S�"��u���O�"�`*��)P�����:�3�YT��e�e�*V�u%v�c5f��[�!~~���qit ��wz���2�Y����X��$yPN����R@zP�v�r�Yr��|���)����n���������1�K!j�WL������#������}��W�����|#<��D�#N�|_U����Z]	aO��Dg8���k�Rh�s���-�36�iK��{#�
���k7=lL����xA��hnGrR��W�k�_��Ae����������H��k����`<�6�e�g;�����b��eE��-���?����)�v�;�����N{�Y�i��^�T(9V�T�[a������t�K� �[@��;���{_����0Z
BK�Z�zE�l��3C=���U��._���K$��!D�Li���<�&S	7La�A�V��i�-��9Z��x������y�z
���u�iC�z;�������M���W�&y����y��^J^�>���-�������[h���������	������k��TIG�����*���?���r��?uG/\t��*(�t5z���Y�0Re]��������_>'y���z�
��F����-�����]���{����������*@��d]��
���L��:�6y���M��s��x���t���9}�������_�����������7~a0�H8[&p��T���Q�Hi!<��tX��-����=+g�?�h�A5�J������/#O�,������k��#����������h��{������j*F
z\��a����/�J��������,�2!�y)�n�(4�|L����[|�]�@��'L�b���];U�qA7
hF�O�%�A=���X]�%��.�!'����@�^S���J�h�o%-�~F���|J����r �6�f���M�xk$�w��N:yF��e@/���rK�������w1�J�w�B��]B����+��F=�uM]K�����BP]<�H�(�����!{��om*h�8�H���H����� ��N%�sVP�v�U�/�;�[����n�t��o�7����Wb��xX�7�}��+����IX�#t������w��{�{�~6�W+�*�7��Z���?�c�������!�iA���*s�����%[�����}��^9�������@~��������PP�������������T��_m ����k���#��&~������6������(�E��jZt%L�������.�#d���Kq�_U����~Pc����&�/��v+��J�����#
��
3P���/�D��
�-�|��=
z��H�M1U�g`�����5�X�,	���hH]�CI3�{�e����d�<ph���w�5@d�E��y�4��K��.jv[XT�hP6�R%c������sD|�!�X����3]k���������^���#��F�O1��
!���,=��CM)I^�-o����fIL�B��DW���wdnD��@��U��Z
���!��.��.8�>7�j	����������o*%�nv��TM���+���;�X������=G�O�4.��P�G�9�j��t�X����/w�#��*OW���3�h�d�G���R���6�N_"��J��+����tP��S�f�$V=G�/4�	�xi�������;��SC�og����y�4�|�M�A�n�Tr-�7��tjiL{��� �@�6Y�@��mz��W.!z�~��#�/�� L�m��+�7	�&u���t�i)+k	��P�g�HF�p��V�_��}9�W��h���@�1��8������`�(a�]�e��4p�4l�	'�7����>����M�g�X�U3�Q�t�x(R�H	��FO����{^��Zdr�c����?m�������&�����d��Y��h�?���5H���\�����]�J\:1����&�S�m�}���%�_�����c��\\��d�\3��F����5y=^���,a��lY�K0��s�s������DX����N���>-f"�B�%���[o�N����m����Q�[�����ba�aA�GK�3�,j*@F-������[r.vbD0�^~��gK���pv([��x	�4��.
s7����2��w�%Z�P�K8�����k��\�GY���TxK\���PF~.
���fX�G����fkEf+���-.i�?�|����4����)������)�na���N���@��q0�EH�BL���F��K��S�
;��`}hB���=�%��\\�����nc��S�K�e����r��#��8���0\rz��#��1�]\�.n6�I.��[4��x���@aY��d!��L��N[���o���d�&����t.�a4���T�������j@UE%��s��0p}�P�=����bg��,l]���<���p�4���9���������zU�������7�,���%^��M��r�f�����o�r�����k����l����v��hO�qi�mu��H�R�($Y���Q�4�"�����BPH=J�H_�QLqi��}�q!�����aN����(��e4Bs�vG��t���
9��L@�D��c�/�3`]|��8��4'~�����|�`N�����4��7o.����#��&t�"�Q�������@�D��el�Em��F�*��e��C<�a��Dg��;:���Q?�<=B2"(����s;cz9���@���H��q�
�O��3��(���%`��C������i��w$����
�)���%6w����_N��Gi�w#��������zcy
�$���p�f�#�0t��d]��������z��l�3��]�+?�T^�a�!	=������[�<KY���:P�*:�Y�N�x����4��%�q�X*w��9��.Dq��(@8�y	�����\���q�0>>P~A����" ��d7��n�Ue[����'S�����Iv��r)mI?�R������>^���0H��Y��Hy
]cGnW��<��y��y������A��
O#/<���3K������E,�,G���'{��=J�PJ{a�q7��"�&{tN	�Hp��\C�6;����ry/�o�����Rago�O���S�:����QhY�I��'��������s��'�E6I�!o�����9���#}���3pi���]@[�MS������\!1�S@
�R������)���J��gD �������0�q������fO�T�q��E
�*�.��c�C>=�8j����Q,�X���g��V�;����y�T'����vh��}w�Y� ��%�$��	�}��1�	J��1��=$4S�a��2 #zr�0&�C���}��j�	���;Y������v������r��o�w�IQXFI�"x+/4�����s@��Ha���k����|qv~\=G��@;�6��6�H��]�����U� ���}�5�(��%}n��
adC��^��bI�0H/	((����Hc(�����I1O(r)<J�
��~c�FNv�oOHP��CYR��^��zmc>HJ,��|Z$C(�$�	�m?��m���zu��p[	� ���h�M��@*86�������,(Cd��L-�uY!�6�6i���p%�h�E�����a� wI	8"TM��P2��n#f��u��{��t��C�9�F��$��#@�A�G>�69Uu"4��L�t������ZG_6o`c�9����x�[A�EpI���r�d��	�m������wF������r���"�@�6�{�����������8�7����O2�������e&x�e	��p�c��j��$��F�(��DG��z�R�u?;q}k���C
dx2h+��>�BW�"a�BHu���5B��u�C��~.Zca����>y���|*� e���L@����������U;mY�Qw��s	C��1�l�f�I�l'��g����c��!�/�����
�m�
E���I'�2u�alQ. ���N3�0@%0�4���'��p���[�+#��L� �Y����&�K��c�%�� )\M�s��pK������/��w������x��9�TR�g��Q�P�1D�mh��"���)>���Fx����N���Q�z�`N8���	1Yc�����K�9G���-L��%,���C�L�b����������y��������T]�+`3:(-��P�R�HDFSV�I�A)���S0����#��v��kv�
���NBC�2}�R���	J���QFR0��w/���(5����l���fa�C�_O�)�D�2�������
���(90uV1!�J�Lt��sPK%?�1���^�
-M�2���E���	�K�DT��1���F���A]=��mG�sF������G����{��	U�tNf���bE������:����&������*tv���l1}e{:$0%P�aXd2>UdI�����H��!������c�$ z7a[��?��L�����hfzNG"@���>��1�b3������,<�#�=�D�lx�t���l���B���y�T����irvF�CR�?�U��qg��v����=�����D���C�M;R-�\
����As���A�jw����9�����1.KVxN�`,^0���&,�������B��� z>v(�O���RK���R���K��R)�S��E�P8�_;��\L���{+�.�xDJ-�������QA����g�@�@��,KN�A�~m��aTfc��O�y��@z?Y����\�����Mz�.���]�:<\5{������i����}��� M�34b�c�,���0p)�8(�`�2�B��6��(����# \��lq�FwU	lV�*SIK����eWvGJ����?;&��b����f���G�����`����wi�W���cQ��x3g��,1��|�%�=]e���qc?�"��P��\��.K�+�N8s�RY�(�	�����1����B�r����[�z�\!!��'����}�w\�U�S2�Q�jw/_���R�B	�ni7_,�(I�����dI"uiS�����p:�`�X|�O����3�����z2B�!��I;*�U�QO�s�P�B���G�I\;v��I��q��]:gE��6�� �E��:�Yj+@���:���D�1)����s��	�^����Hkff,����X�\�]�Sf�H|��}�*���+����K�����N=i`�]����>>gNq���e�*�m^THJ-��[JIJz�
-�	_��!����/��Q���J#	�#��N_��i^pjw�}�L���H����i{G�P@/�4A�
O���!����1��!M����:�@cVR�����W��p��U��,`SJ7QP�R�����6'���;�I�,%	�Q��Z��K5&����N�YH7X\�[�<srD�qze��guJL����8�`�T<�b�6�A8��������L�t���Q��� O�0�y���-��p��m�a<4�=�G$��B�u�v��Ig�[%9�L$o��P�����nY����?n��|�o�w
�����U�$�+mc�lD����M5�)r��lHlHVg�2Mh�duK�{=�9Cn��K��xD�A���M�����o���#�a!cd�sT��Fm��O[8}��3���x�L�N���b��=�F�|TL!8��M&�0G�P�1�5����`�c��5~�@�#������R"/
 ��PA$)���K��5iE/���9��?��o����*�bY��������W�m>&���
K��q�N~��"��44����dE"��WuJ�����{�e/��^�,���;n����W_����p����!�l�J�4����j��d�������P0G+��)���1kr�2�)Q�%h�&}

�����x�+I��c��0@M�tJ���KA���BJ�l����Lc��*4��t��.^ i�N(�Y�IO9�zQI�6ZxY��6��91k4���	�E�u�{T��t'75H���1b;����6�PR|���p�HH���ow�<�q�\��:�cE���	��:�����&3~_L��v`���=���J���W�+��>6��C��7�vz�=IB�?�?h)`K��#���hqf���x;�?�P���I�v 
(SoLR�����#���9�1���E�K4������|	��#^��HF�s�+��H�+�>C�:rxt%�*%���HG���,���&E�d5�h�MHE�V��gt=<?�C%l���H�U�*= � �A:��|P*#o�����)v��4�N���1���L��+UN��1������]�|>�@cbF�E[V�cryE��hgY�0d������8�1�'	p�4��dVHP�B�7�����z{<p�9t�U#d�{�q�{"P|������!���9s&%���Nf��|h:kesr`����]IhKg���&��2�Y���m����(q�s��e���C����A<�
{�K__�]�H+���a�����<���!���NY�������=1�J#s�<=��tJ�����?�|������f���<���>�����������G���{��3�t=Q����'U�sC<��>�QwD^�Mr����k!�����q�������D��8S;�M?�{�1�}�=��_��������3��,	���T=�xM+':��y<�>�%29U4ZN�j�9��8���]���md�&e���9Kw��~�~�s��c&���MZ]��\�L}�T�>sC���O�fD�^W��<��O���iLh��B"�'�	�}���l�����I/���1�SC�&�2T�X�B$(0�54���w��~6���`i���'�B�����#3�$Wt;���g�|
�wG.GG�*jn�O�|��7���x� �e�[��5upQ����90N�@�0uF�� ��o%��T-�`�����Q�I'���7����������>�������R;6Pjj�[u�#u;|�N\��=������L��G��:���-&yk���("8a�9�`��J�&���T�3����^5:3��~A���~W��d8�Y�Y �v��t���a���c]n�8��Q�nK�j��D+*]����+���^Bxxmt��G�z��0���SxD��6\|rx���C.^0M^�}<z���E
�>���j����h��|����_[����8'����n�
�N���1r9�q�kE�L����W+����=(���q���-H5G�I;�����Y,����<�Qmal|�����j�!�VH78�(���!jg'r,��,����k��u���)��uv������l��>�[�
2]R!d�k#�X�4�U@Q����<����D����������)���������-���~��+F@Q�	���`�_Rp{VR
-�nW\��j�m2�+��m�$����A]j��
���0,LA��rb��������H��%�qC\`'�W���9Z
n�z
UPq�]��A������������O,�.<~����d^J��j�`�\��F�)�N%����x�S�g��������������DCa2��Lx[�"�	���^�.i��q�*��������+V��W�����������T{{[���^�Z_����{��}|�b
:�o�����v���&�_�@����6|��y*>O�$b�\�Oko�`~�7k�d��b���
�]�*B�b����QX����1�c�����j��O/7��w��)���r���_tvK{��e��bk�X(�����,_���[X�PX{�P8��D�p�9����}��f/���_�g|?��?
_��:�fr�*������|?��J�U8(�/�M��?��P�����V�_�Rh���cR������a2�CA)��$_"���~H�!��?��*�������R�E6�'���
|�k�:�1`>�>�=���k����
"��V���__gg@�1��_��e$�>�"������uub{�������x=+������?s c4�"��2�����[_z�oP$�
y�����P"�������bL�A�U0���AZ��2,���������^]Ys��
Z
����-�#,pf�u~+�����C�k{�.b���`�0���j�.��
F$��0����Y:�[�}q85�#$B���� �'S0:X�P�,f�<Q,�O�,qA�����^���X�9Z!C��(Tp���>>dB���������?|��uL�h�����w�	���%���Ekw���&%a8��a};�8�n���t���!,���K�{��Nb#�
Z�v��qu����>�����sFc�P��J|����mK�l�-�K+-�RBa(��{�����������X��a����n�vf+�����{;���=m��i%T�m�`�](���|~�*���c�3oOk�3pO+����K�}��rn~����n�F�M;����[k�����o�������������/x���������"�A^��q��s�?������=�~U�cG�Lv�O7@ ��7XS�������n��u�)�9%y�3��������=p���M����2g��s�g�0
�#�� `�.�L���Y��l1yH_��gh��J��d~�8v��5�JbG��^�����h����J���s��r9WN~M���z��w� K
�'����m���R����fq)��gh�yH�����
�r��7yjh��9V��|�l04B���4�C��z��N@y�m�?��'�xAK�T�U�~uZ�:�����X�\`��x6�h�����`6>�����_30x������a�5�#������6y�`��+��`.t&?Nk'���R7��j��H����*
�D2��|[.N`m��@����r��n����lx�9_@pth����b$h
��v���a�~�5l����SA"�����S������������v��h���q<�5Gg����j�~y^m����>���������F����H����~�)�2���wl���f	���k<7������`��!7?���X
KEP��[��n+�m�{F	@kZP�i@�Y��,��)�3�
���t��^	P�������ordk�f0p

����u���9V;�������|���^�h��P<�1���H��PRCB7l6.0i�]�����e"f#���U�}��Q�k=,�Va�;4��BCn��k,����G?�y�4�.b0h��$�OUq��k��d�<�������W�~CI��N�����
Y��i�>?�k��>��@�����c7�0pT��MSA�2c�����T�1	�q{��$�{o?w`-���A��4lC�3$�T��X���jA����,KA?q�I��R��PPQO$�B���nD��D�E���_S��p�Zz��x���g��9~*�� �E���4�`�r
�KhZ�Y���yh"4L��k��
�i�V���]��9��Z��5Jc1��(;t��o�`6#N���$����F���e�2�yN��k������%c�n,�m@x���(�C��~J�H/6AdG���H D��F��T!��Z�^V����g@��qMH2F�����Q���Yqu���K�[��]C����O���V?��������c��\S d�$x�~6|�_��o�-J
}�R^�)���t	�S�fd#$��
5v$�n������>�:���5W�U-'�~
s�lV��i)B�r�2XJiX���:���`�l*��T�����h������oJ+��E�I!���
<V@vHo��e�CA�F$�HN��Y�^2V��qvY��70|F���v�d#BVH.S������*�q(��=`�S���f-A:���X�Ry~�3�7|"�x����7E�Rm��M�
�$�IJ������uR��;l(��f1�5bF;i�K�����&��R�|n4_.�'��J�`2G����@a��D��1����lQ���f�J�KJ������e>��%�%��	{����-�,��Cls���*�`���A&G�FJ�������G&�[S�X������5T���8�5!@�J��-zJ[\?>w�n�_[�����c.<W=Q�Md�,����h6�E��j0�������|TP���g�S��_e�����D���<K0�4�$ �U���$s���1,L�������
~et�FK��N��#(����5��L��7��^�����'��V	B*Z��.�0���Sf;9-_H0h0�n@tC�C��AP��1%2j���.�;v����O�iL�����^pL�$�nd�$����x��f8�\R��uy���>��K��V+�w���v�i[��
M8�
���#������X��}��i���q��^iTO�O+������m��N�����.��]��zU�N����R=�o���*��86	o�v���_2�1��Bq�>�����qS+������G�q��zToT�����|X;M||z&$3�LX���J"��8qd`X��J�����\��&�1�A�Ad����$��� ���|��[������i�Gl
dOnk��U�#]NHW�PKZ�%�t0�(��3	�^�
�>^��J���7�*f�B`(�U�z6�sh�O�'*L������*K���J�WC>�I��YX�q����M�po{�=h� �"��j�$�a/k����%%p��m�-�k�<D��MN��@�'m[���+3 �u�|��oB5��JFn�|S5�)X}1��'cf[FS�-�����t�A�s	����	�[���a��!AD��%L�
EB�D@������c��������=�����5
d!T(�"��w�f���O�P�d����ct�xSov�,��6G��g"o<tN��G�m�>N�kx���/�h��Q���������]>1|j�����`'�l�(7�����xw��fX���'��x�1r!�V������	���Hu/�
K��F(����0��!e��
��B����3�a�MB��I�\" ��
�Fk������xu#�d�S_Q�+��2M%B�u�� #>lv���>��cC�����D�
3�91\������~j��9�����b�`�=�-	�Z\'�9���w��L��^��/8��:9�w"V�ydc��"�$,pJ�%�@�H��j��
I��aB	����Y�L:4�m���<�T��Q���!��n
��������M��(�@�h���Y��N�<e�����#�k@FEs��IkN���xD�]�=�a��7�����s�=k��9�?l�g��\|����m2�C����[�����QC�]��a���4��4d�C9FW<_���1'3��J"���.��G�a0�'�H�j��ShEu"��d�P!�'��0a#p���������"��a���x���Q�UA"R?7������I�e�s��*�hD	!:���q+mc�5k���L-Q��Q�&��|x�xsyzT���6*�o/�)��<$���?��g�'IO�m����D���rIx����i�|��p���n��l�����~G��?���j9�����$��&��a���L��2���[�\�����������'��G���sW�J
�=�I!�'=����c�YT:��q:�R��������l>�)s���8�v�:	���6��!.�G����o���E��vT�7�>T�+�y���9l��|y{y���)>#���J�H�S�Osc�R����_���->�����Y���RXV����(���X����~��C*>3����D��:F|���Pyj'	1R�r���B8@
e�
J���Q	G�g(X�J�S��]��e�T��Y�����",��rQ�; �����,��i����s��w�U��9�H�>I\w����N�X���1��O�� `����\Rt�\G?/���M��3�F�[��U��#'9�h���R��l���
<Z2�k�^�Z{���YU�i�F�9JN�R��e��x���O����
����a�PJ7�(�5��1�@5
���)�#�KnD(R�SH���T'�iAP��pF21}��E�����B�5��R�l��.�w���4-#�>�655S�
+e9V��#�8�r��D�K�	:V�y.�����H(��d
�5����g�S��_�����#>�p�����D�R�o��B�[���W���m�e�mViZ��O����m�H}g�����������a�|�������!�$��O���NX���!�o\TO�Gu1�?�l�?^l ��79������LuC��f�+'3�������W'���k~u���&��e-Y���cQ9=�~Z�[�@z�^e7�^�����O���x�S�.9�rX@�jJ������rr"6��Kn	�gP��f�	V,����3������&}�������������������m�M\�p#��vpM���<a�E�
c�� ��e��`Gj6��*��$�P����pA��t�������r��^=	os$��zd@���af��'C%�'�����=�u<S>��w��;�:��m���5�W'sa�g�r��N�'�t�f����&e��k����!�|�l����4�k��E�e�P�6;*#*�Nr$�H!.���X�� ���B
��/�
|��[���F��!
E<eB��l4&��W.2��"JA�P&���bG������/x�C��T��)*u���:�l�$�~��J�^`�Z
��gE�	��D7 x�]"���gY��?��Y���jg�Y�����Z���V��������N~/H7�-y2�f����0k�Xd"���_���^�d�PWVP��?'pI��u�� �S�1�����;����A�?��]3-�V(��J1��x��^s�	���J$�b�>w��h;�$bXn�B%�M=r�:vJ���<���W�!0m�Av)Gv����(�{���_@��K;
'����^�-�tF_���|�
,jM����Gh��L5j,�@�5��|Q�i9|P�iN�2��A���{)���r|��d�'�=^(����j�
�]v�-���|�7���)����#� 7�HvK�x�����_�������r"��CxF������d0���cw� S�C8�sm�3��������������C �;XFv���qzeD���f����x� +B���Z�x5��q�i1�&��c!0(K���ecI�E4"L
������ "�
��J�5
�n_y��#+#�F9�b,Pl-<������������B@�nQy�2�@���*�JR�sJ�������{��������5#tw0��*,�4uFwS^Z�p�yY��J��B�F�R+3�2��:&�Xs2����K�7{A���h�a����E��CV0	`������+I5�%]4�M0*���@�#�9yU@��R���X����H�An%E�wJ�8������#�G-��NC������KFLN�
��*�����C��DM��cR�or|gw$��������z��H�@HFH���"Y[�P�5����TF��p@(S� T�0�L�Y6F���l�:�%����]���@B����4�2��q��|����v�_c���V��Ru���em������M;o'��F
s0��ZM
K��U�%�E��3���!N�'+iR�V���s+/B
$�i�Fbe5�H�y���Z,�f��	�U��� �p�����������Er�T��rY2���<����lN���;����$���pgP��B
�]��G�h*��>c@M��}��TX���)���pSX{%���K"e�\m��EzMF*������5:��u��YD5��������k��
�TG�4�d1�J��r�9H��8��h��1���#��������\z��!�k9L���[$�Rw�o��FD�S�R�^s����_��S�C?�x1n�����`M����F�g� 5�Jl��M�u:a�e'-�eqt�K���<�M<�'�9��~����*G���8�b��M�%���������<(�!�7���Ao����X
)�����WY����wa�>E�y���AP3(��8&R��	>������^����.�D����N�c-��S_@^���������OF��C�o&Q��i��,��X4,��qG(}2���kt{h�T���H(k��#�j���C
������%ozKo���z���V�c�N����/���JF���1��r�����G<1!�������"m{Nkp�'��8k�:�gh>K�����u�}M�$�sZ �X�7������B��G����nk�����q�����qqq�4�T�a��l����C�6�o�P�P��@"K��EJj$H��Gp�8i~�^�%�E�����g�:i@�9�=�B����F�h�X,�oBv��A���+�TN�9�8�e,aX1�K��Y>h��[��+��M�����!�H���'2������V��r�LP��W��n���_�wnt(�0�2� 7J(����
�����CH��d+�_2!gr]��0D��@���<��vUH�W�wc
#Q��)n��q	I+b0	{	s�3.��];�c,FG�@/E`"W��*�_�>KJ�Z����cH,��Y9�&n��J<���#f��
0Q��T��v#j[��F��
�9����|�H���j�_��0$����{�d�I0���4�����l��K�b����������k�MX������e�<'8L����������V���(�l��D�(&4Q�'4>���#����������Cf�W��uO�%��{7��d�*�������{��b��P�!�4
���Z#f�!4�`�������!�^�x-`��i�\IOR����a�TH?�a�����>�������P��M%�)7Q�E��"�7lR"_5��V�Fi��ZH�d�=}����/�4�&�F�����B�Qt��|�8�)hG !U�p�Zu�����U]��/����~a���q�n>�����J��q������E�<�3f-9B�b�,�[�����\[�D�Q<�r����4O�1��7�T���[��(��p�L�
��({��Y���I��s�/N��,'�<N��hw���i��tF�i��mK�����`�E:�N#��C�}�� ��xE�vV�(�
%"�z������,��1r`>2�0���
�p@-6�F��@%��(�@M{�I�&8hkrt�:0�15\�1E��p�Gd{j>?��$����\��\S�2�3���u�b�Fi�E���0]�=C��/k�0�)`�o@�:�Q�����#C|TX������;FD�2�f�+5��c���
2#�{\�^9[�6�N_6����Z��c�|�6�Yy���Y������OjG�z���
�6���k�����+������y�,�.
�a�� �P,*Z�W���#ur���1���%���n�_�a,�p����3��h$��C�aw3F�'�I���d2�)9�9_���$7|V����g�k��h�:�3����O�}��'�����8C$�C����p�@C`\��������\����S�l��z���v�x�3U��,�G������sU~D6f��m %�}�SX�F���\U2�<y-��8��H6�9��'�����bS��?�����I�*����rH������_����7����g'��
zU��fR�oa15��i�M6Bu#��u>�����(t\S�M6�(RJ_(84�7���P�
th�;���e�-e�.�G>c��H"�ds��'�GhW��N�e���j[8�����S�����pqx���4��l�{��x��Q��))��@�|���u�
�V4�1����s��b���qFR�d�R��!�a7�k�~�y?�(����g�?�1C)I��M>��~�9v�������	�fPdc����O|E5#�`��[h�zK�q'�1&
�����}���3On
������1�@0$#�=��4��"���M@�0�bPXG%4(7"2�F�L"�������n-�W"xI�GWM�h�<<&:7V�{�������+f�y-4�.��F
�Tw�w~J+��B�5a���p�Z������po��?P�w-i���_�����(�����c���i@�a�sW���3T.�r�y��:"��R�g�\�	.����N	h��>0�47}N��'�x@��gG���P]�E3	|+�{�<:��`�wA�o�������D�r��>$��'�-	�)����]On�l�I�E���0!�M��JUlL2�q<���O|%,B�E((_�
��_H�E�#�Q:���U:V*y��g���eN�f4��@�O|��'n��6�v��Z���������p2�����"�9yT(����&ut�����Qa�x:�+�U���W,.P������)��
H���i�
r��s�=�+����Hr���MJ��d���lNp��}�R�����a�w����i�8�S��c����y=SR3Iss,IH.�6J�$�U2E�R��������X,�)��2,R2�D�-��V����2~���4�t��������&����V����	���e���nX]���|/R�`�#Cl�:4�0�g��~��ND���X����Q*�S�2�Z�-��E�+�����mO�Z�H�~���6�	���f~��v�9H��Z<�{8a���u�PZ�g
����������T�����M��N$-�S/�H��#��!e������z��D���
�:�W�.N�#�"�[m-5;�Y�E����K>2�Z���'<�ocHbQ:2,=
�^��$���F��iU�E�p/bg�%�<j��?*��PZ�S�����xn�ZIb�@�E
@H~o�!���H/U��,�>�0n�`�D���T3�����FPa�����l�T8U�
����
��C�Ar��z���HW��O�#������k�qH�S�%�J��o�:����?W�&:���F���`t���.&U��Vy�����t0�P1�X4;U
K����IOev���D�L(o�,��G������WeQ�+7&�R������
|6U�_R:�Op%�����$��x���������<R�(f�)����^>��T�|���O0��0��B��?B�2*-�He��'>	Qw �I�e�I��p�0�Px]��(>s�c.�}�S�A���
��sBT''L��[��rz��x��na@;�(zevs�E8r��k��~!�h|�%������R������������z�@6����K�#x���� /�
����rt������(/L��������P
���7����6�\�}�x�y!\�R�����>�����-�U-����t�e)��I>N`Dl��

C���|Q`��l��R17�B�Hl<�3���j2��o\}����x�zP�}�.A�J�h�Cd�����7����!k��-������g��<l�
�)T~�C3a�����&e�~w��)t�F(���P���\�����0*1&y#E|���G�(���!��<������&����"�������7&���l��x?S��8��}���{s���O��1� 1�]���:����4
�d��p��<��i,��-��/*�+�0$0�����|�<����E���������{�_�S�*Old!�Zv�	���Y(���'���`�8���:���[��������+Y���kuUu����~�n`7�I�=�����@}YA���A���Q��S F�s��5A���_�A�E��$q[����C/dN��b*��U��MQo�y��X@Xl��~�Q��duw]�y�rNu�CY6�[�����4���)�	*Z����B�y�R���mP}���n�d.��������a�
z:�}e
�����txjy{z��.�]8M��
OyUic��?��4���6t�n�h�S��o~�up�h�U��S��*�t���<%���j���p_�g����k^���1����k���Hm�	�5�����0�4\��|F��U��{pi����h�9Y��e��lUz����X�B����9�s�D�D~�T�I*�6�J?q?Ie�q
�a���0-{���Xno\?7��f�����h�2t��������0q���PT���'g���r�P���
���x����n�rT�����������M���5����N�\>�����u^~�=��m�C��f�J.}�����rtPv?���������*{GG���Je������N����n��@�����*���[����f��W9��+��=�+w/���������������������aY��C���r���g������<S?����2�����i7N�R/.����u���g�=�swhoO!���<}�_~Vy������zkN6����.0 �*�T��A~U��T�,��,��}}�.s���9�k�U��TA�}�I�L�%������Jr�D'�e��q���e�j��EK��X;���$�0��?36E4�/Y����UM�)z�A��o�$ �A����������F������<�^C�����Q|�H)5�q��lK���g����lD"�S����N�c�'��?}�-#m���A;�x�P$���/%���q��m�O��_�V��0����H��Yt�od�%|�k7r��w�-/
���ag2K���N�iD'y�$�C�0�0yC3���G�<�Y%��
�� ��:�Uy��	�]�(������	�&�a���c�H5T��	�����eM���}��=�x�K]�Y��ys�*[VO�6^I[��/�7@�q��w"�w��������*�6����VxZOm7�����7i��D�W����U��E�e�0�Ty?l6��1��:{6]F��{�O4�K�zl��d�����~�����!��-����G����R�_��:P[~n��Q�vxl�	����O+j����#���M_
�[���o��_�+��]�-����zhQ�oU�����5t,����������sS�e_�;���������r���GGc\A���SjP�j_�R����Lv���+���U����my��brg����}��[������tn�&}�r�b��RO$��CSy�FM
[�E�wl������:I(��W����,��������q�_�<�U*��R����?�\���
q��Wy�:����I��^h������e���3��x~S���"��^E�T=M��n����@���pY��2�Y�JF�k��I&CL"���
<�O���^�'��.�Ps�f��%u���pp�A���Z�G�������IA�&�	���F����h*E����xr
�T�$=����i�SV��
7��������������S2,b+�t�`/	�v���bW��G�����0��������������,qB�b0�1�7�G�d��v%���j���T���M}3�H?T�0�t<��n:��:&
�Z��,���u�5�]�;1t�\.�!���rp�;��U���^�r�tp,�����{0��v�nqw��nq�����mi���\��mDxg�����t�jN�����8��y��k�;gI~g��d��d�����nB8b9��a����&�J/!>�z�nh%�z9����_L�Lr�q7������OUOf��!pk;b��n��j��]��}����t�S��L�'�M#�Z�b���/���m[�XK)��
���V�2�����T=r��$7W�������������p���C?,���q�jJ�sN���������T����G���qw"��4�{�:���QL���
)��+�eqLr�����.`��6*���l��dz���Eu%�&����*�������z>������`��i|�S���,����DI,Ht?�����j��x��C<x�sk$�W���
�v1�������b����1Ji+D�U��n�<B�0���wP����q�AG��#�zx>�D����"��uz�.^�����;������rWB��2�~4�d,�:��	C�?��B��n���
N���	=x�d��T.j8+=n��s������8�ebo��2rNo3�$
�bj��1�����~������>4�{����J�p�<��I�I��Z���E��h�np#����e�����k��x�9�x���+y�����8Q\����V������/L
���x����O�����Z�����dF^�����w'�����
�O�&�m�
�4S��&D�������f.x���.�����5�\����y��I���yU���5k��F��zwq�h�k�����o������F���UF�F:���(t�)�H7�w\c�
�
���P��v��Y�C�n���BeC,���+�#-�W�������)�+��8���
���g-�!�O~N��dS������d���;�
��$��E�l�&��C��*�"� U�u�wV�����k�h�.��Zi��P��������	�K!3"�����hk�����*EK�1wU�R��I;�����>��+�x���M�2@��t(�O	��7g����A�z�:�����X������~@��uQ2��,����,zW���ISb�����J���G�?�Dc/����-%�3�x6�8<���[���<	���g���#z�t�dG7�G��Cx��1�E�5u#,�A�D�����XB:���8I��n-��<7n:��k�U:�&s�r�6y�i2�sH� ��|�B���i�.i�_v^��S.�S�������� HR`x�X���������>rq��%���PW��mv�\B
(S�"AaN1���3hE�A|���z�Nr��2��+KHr8O�p��[�����i�f����5�3��dRA�O�?}��[xEx���
��r.���c�r'����d��t�����*�����O�����R��A�h����3����-�����[�M_����N��`��[p����+/(v��k����[��
_H�����&y����\�
=`k�����
]�4�H�
.�6{��5����i���M����3�������>�wi�A&�7�C#9�J�������GD��y�E2����D�"�W��m-=�����H�����W������5p	���
r���������K.��,�u�1
-�[���i�_��5;��.j���z�&&y��j���W����i��T
2T�
#�����5?��j3�����j_�2{)$����e��fA���=�����
�A���>���+?�B�����<(�����Aj�����.���PW|����N���Q�
�
`,��	����s��$�GN��Y���H���-.xX>,�������A�U
+h�)oB��V�/b���c�Z�'����~fvX*���\����#�m�"���&�Pi��H�����x�4�.p#���;�3gp�����u�����V��OV��|Z�ex����%e(��e;'<=R��?����DgYUc��������O����dJG��	g�����:�'��h8�8���J�"wK��Oj���dg8N:���
�,�Dq���A������lY�jN�0i������-�O�y-����y�GS��*��b���������-��2k������J�ar��%mkV�d���L��x�((4�"�b\-��&}��G9T�9���kBd"�y	E5��C��+��?���36(����,]�*�3-]�-��F�
9�:7!�����8�ct���{�fi(Z'�KS}�d���Q���0�7��x���46��gA�'g�����US�3�F{j�p�(4pG��pJ����v
�k���|����6�������M8x�=�q��c�q���k��{��F\����-�N	?����#i"i5�l��(������O�[��[,��M��x{�E�	�2��m�N�&~j\X
������;H�F����N�G��%$����U-1VQa9���R��C���g�����\��������k|�
�.'(������\��_]	�u������5�s�
-y��k����������+��|������zS}_?-�i_�7V����7ES)�o�^�j��~���V4��lw�����h�����O_s@Fp����(��"O`��Z|��?����w�^�j�����n�/�������8�Z��a8������u����I7Qd�3t�$�� �����f���Xf��j6���]P���w/k���P�h��P�/��������;�s�Z���N����E�
�oL����{��K
����W�kt3AbM�#�����4��V%_}����X��i��6kL

��^�"2u#*�n���!N&*|�TH=
�]s���J��>��}�7��_;��mU�^�=������\>���B_��!����c�]G�����1TI-��1,NS4���5,��������������_s�%��U�f�zV�����������tE��z�����]�7<l>9t%��S����x���h��Zx$�V��$�b����u�|o���
iv����f�r'�3f�N��������8aW�����M��~��~'&�'G���x��m��p������s�G���U2��w=���Fp�
����j�:5��,b9��Q�w��7p�p����	^���tn����Kt�����(�������X����}�V�x�9*(���cC�ej�1gtp���Fn:��_�}�Z��gn�����QfM����Bw1���s�BqRKK��Z����y'�H��d�J��|����hTP��#HH��jL�o;NV�X)3?�F�Q�bB���`z��������G����z���R�O�=�t���K��Z���o�
�����.|�oj�F��
����c���	��HG�Ih��q:zXtc�4��:�?N+�Fq�q���0#�U���N�x_�DZ������g�#�ssU�����}�Z�>��r�[������_���������4
��>�R~C���������d�LO?9&��C��C��a��������N��A��>�����k��&}��s�[Z�q�j���!�&S��I��:C��r��Q���Y���9��������������r'���N39�i�43��:	K,�f�N����>a�?�w�s�H]�}���G�D�J+���	�rL������k�	){m������0��i�]=���C���0��� Y������$��5c�x�k�Vm��M��������\/�.�Sr��99��Y��]3K2w�}���f��p�c�HI���+������B��!MK9*��^U���P��#�!8y "������$������"�L�1�t6�E�>,k9�'>��f�AuB.z��)�H��	
m���G�7�hy������F��Q��=`���&�	�V�#��SE^��@T���^�8*�rMm�=Z�9���[V#��r������7�D����mLc��L�"�A�W�|A,(������X�D�j#����"����L�W��a^)�9�������R�2����j���3 ��1����	�.HbN��n��j��������T_A*J:����V�Hh�k��l"�����^�0��xnch�6��O��c?��l�J�t�,�ep6�X`�8K}��Qd���2>���*
�/��I��,� ��M��T���)zct�0����=�����}�`���������o�\y~�.��}�@��mi����!�y��.��F�
8����
b�c�������E�U�lLW�����7��
�D�����F�~Rs�;D�M�
�L7����:��8��o��|#Vp�cV�v��u������.f;�)��8�r��Z�CF����b$�}����Pf��#���x�:F�E��P�����GNfg����2��*S&���=(1{�k�I<$X��j�gV�D���y�I��g�-P�D\����?0�����MbF�i|$RC��<U0-�	rb*�}AB� ���R>!�asz�h���is|�lt�qkC�s��;O)1��!5B������
�W����#��N��G'B����V��Jl�ZK
�uuhC���%�m��u�Z�/��:�U6�@��ew���pw�&a���2*�!�c:����8O�N�0������XA��7��"���"��]��~j���=I)r>q}ku5C#
��Nsjy���Re�!~�W��A��x7�*K���#���x��&:
3�%���F�N�tF
[
�����`��j�p���B�\BWk����^���e1Kn�1AC<j�,��G�����������A��&�!t�!���Nm�����h��(cM�e�������U����5��&��;	�=�9�R��h���;|�$`F`��05�?r?�*�"�3>�N�TjQ��p���t|�'��rb������IE���1�N�e���\p��N�lo*��V����&xj�t�p��6P�z._��HHKbqp�;0�4���c���4�����.�=?��k�����>fa�-�hMflm@-O������������)&��>�R-g��X�z-	�Na�S���S�nm����l�����=����4��p[|��U����tR����4�]���#��A�����9��c!�����/2=�4�2w��%����f�v��!d���h�#��4$,����H��$�����r r7�E2��E��+������^o�T:>�����+@��U-�"w�p�!A-f�X�K�5�z1�6�d�����_��g�������"!��aq�a\�Y@D<B��;�:�K.&�7����'����`�!$�����N#�4�>�F`������xW�>����l������`�a.A�}h;�{Fs��,����!�m@����
P%��DwH�������)����pPi�+����
Y�"�k(!��Q+�D[��(��rR����U�=��I��a�}�gW��6W��<).��NM[�+eZ��E��Y]�,X*(��xx|�`:���V�>]5����i����*�5���s����BA���8�83t��#von��_���:�����N�����ze���/����kF�Q����qP���XP7���2�{$������Fe�9�L9�z����q�`�W*E�����~e����g+Zp��L�a}P�Eh���~^jT E(�F8�N�fQ�
�(������
��r��(z����(�w2�?��(��=�����H �H@���HA[��i%4x��9�42�y�4r�udc^t�����,
��J4��X��q��h���T\�+���
b���/���
�{$�%����s�����B���8C�iN�������D2�D�����q�����#�����~�`O�,|9$����j��4	�����N��,��T�����mc��������A��������U[�:��E0:�t�kR������]D8G9���L�aY9��h�����H:�ug�������5�����l�`�4X�C�R�������t��g^ X�{6Jk�����OHP��8:�H`���a�w�D����T$�V�D�����<=��<��;�m��jT���_+�>Q=���.Ek*��*8�>������.�)���d[#���
�5��P1Fw�vA��&� K�V��I�F�9.F=��s�u��c��9�
NE����P.-o�6T6i6��$+���\8�����M5.���(�������gxc���0uO��`�dl��5���TR0[�O(4�0A!��F�K�L�����/���[�n[�#v�]��Y"D��M����~@�v�E��X5��a�5�N��pbt6�		�����Z����C�Ga����(	�Q�<�hm��P�����.1�Ve��Q��W���4����#ay����bs���>,tp���t:HP ��
c��Z�/A�����{��Q,�S:�k��5.\����lp9<��"v�����y`���Iw��(
�u�D�pw�N��l�`�M=(�w8.=�hz����k2?`�a��)E�1C��D;�a�*�������������N��;	6��y�#E�:4�t�g����t�	
+X�i���!�^K ��P%� ��K�ES�N06�J�4-�����z9��{N(�tAa��P�Ug2�8rv�#Q�n����hd`�<�F}��6���9�M�9K�k�G��E���!
�L����#������Y�\]4��a��Q` ��f�
�-sxNe�����9�@i��)�2�NQe�V��!�y���1_�aN_y�pd�d/���`;`�������Cz�Zvr���Q���`!gDS�*M�q�D����C��@�2��S�z\&�����Q���l��\�����I
,�&�����vw��3��LFw2i:��Oc���H�������P�>���V���'�Dl�����e+�,H�(t(+��>����)Y�������[�o����W�k���f������������g������~�[���7�j9K{����G�<=;	#����U4F�8�D��PpB�r>`�Of��NmO���O��&d�x��| ����
����b�Hb��|���;��Cz�B@�Fr?cGJ�P��h��O&���.��t�uw�m�1Rm���x2'Y��;_(K�����uy�-c�����a9�1��lU/�X6�>^[��u����MP�@u���y=o�k��b
/�$��5�L�U���c�xm�n4�^�@����&���6�"�����D*���>w��u]�b��o����"������r��5TR�d:�����v4D��3q�;&1q,���n�0@�,���dG<%[uu��W�����x3�wi
�����w#�n��Ay��X�E��?���>M0:0�!���]r�/:�'w���S�A8������G��1�SO��Um���7�;���~�5�Gf�x��a.&�j��y��D��^���v��I[��m����>�{0j4/��qQ=����#�@=���H����`����ROh.��W��,��g11GU�]�9��]��z�p-�l�28fM�00h��L��lC��h�^5k�7�2i���"�S yO���:��_s�������)���k����4X������Zm��k���Bl�p�,�$&.2=D}�_9 �~��x���d�b{�
���e��z+fi]�s?�H���y�A�m�dX7�.�P5QI��C��8���E��C��������{�qpK����SN�h/{gG��0f\��62V��P��u�K�@)��<-xQ�J����1�sw���d��<U�0	i�a_���H�aP����J���v����M�d���'����y��'
s�C��x��J���c4T���YL�5�����H�����jc�1\
z4�S�������a%u1�ao`�g|�N�����+�C��*	9C��C.#��`H�u%*��.��/���!��#����Z:;�Ao�\G����lb6��#U����4��aB��u�W�=Ooh�mH��06TN�R�����D�|`h`q���w��m�b+.
��/c�]*��N:���l�:�ab��	��o�6��Y���&��H�<��r@�2}�����2�["�?y���" �w�<L��K�n3gb������a�Z���������v�z�������86�Ns�ng��%�@e���={����z���
�Cff����C�=O~xS���1Z���S��v�����G���s@�a���t���V�=���-%F�-q��
�����,^���M�k~��A�n.t�]x��C��7^^�����2'#8�����Q��Q��
\�,Xu�Y��o��1���k
��fff5�p:�{�\�Yh/$�x���zf�rX<�}���74���#r9���Y��sE���YF����)��!�.��%s-/��He��~4i�,�����KtG��q@K�+�1i2q ��'�g���
;�o�5X���E��w���vYd�Jl��K�c�i����7�:�Z<^�A%��8���L�|��H�f�YY>	��&�<N�����S���aV��	��"��p��'�D/��F�X�����p�wLO`�`\����v
�v��`�_�i�W�k��22����N!2�w	��2���a�;����TK���.���{K����x]�hh�qX�K�1@���gs��mHO��������@�E�s����R:��L�B���_���������z[��i�L�����l4q�L�����9NJr��Y��XjS����vO���6*�p>i�e �� ��v>�
�Z�WwR����o6.:��d��]T���F�-��7�;#��j����~���5T�d�z�K�<;w91��I�!������Y�h��jn6�*)lW��W�K����T_V�M�1=���iC�@�����C�v��T�vd���*X�|-��`\�Cw<3r|�%&�L�@�n��}XXel;>�&�o������}!��n1�	�;��:�W��`�yo��5���#�|!�Mo���s�8���/�`�w���:���w��;/O�L�vr�j�GS'�1z�P@����&���n���E�4���i:8*>}�Y�u��<����C3�%�L��4�K����
9R )�LR��C����Ai�B����A�(��&s����S��kB:����.hB��
��{��Q��Q%��5�N��xV�4!
�#���{����0���|�?����2Bf��&.-4�^N�*1��i`.��Pk^��/v��b���K������I����5o����
��cr�7))�uG�Kz1� ��	�
�w��S��r��LW�
X7o4^���\��D�tbm��Rp����0�x�R9(V���vU�1�����no���n0�x�\S�3�)���h5���S�dz�(`�������H���L2��r�6}������2��_<���Q�n�W����G������9�+�/P��B��F�_+�j�{�lE����i��y(��={��}�Z�}|�^����l����Z���jM�LA�X�U�pk���li��jE���VS`"���q�?�;���\d�K�4nK�*a��\�����7U�"�#�;D�H@k&�a�����H����-B"�13�L4��L�>3H���[��u��������-9*��'a�	��\��M�T��������`�#{i����W���&�
�Xi���o���������U��:�_�3�CUf,{yo�#�����J����C�K'���^�y}l�������o��8���D�p��������'��j���]���	H�1�J26�r�Qm���.��b`m�E/������������Wm�
B�l��y�O�]����"�;d���#p6F"��n�0Q�im,"'��o���i�1�
�eO"�\�1�w����}	P������L��$���a[6k�*Et��������Pt�JO������2�Z'�~���g@���m�� �+�8�x+���IPOb�P��(�Zn�$�B�����a!]iqk����m��^���EO4�����h����EG��/�{i.J%
�f�d��2���T9���N�G�#c�p�8��pT�W�g8w�#_bLc��y��r>��0���4�3Z'l��FZ���MV}����qc�/��j�8�Y����3v/u���R��4�Yc`
L�n�e����`��T�n�C�� �<r�{*��wj��.�GtBY2���e�U�I���3��Eo�#���W�	���*�_^���D�
����k�JR�d<���dH����?R������gOa�������{>���wep.����[�&�aq;I���t�0�,|��x���H����d���|=������\?]YP�28]��GdI]U����]Xz��Q�E\����+i?��6V�<�!^��`^�>������2v5�5%`r\a_�\U���5���� ��K<���aw-�P��]Nn��7��i���[9��[Ky��o��;ge�x�m�����h���c�2N���~�d�mtO�N��h���Ndcr���-:�Qm;>xlk���#���=O����;�$���`'�#����Q;���j?^4;d����N�b��i�(��5K����|����a�W�~��~����Z��I�^�[��`]�@t�bJ�Wt�*�8����
H��z2�4�XF���H#��
���w��x���l���e�q���d���8���Ae/-R/�
7��r?j�3��~�rh}�z�����''rb����ub���rA���r+���2g�NKy�:<�dH�d2h��p�p����P���8���"�=B�A�1�A�[�z��T�}�W��|	����(��	9V�m���h��;�fE�F8CQ�fN�^e����i}��f�X��iodnP���wSU��_�C����~��z7r�Q�v���u�	��Y;����S?o��mI�"z(��j�s���Lud�T[�:����w�������:��[����������mXCG�������G��������w
$.T_�4���lE,�Qy�~�D��m�/���R�u��L��7 oP�(@�q8�>*sE��_��������.�,vz�T������.�$���������M��?G��2�����)�a��������+X�Gj+ugt�Z����f���Z&m��ft*�1�+X�5f}��Qs]�ECU1k���Sf�������SE"���hIJ��:ru(�f7t���F�[#w|�	�/���� ��������1��h��E�k;�#�z�9���]hr�U��_�sg�b������:��=5���t^w����������I ���)@��������8�m���8D��a��RU`��Nq������	�TiS�ED�_,+	�!���LPnz5&b�l��������.�^�����e�C����L���!����q����.<r� �����D�|+m��2�V�_�U;y���2��pJ6.jM�)��}�����sxrFq#R��9����Rw�C�G\d��8���"k3IjF��5�x�/�P��B+<��'J�����U��*���
���� ~��k����������������'"���I��
�F7���m9
r�?W��L�������.������%x������O��5��d<'��UO�oVi�A�rZ��>9z)�g�3�����Em����K������`W�].�A?������������K�l�ZO.���y�����Z�L��P7%�?7��y@*�������A&#����`��!'�b�����3��KgT���%���a=��ow��G�E"�����`!��H$+��]��8�G5�C���-�b�l��������`��FZy������R�Z���Z:M����F\:F<�j:�#A�u�*�8wrf�zi���kZ��O{�t��}�-j�Yn���R������)�b��>�3�����v�'�,�.dhP�@b����0�9�n�s!�\�-E0U����1;�Ej�����������j��M����/����*��N�M��C!]w
�==zf��"X����T%$������Ca��t����?~y�S�\����f��B���*�������d�����=����
 �~$�C�~T:���l�� -�<Co��������E+�a��TT3�81$����e6�:�j
4��@�>d})���f�fP��)-Q�9b�'�����{-,���h���Z��ea/:2A���$���Ba�r�TSJTCP)�&OGk�e�g�Jh!�/uC#�l-q?���9�v�	��Z�Z�*'H�\{)
�[�`X89=�3��M���^�����Xf���8��g�m�k,�C�X����#�����0��n2��;��O���09�V� �I��j���c��|�'�������^ ��~i���]^1���[�|$����s0�0�{�*���"�@����HM����]>q�^�]�d$����r5a�����f�����ko�N��6���]��p���i�v\��n�LR��?F`�g�T2Kw5,�^{����C9�\�xP^@?��z��!�)���o��1J���[[xA"$�G���U�
��S#���9�*,RY�i"a@�������T3�@.e	��(a=�>lH�IJVh���QS��1����)8C�>�:S��&J�5�<@��m�f�\"��D?oc����.d���	��q���l�L�2��S��
����
�	p�@"y��5�6��'{����\G���`�=�e�h�*����_�5����F6�i�S���N5D���/�*�g1�������AEp�w0�	�4�.�j�v�Y����6�.��
��o��%��b��S��0+��\0��8F������~��/�������l��wQmW6�,��1��/�����e8�e��&��4�Z�������}�11--��N����h��33��fQj����5/�sR�<v�3���aO���CL�VX�(+����|��,;m[m����m�������Io[���q��G�Z��.`D�t��i8�Q�H���mL%�Kq&#�3��2�T�D����>�$l8v��:O����-����0cf��/B2�2�%`0�.i������Tmg9���[�r��������;���?'���U]�A0H�*�FO�u��e�����E���C�����@���`$[g�_�1)K�#Z����N����c"$3�v���1@!�7�{ �r���L�U�M3?4G�����
��
�J�Wod��P��V�!Q�
J��B�**�Jo��M�����������3X���^5�RJ��P�a����Pu~����I���'#+��b7��1j�\R���C��-#]�;�6���z�\U�����p�

��
Q{7�0I���a��,Rj�kJh?��XH��6-@J}�H��&�T +�����!��p�')�i��LG���S����9'!fj*zH������E(��hX�	�2�:�0s�h/
��V�H2u����*��Y��0zA��s(�1��>���|3%Y��tf�q�(�T����[���\�#��|�_��9�[����T8J��'}��%�\v{��k���)��X�o����"�ml[��\���7�m�D����,S��B��K�_�\'���Pr�y}�\v��!��+_���* ��>��q!�"�����������.���I����L#�{�y�uo8(���)i8�6m�y�4N���4P}���<�_D����H5�c5��	i�Y��x����o��nR�j��7��B~��<�GI��6�&��$8��w�t��������k8����ab�"��p��#Z���,��D�C�;�y|'tQ4�
��&��V�������T�����3)|���E.d��2^�#JQmW�j���L�ptxl�'p���_���#����t�0�`��_�-��p������;;y�;I6Razna���/N.�8��
�F����@�~�~�p����.�3��r(�E���7����y|��f��������af���z�R� ]�J���|a>g(��e���6���~;b��n��h�4w���u9wy����d�5��.�'���3�+�?c'���%!s�"e9���\k��~B�r�g�b��3���7��X�
���{3�p�r�������w�50>��_���3�mln�EyQ�4j��Z��Y�����TQ��]�0o��������%�@��t5������`��VD���U���	<�nq��|I��`�x*y}#�1S����������Ag�`P��e���V�~�$4���8z��
58�6_����_�����6s������n-S[VW���i�[Xf�N��b��wkk6�I�R*qP��@)\F|v�������I�hA#��������Y�(��n,h�+�In@o"��w}�m�����65��OQ5������H�`^�	d�fV��aW�X��e�8��F.s1)�;�pf!�?������f����2C96���R��|�P�W�
%��C�i�����>��`g�R��\b
�2T_��N������re��7��C)��be�P��\���t��t�[*k,������h�� Q�?������L��f���*�
�*~+�,�/�ioJ��/w���+������k�KA��!��%���7f9�������:��F�u.��D��<���?
��b;�Bk��G#�vzfV��1���*���\��M�����#cR{ki��t-�zr�l��������q1K�J�&�b�9��@&���O���TqC���e@��6��q�PW��$�t�>+�Wr�V���%�i`���^����)z��}�����aQ����y6�y�-*���I���W�z��e��l����;	��#�W3�9���(�i��T�3�����7��^���)������h���t��]�8�C��i��f���Sm�?yO(+����\{+z���37��o�k��5\���*��^��ScLP1�z9�u��V
�����'���Yyq�?��A�EJ���{>�Y��tF2����+���������=��U�y�,
�nqD��1�j]��/�<O��i����4��9i���N���
j�)��!������9���h�����C6���3����E�����L����
t��R0 ]}z�8�,��l$���qFxU�*�'x���
�B����d������3`�MVLy������4l$�c ��p
b)�V�Yf������j���}2�������.�pY��kt�3$���M�%9Pi��-�b�W�A�TF�98�*3�))���\�-�z����F����&c������s��,m��5w��z[�"<�C_�����#�c�<��3*�	
m>^����[(3a����e?�o(��e/����?D��pza��UO�����3�j\p`���;�_����7��Z�������!�g����_u�&*FL���U�,��`�b�):d�0I�1���%�������LrIU������"Y���I���2�E��WW�HC657O���I�^x��M�Rz`d��zF�6�r�v�o��@�Y�I��6���������C3B��V�� ����:,D���`>4YB{y�1�MF���xj����U��E"_V�����l�Po�Q3Um�����D��O���^9�jEk���?�:Y�6'3(��s�C��dU���h@\����N-��T7����-C�h4��\�{�>�lC[CC����|��8yx�An[�L��<�e`���Y�Y��H��,G
c4�X�&�U5mT�Z���b{S�0-���,[=
9c8�T4�>o��@���k������@�r{����~��U?�f��_���X�V	����*���?!��l�`��/Y�Nm�/�'��,qT��{|����/,,��3%z��%����C7�>�V9��0Z~�������(`w���Mi�)j��/}�H�63�,�%��B&%�����/�������e#�����1b�0���$�i
����>M�eq!������z�C�W���Cg<�����T1�JP1�����S�����F�9��2G7���T"q�-b��a��U�ye��MukO;~x[
72x$����ls�Y�Ry$K�)�g�<��1W���t����6����o"��r�k��F��c��n�����AD�\'�X
jV8��8������u�_�=��T_����o������ �����M#�������Mm��n�cO������� �~�?��?Wy��L�qs�~j��xw��eB�>:q'3U��C);��a�d}�E�+;�\���E���#��{�k�Z�]�9��	>M&�7l"�5e:�Y1�>|����d����%�?R����XH��,�'��B�5�R�Bz�`�
��<x�������_��|�C���wse�G��<q_���_�H�(xZ��=O;���|�y��%y���������f'��h�;	qY�JQ���|rH�mx�vT�����7�X������~�}���4���b�?����"+d�D����J�V���%�V���
�?�x�aSD�,L�=��[:58�Ble�v���fV4��}�����0:�����2�U��`�J0k���:�����i)=�7�'@��"Zad6z���"��� �$���x��HO�;@��8��(GiB��3Q��������%�Q�&�(Uoq7P#mnZ�����S��7����d��LD2T���m�`(����� ��������Z���f&7Uc�+t��&�9��i@>�����M�2�Cp
f��?	7�h�:(z�2��p�[�B����e@���>�
��U����I��M�S�k�q�4��w��z�M"
�����_:�d��4	#�	��UP��S��O���������y��D����
��$��'���������p+hga<��H��e�]�4��4p�L�j:<����
�#��
���I�)1���S&Znh�dC��s�f[{�>�5���7�M�
����E%�6���U�R�-��}_��cQ"u�O��e�`?�. `{b[�x��D��d;104Pe�����Y<��U�/��Cq(,/p9o��������Vz���%�6���>x|�a��#i67����0.Hy��75�����3'�l�V��~�RI���`���!�� �`��t�(.<�oD���D�����P����H�Xts�E5Z[!2A�-)�@v��WC-d��s�\'��������'�&���?�2_� ���.�����|�_,M�D\7�+�+d��#��o,�mQ�l'P��m�)��n��6����)f�]��U�Ng?���������X,N�oz�X�>>f5��0~�l#�0��t������F\�B[��C�8���E��A�y"����Y�����K���� ��(���R�]W��5��{7���`W�fQ��3�������g�j6�O��a�@:�1
II�.w��I�����)���EDW�~T�hJ9�o&��2%�
��L��6@KH4 ��b����G~(��6^�z�eC]������*b��=�~@a��p���<�����&��i�����M<����c>bO]��4����-#F��f�A��@�q������qi���{��*��f��%�������p������/$�R����������XW��E�A��j��[�	��� 8a�x��u��������e���VZ��iG��
����2�c\�zpS�R6v������aE{�t�u�;��SLO���vC�V��``<�I��i��M�_mQIq���t�.��|����~�5,Fb�^���IC��}5���N2��`t!�;w$ M�C�n�I��.�fYg����{<��OK��>��/���J�|�������������Vy���������{����u�{�?�6L	�������p2|��?/����7��Hn!�0EQ�G^&&�y����#�1��W)S����X��N���C�;#�h:���<S�9g]�������t������kq����e�����p�Q�U�_�Y�x�WX��)Oc���z�%���t����T���a���X?��8���J��9o�;�w
`�N3�m�QU�g���!����)'2�c�M�X
F]�H8��	���'��P8e$���Z�����A=���l�0�b���c��}�
�v}@~��w��[�ip�O��&��3��������G��Q6���~��_���o�l=������|w�R�eN���E���i��Rg7�&��I���N��zVmv^�kg���L�@d6�9�Jw���0�ms,���s��e������VY2��0Z4�n����JT�/��v����Q��`��z�����&���L��<��g��j-`��%O���*l�3���|��p����K SIjj�Y�����x>[�l��������a���K����������[��j�V�������e��kr�+�Q���y�'m��f�]���	N�N���s�]�X0�:��F�,4�&���h��fj�0����,�%S������I��,�GG�Ao�T:,�v+G��p�d���������w|��I�<����O���(4k������EE����iJ�a�Yb`O�0������%3�=8��?=�<��J�J�����?>����������6�!m��#=�
G8���������P���]T�tW����AaK����::�5�h<U�q
TA!��,,��P�T�Y�?����x0^�O��D��?P�C10��'~6X�4h�"}����d��-yYG�ue�F�0(��\%�~Q���+�����}�Ug�����=�W�:��B��w��������;�����`Ny���������.��)���.���������'�>�x����� P�'?v�9�����'�]����s��e�� ����*"
��jo���������p��B<D�J��4o�\�����=����{�'�>or �A��j{5M(y0*i��QLl	C�B��T TM��Sy����-a0$�(�O���YB�R~���&}�O��E���]���ET	��6�5��)������������7�zm:��.a]>�	��H2\�4�������(K�%�9�7��+$@�<�6:���z�}��{\���<�f����\j����X��ZvP�o���?���?��nH������2��C*~.��K4>���v!>��a[V�=2U�����!��w��)l�E�������O�~a3�������a"sF��:�-�gt����iN���X)��<o���3W6���������r����r�}w���B�%�*�1d�r���#��iA������}���&��4���^y2�^����7�/Apg{.���99�m:1v�������-'7���/E���<�`QF���%RDt���`��?)�w��w+��y�e��������Oq����9E�(�����C[Pj������6����{g0������������x�����f��n���i����]6��Pt�p�%��q��u�;�^��iSp{��M�Xq��8��\S�3������Y���x��0�Y�����J��S��3!;�5��#y��������vw������^�T�+��~w��/����E��������}���Ke��T�����{����hc�x���q9�SXy�4��(�(��qQ*L9R��!�y��W}*"=��"��b�!�}���1�[$�)�H��+\Fh����8�6�����&��*��q#oc�?F��6���T�u����_������|�OZ�z�n�+w�}�c]�c�*7W}d��MJ��U���o<�%�+���J{���rwp�_^��5Vy �r�����S�5�(�x$)���&���$)��N�]��TAO/�R��^<�Ah�����b����W����7\^N*�W����5"��fM�H)�9���5�&�*�1(.�T��S�������K�y?i5iC��j��[��AVlT�i
Z9���4J0I�	Ek�O����Hr��/�\�#
bT�1D�_@J�^��`f1���j��J
�K�k<���C]�x ]�J������?����&������?8�=-_��;���/����v��h����Xm�J���Z�z[m���~��Yq;:{nps���O��PR����iw���(�Y�<�JmE3a/�	�7��27��@��10=�&��mU@�W�����~or��������f������� �\����$�#��|�Z��yd#��Y������(s���z�O����
������W�Sm�&K����qQQ�Z��[���y�?�V�V"|������1��@��r�����%(������BP��v���G�G�������A�_^��*��@&"(��1��$�27@V�"s�K]b��&h�5���j�i��-$Q����{%}_�C��*���9��b�Fv#l����9&���J�at���*MSz�v�9�����Jf����!�vjJ@o�����h:L�s(�"��1�+�(�������[��BTRp{Q�A��
=�%�EY��r������m4����[	�B��
=�"�����Y�����c@�~��B�]�!�b].^�rG������l\��6�E�-����s�S�-��`����R��TY_����o����}����������o����}��������������9Z_�X
#166Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#165)
Re: Implementing Incremental View Maintenance

Hi hackers,

I heard the opinion that this patch is too big and hard to review.
So, I wander that we should downsize the patch by eliminating some
features and leaving other basic features.

If there are more opinions this makes it easer for reviewers to look
at this patch, I would like do it. If so, we plan to support only
selection, projection, inner-join, and some aggregates in the first
release and leave sub-queries, outer-join, and CTE supports to the
next release.

Regards,
Yugo Nagata

On Tue, 22 Dec 2020 21:51:36 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi,

Attached is the revised patch (v20) to add support for Incremental
Materialized View Maintenance (IVM).

In according with Konstantin's suggestion, I made a few optimizations.

1. Creating an index on the matview automatically

When creating incremental maintainable materialized view (IMMV)s,
a unique index on IMMV is created automatically if possible.

If the view definition query has a GROUP BY clause, the index is created
on the columns of GROUP BY expressions. Otherwise, if the view contains
all primary key attributes of its base tables in the target list, the index
is created on these attributes. Also, if the view has DISTINCT,
a unique index is created on all columns in the target list.
In other cases, no index is created.

In all cases, a NOTICE message is output to inform users that an index is
created or that an appropriate index is necessary for efficient IVM.

2. Use a weaker lock on the matview if possible

If the view has only one base table in this query, RowExclusiveLock is
held on the view instead of AccessExclusiveLock, because we don't
need to wait other concurrent transaction's result in order to
maintain the view in this case. When the same row in the view is
affected due to concurrent maintenances, a row level lock will
protect it.

On Tue, 24 Nov 2020 12:46:57 +0300
Konstantin Knizhnik <k.knizhnik@postgrespro.ru> wrote:

The most obvious optimization is not to use exclusive table lock if view
depends just on one table (contains no joins).
Looks like there are no any anomalies in this case, are there?

I confirmed the effect of this optimizations.

First, when I performed pgbench (SF=100) without any materialized views,
the results is :

pgbench test4 -T 300 -c 8 -j 4
latency average = 6.493 ms
tps = 1232.146229 (including connections establishing)

Next, created a view as below, I performed the same pgbench.
CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm2 AS
SELECT bid, count(abalance), sum(abalance), avg(abalance)
FROM pgbench_accounts GROUP BY bid;

The result is here:

[the previous version (v19 with exclusive table lock)]
- latency average = 77.677 ms
- tps = 102.990159 (including connections establishing)

[In the latest version (v20 with weaker lock)]
- latency average = 17.576 ms
- tps = 455.159644 (including connections establishing)

There is still substantial overhead, but we can see that the effect
of the optimization.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

#167Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo NAGATA (#165)
Re: Implementing Incremental View Maintenance

Hi Yugo,

1. Creating an index on the matview automatically

Nice.

2. Use a weaker lock on the matview if possible

If the view has only one base table in this query, RowExclusiveLock is
held on the view instead of AccessExclusiveLock, because we don't
need to wait other concurrent transaction's result in order to
maintain the view in this case. When the same row in the view is
affected due to concurrent maintenances, a row level lock will
protect it.

On Tue, 24 Nov 2020 12:46:57 +0300
Konstantin Knizhnik <k.knizhnik@postgrespro.ru> wrote:

The most obvious optimization is not to use exclusive table lock if view
depends just on one table (contains no joins).
Looks like there are no any anomalies in this case, are there?

I confirmed the effect of this optimizations.

First, when I performed pgbench (SF=100) without any materialized views,
the results is :

pgbench test4 -T 300 -c 8 -j 4
latency average = 6.493 ms
tps = 1232.146229 (including connections establishing)

Next, created a view as below, I performed the same pgbench.
CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm2 AS
SELECT bid, count(abalance), sum(abalance), avg(abalance)
FROM pgbench_accounts GROUP BY bid;

The result is here:

[the previous version (v19 with exclusive table lock)]
- latency average = 77.677 ms
- tps = 102.990159 (including connections establishing)

[In the latest version (v20 with weaker lock)]
- latency average = 17.576 ms
- tps = 455.159644 (including connections establishing)

There is still substantial overhead, but we can see that the effect
of the optimization.

Great improvement. Now with this patch more than 4x faster than
previous one!

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#168Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#166)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

Attached is the revised patch (v21) to add support for Incremental
Materialized View Maintenance (IVM).

In addition to some typos in the previous enhancement, I fixed a check to
prevent a view from containing an expression including aggregates like
sum(x)/sum(y) in this revision.

Regards,
Yugo Nagata

On Tue, 22 Dec 2020 22:24:22 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi hackers,

I heard the opinion that this patch is too big and hard to review.
So, I wander that we should downsize the patch by eliminating some
features and leaving other basic features.

If there are more opinions this makes it easer for reviewers to look
at this patch, I would like do it. If so, we plan to support only
selection, projection, inner-join, and some aggregates in the first
release and leave sub-queries, outer-join, and CTE supports to the
next release.

Regards,
Yugo Nagata

On Tue, 22 Dec 2020 21:51:36 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi,

Attached is the revised patch (v20) to add support for Incremental
Materialized View Maintenance (IVM).

In according with Konstantin's suggestion, I made a few optimizations.

1. Creating an index on the matview automatically

When creating incremental maintainable materialized view (IMMV)s,
a unique index on IMMV is created automatically if possible.

If the view definition query has a GROUP BY clause, the index is created
on the columns of GROUP BY expressions. Otherwise, if the view contains
all primary key attributes of its base tables in the target list, the index
is created on these attributes. Also, if the view has DISTINCT,
a unique index is created on all columns in the target list.
In other cases, no index is created.

In all cases, a NOTICE message is output to inform users that an index is
created or that an appropriate index is necessary for efficient IVM.

2. Use a weaker lock on the matview if possible

If the view has only one base table in this query, RowExclusiveLock is
held on the view instead of AccessExclusiveLock, because we don't
need to wait other concurrent transaction's result in order to
maintain the view in this case. When the same row in the view is
affected due to concurrent maintenances, a row level lock will
protect it.

On Tue, 24 Nov 2020 12:46:57 +0300
Konstantin Knizhnik <k.knizhnik@postgrespro.ru> wrote:

The most obvious optimization is not to use exclusive table lock if view
depends just on one table (contains no joins).
Looks like there are no any anomalies in this case, are there?

I confirmed the effect of this optimizations.

First, when I performed pgbench (SF=100) without any materialized views,
the results is :

pgbench test4 -T 300 -c 8 -j 4
latency average = 6.493 ms
tps = 1232.146229 (including connections establishing)

Next, created a view as below, I performed the same pgbench.
CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm2 AS
SELECT bid, count(abalance), sum(abalance), avg(abalance)
FROM pgbench_accounts GROUP BY bid;

The result is here:

[the previous version (v19 with exclusive table lock)]
- latency average = 77.677 ms
- tps = 102.990159 (including connections establishing)

[In the latest version (v20 with weaker lock)]
- latency average = 17.576 ms
- tps = 455.159644 (including connections establishing)

There is still substantial overhead, but we can see that the effect
of the optimization.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v21.tar.gzapplication/gzip; name=IVM_patches_v21.tar.gzDownload
#169Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#168)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

Attached is a revised patch (v22) rebased for the latest master head.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v22.tar.gzapplication/gzip; name=IVM_patches_v22.tar.gzDownload
���
`�<ks�H��j~E��&��������$��qp&s��TBjl�B"��cg���9�[@`���3u�JH������r���>3B�����U�=����j4�Sm5��gt<S����[
US�)jM�j�X�)�Y>�Ah��=��_xew�����M�����r�q��r��g��x�E��'����M��o����[������
�
A�������_����5��3�|�?����2�j�����f{lMZ��Qk�V�]7Z�����jN�V��z.�S[LQ����9s�~��qa���K�?��A��*�f�r�F���.�s�������
���+*mE�
��O����wFG��R���3KzL�%C�d(����D,�K�b���
��;��r�3���]�q�J,��`h��0�t���x������ �
�yn�sn���3n������w��������Uo�3�,fO����� J�0�����_���Jp�|�17�y���K����������Os���n.=�K4� �Om�^!�)gc@H����ep�yi�����$�&s�hTr�r9����
�
HW5���p��*WL��WX��C��]��[|�%�a+��4�O�^	��.��p� 9@j����B.g��	+�/���{��3�a�[V�-e�i6x�R�o��:\��AN��!]��)����~b�Z]k���H����Zx7��� ��f�:���#�P�����c{(���8p��d@;`>w@���s2���Xv`�E�Salq�F��q ���
h@��k`�����,Z�1\7o�
>�+�sR���'rk!�����p�D:��� i\Zj��<�zV�:F�
�I��Ti�F*���G?uRQDA�s����E��j��`)~��L0���U@����y� �t�a[�u���~�uW0�F��yxb
��pf�&��Nu��C;�?dG����c�"���0-��Ze�&8CO�������!�
��CS��Ly���,1{��Awh.Xu�$��#AH16%�`�f�/��L�0�r�����c{������}��DXp���`��u$�LC*\�V����EU�e�y������Z�;;�����U���g�X���'P�9�����������m�i7�����F�8�1
�@���YP�b��%���:�x���d+�LJ�@,��*Kp}�� i�(���]�I���k�+h�%.��oV����pP�5������p�H���^{W�V��	�'U"�����UaA�G��$K���&�Dc�����0��@s���Z���7��
���B��s!#�@���������AHd���&T���OL1
B	��H�^��<\�=�r�F^B�����a�6�aS4���k
7����B�������?�%�J�hj��B��,����~d+>���.H���0V��7z�d�	�!��#�8h��5���K�$�D�������tU������"T����L�%���y��w��A��n"E��p���p� ���g�HF�_kq��x��T�1K�sP~%D�DB���%��#y�9�B�����+!�/_J������x+���9>�
 �M��=}=�H/����1'�+_���������/�}������S������t�;f?��.���n)�H a>�)��"ry��u~IF�zK##�P[����0!n����|
#��H�9X�@�t$0�Y��pRe��J��\�de)�8"3B�������=�����Nd0`��O�5B?�'�w
�	����
�Gv��?2Y�i����u�����^&�H�H���u��"�Rl������k;?9�$�������F���D4S��'��zj��V@X
�%�����C�L�r�����Y���l�?���p[���;��g�#��c���;�"��'��e-)-����o��/����7�f�T���?T+�J�LlLv0���N,���tC�p�cU$;%fg}���gW��^/���A�O���+�����w3��JQ��kWj,J9�{�K�7-s26y��6�k-�Uo�Wk,%5�
�P�4!m�A���}��G\����p>&o�A�t�6J��g ��L=�d�]�Bv�9�0M?��SX��@&q*>�+�'|`x����P�v�<���jXa���8 7��,�U�VE����5����;2�?�r���G�n9��f��m��6tx6��uE[��(u�{������7�����U7�O��l����]kN�ZC3Z5����j���9�^H���!<�)�@m(���?m���,R��T�5�����e�[XM��Kfy<�DK�`��qq�s@�<>;�
G����
`�w��!����C��@�c�J�<��r����Q������{�Z<2L�J(��E�p,!�=Qf��
�	�����r��G��?r�7J@&�M��0o���~u�n �1���n�%�ai>vlsS�DD @�G������y���L����8j�-����6���jl������A�!�	�r���Qj�/w�T��[J)
���$#d��h����5M�Rk|��Tubn�TJA��UJ���GU�
%����&V�t�z�c ���P�7�s��|����
����Q#`�oQ�B�� N���h��}���W����0L�d�X�)'���H8�����G�c��-/�a���?(H�d >G���yqg'@L���w�7���|���g#}x����`�=.$����"����V$1`�R�I����������E�a�P��.�k���
���gA�X^��E�U��A���/i�'c�W!�����l���wF���]g�e�C}���8Y�Z����"s[�F0����k.��e>�v�������1���Y����C�Ev0���C�v�#3yT�[���YJ����~hA���9��a��z��>x�|4����������^T[����y:��"�	�w6��T�B�������Q�Kd@Tc�.�kl=�Z���0V	��Lb�5��^/��8��h����5��V#�^}��������C���/ax~7����P%��Z��g�R	7�������X�\ $Y%=�n��J�|��
(�}�_�|h-��PT�v�	C�
���������E�OKFlOc��|��h�$�C>NQC��,id.x��|{j�w����0t�@���N��|��/�
�3�"��B�������2�9�S"��JnM�{��F�O5EY5/�{u/J�D���Z�S�����	�H�J����{�2\l�O<�{^H\{�����������iq)��v�.�o��23� _8Pgu��8��PA�c�;�K�K������`��$�p��|{<��<f��6�@l������vG��\��������o����E3��yU�1�Is�Q�h7���1nV*�MM�x���-�����*$��Y�jR�j����P	hJ��3��N��L5�/	�������-������3�vkbj�^D�q��s��1��g^y	�DI�?��'����*����Y��3h�~�m��Q��m�#��[�x��'.r;�!�$�p��$��K)�?N2��m&h�u?����N�h�3��z	���N� ����������f��W6m��~|?�����a��{4�G�����p�-�P�o90�������'�Z����Tnh����M���
�Qr�Q^�����v8�m���,����I�pH�5���=�J��q7:�Y
et@kJ��\��f�Y�( e	��I������!�Z-��@��{�n*��&����N�1�aSri��"/KA	=���m�X���D>�F���;�F�q{0:�(�L�p���l_@L��P���8hx������7�J]]�����F��!�6���������<���x���j7�q��T���I��������V������5����.����o���L���/�,�T:�fQ$o�����4X�O����3�f����x�J=�esD[����\�A�J��h
p�U<}�����]v)�����;Z4>�������U5���������_�!X*��]�>�|o�}���FMS���������oqP��9����j����J�6V
�2[Z����I��f]iM��|�O�`�*��jq8KFbIYiJ,�%K���D��R�T��U�:���pT�=���<l�k���N�}��c`���jG@��9D���<iq�r���������*��0��d"����jH�3:��f{���>���D�M�\
����}����d4`�
�<�i���Zz��E�J�n�F���6��������qRN���E'��p�%���~��$��3'��0�����B�H@�W���������!��!����G��l�y}�e�k��
��1L+�O�����v#����R�>tN���\���ZRl��W��E_k�Z��^�G_���@3'��_m�|�<���*Z<�����U������/����s�N����9��o��}�u�i����T"Ydv�S������(`�~w�����O�6f��^�m�.�_{�w���;���y��{�X��@�^W��0;^a!��0�e�<��*c��+����U(�[u�&B?7\x�&�~M%W�����,r68������%�T�QU��3����1�,`����A�gJ��z��
�o�����2��~L4����3�'��W����i��p_EC��I��|��O
~��[M����:��?��b��|�7��'/6-��������G�AcT�Q����5�+��
Xf#Y�h���c�/��~}�@�'
n�����8�`�>�K����V7��)\LZ�g''�;G?�E�eI|j��nz������)O��Y� ����[2��,�%���,�.�n��b�n��9���Co��wN��������t�EE_]g�����)�����c�VJZ��<���o�C"���!�O����,=
����,������{��#��������]~���=�@Ha��H������.�����Z��0�f�@�d��]�����yI����x�q|�zH�Z0:����8����w�v���_`XF��)c��;==���+c5�/%�RHL!�B@�,�����������GX������Z�%��C*b"�����&��������^	E�!���	����6����8��/)U���,���> �����nN=��Eem�01�w0��$#�k�f�S
Ou<5��o&�K����`	�=������(��o��1y�U.*x�����4_(A�t����F��[��������2ZH�i�O��6|���Kx;8;�����n�d+��q�k��x.��z��)����?���q}ObvyN>�(��h^1P�(K���E���2�������Z�8�Db��g�k�;�q��qi��h%�&�^w��tWc����_��k3~MQV-���a{>d3b'�N��WY�5/�f����������ob��]��3{Y.�I<������i$��:�J�"G��XS�V/si	���%����w�<N�T6P4�u�}������X2��6	$��nU�FF�����'K��\�cE|��+_���-�[�wn�_���q0A�"s<"c�`=�ny5��%���)M�Z�E=?��O>,�H)g��z�w�(Z���������t�^�,��wq;�g����>pp�@��&H�&�uo(��?v�/��m����E:���;do�so\�����Y���{�o��T�k4�B���������Mer��	�J�H3f8�Wg���bpO_S���
C6_�����7�w�������63���*/f���b�)6R����v�&���_i����F������'�,�}�v��L�Y�M#�j��Xo�_x��0�Xl(FV�Y�rd��Y��,&~g$z��WP��g1pN�G�Y���|S���U�*���@#�*�K�j�"#}�@�8Wk��Z���n[x��O|_O��Pz��}T�G ���xm;y6-�6_!G�I
|����Y�l�`�~o=#�O�hh��c�����S��U0��b�'��t^���B �BE>����>�4��v��Ir��ex��TE�D��TH�
�8j�27��M1��/7��791*�o��kL�o������P=���{�m��z��Ik�	�
��7H~o�W��;�����$5:�#Yp���|#kH��qV	{����O
n���3K��*&�}�tx>q�7�S��	�adb���L��7F����0Ur]�>-�������nqZ]�f���A�10�t����*��#Yq��~�s;�pu
b�x�%���AS�T����.K �!TtPX�8Xb�c�+�:4���E:e�tkg�x�Wq� O�8y��g�a�YhX���T��C������zCD��A�||�k7��Q�G#ti4�$r�3��A����0yK�+
G"9����.H����~r�uP�)&��a�������.&�?���F��Jt5����p�5����
���eL�(������"�a�FT��%���p^{_9�M���[���bB1x����BC/R�\����Q���O�R�')T	G�3���&�)���*m
��4���D�6�'��
v�Hgbf��;�z1t��C7��p�E�p���TD���$.��&r	p�{�W�m_���WVD��B�����F�FHe��T.�2I!
�x1g�d���F��z��+l���B�L�_��
�P6����}�N�h������a��B�R��0
�!'>��/R�Fd�[h��6���H-�C9RK����&��x �(���`A�i���AxFO�� �P$l#O]�)+1c��S����IE2{��i.�5-d�����8�sZ�](���bKi�"S�%�6��
�-t�/5��"��)�d1����x�[x+�
�v�E?�/+�0�.C���+��Hj��������<3�����6Me�O�	Oi
w�����9VW�FM�rz�d������L}`��`�(�����(}�QN
�z������L.�J����e�d���#��9&>�&��4��_X���=T�{���X���e�jy����������
�WF�����i�J���Tll1�<E-�Tb:�-�h.Ck�Qu��}cO�z���%�I�����X�����>yoG���N~jSNH���\F�N���f���4�?�PW��	m�$��G�����������.�"a ��-���$<B
�	�����+�ZU\�J�C�
�:x��Qo��
����K�d�n�3"�l�	��
'�}i����`�I�v��m�c�I��r���kB��nUq�0�>�9�fK����O+���o#��8^���z~b��i��H������}-J�C��V�I�NZ�Q�����f�Y&�m��d���l��dd���;a�
G�\82#��(���]3#OV��������fA�����|�XCh�5������������TlW���C�Z�����"��bN���f�b�<���w��*,��D�(��s��D)�-w�����<#V���LPf��C"����M���=Vk�����	���E����P�C�i��G��^�&�j�9�d��������3�,�b�a��	K�����*�����,t�������)-_����;/q����0��Q�zr�^l>��I
�^��\sq�����]z��w�wy� �������1^���)�l��K�����I!��ORq3�O%)�(P��D~���#[6�O,���L���d�t&l�~��<:��W���
����P��zt��Z����rQ��>��B�i�v�������on��	��<}��O��������c(R�����.*''�������)���hT�THo����<�LJ�����0h�����
����{�1�����5�Md:�nW�$�#;{��8r9PG��]|�����'��`�7@}��"h���ax���@�����D��%�=��8s|�YC����?�?�����5>�V�(9���bOH_W��f��j���D�#B����d�����O]��d0�0]>���b:\������;#D���
�>�{j���J�<�I�����J��*OT�;�1��nq�;���y����-g��X�f��J�O�d����S&{����k�'��Ux(<EX�#�Zk
��1�J|�����kI X�(2��
����'�2����i�C��b���n+����Q�	P�l/�g�P1�I_�:'�����!���{�
�/SHp���aC�KE���8�����S�����mhe#���z�^��U>Xrp}�-�0p]r�]�����u�7e�������0��1����qV�����wD�vp'��O��X���-�w����������vi(�pE�wk�����n`[g�~,����=|���.���D
�]X+��D���4��@�;�2.
B��M�i!�S�>U=[�����-��v�;Y�%k}�6�Y,vK����u���i���T��W>�K�[�>���j��_)�W���ri�D+���;�&3V<������]����X9g1�-�kqQ'���%�;��i}���G��.�Z���F��`F��h	}Z��)ZB�d������$�2�m�|T?�����g�]�W�O�z^{�ne]��l��3��������f�Y���MnA��cXgJ����@��L}����$��n��� �4��IX����g�m�(��c��CxO}R^������&L|G��I���5���������v��/(������\�a�
��������Y�� V������{c�q�}g��l>F����g��{|�����+{��H�wc�YoY�aVv��F�m��e�����X�|��3��?���f�H�E���Y+�7D}����Oc	6��)�j-X�4V��`�c��	�Z����p������	Wqp�*������n�;��D��]��pY���!nS��wlzb������p��9�m
���c��>�_�;�c����;�w�67�Z���=A�=������c�-9����;��[^��B����k��n��kip�^���ZY0�����.+���*I����2<�����.���%%�.l��i,�,3�.��������-
=�+���	��V��J��Z=����33M5��23-p��7�������2�e�q=�H�����uii��O�
3����P����{�S�?a��d�M}R^�I������-GV��9,,�+O�?��#H�	�u��h��_e+z(;��g+�����-pm�M�l��H�wc��OOD�2M�Lw�XXVz�����p7*=��`Y)I�j	XZ.����#�z��z�{g�{�!�Z:���;�S�U���������|��L�0�-�������X ���<�����<�
|w���S��5�3�=�?�5�K3D@��,�����'���������f�P1�I_�:���z
���y��d�N4�Y�5�r�s�o��g���3�4n�(%]��e����V= �G�c����nS\%	Z��<�Xsc��i@���=y��B1V���yT?Y�G�gs�����O
�� ���=;���������p�����;��4������+�s�V&��2�=|B����UF�eB�qR"�����-p���&��h��w��@��,^�J���k=1K�<v��)Y+�i���n�������3��v�.f�\3����c��6��8������l��_�z0�����n�X%�Z&���c��������AO�\���&je�Y�b��i`����@�G���G2||�7�=�����bYY����i�|[V9��@F�����WW9�p�S����*Lk �����5��R]�eAV���b�������lK���W���
pS��)/{P��;2��?�v!]]k����2��>�S���}_���E����aZ��\�����$M�"��$-p����i�_=��gi���l��n����,bO������?�����	.���`�'�N�Qhu����}��YV��e�S>��t��*��2!�8�M������.��2�e�<����<^��L������'f�����p(�=�f�\+G�������1O� "��x��a�Cz������c�]f��*���M!���;��nu����gIV��U�H�Q\s�a�������7�
��+\�RP��4�A�N+����mX���6�'���i��4�K��kLH������{����4�7�Y'�$�J,�f<���<���#�;��RF�L�F��O������S��$}1��>��LG�4P��v)t����a1A�g�����������)j.	�K�4B2����z����H�������F9���e��:�[0���f����2��p1���S�����o;��g���������H�f4�<=O��y<^;��H�no��}��bd7��h]��+�G�t'f�`k�����yL�;*������mG���
��j����v�V�9���TB��|	y����IG����keZZ��6n��;����-��?�$�-M��&"%t�
�C4��{�$�+��"��$��M��i�Y���F�1�)�����b8��1`�������g'�*�����a���}�%�>P���q��+F�g}����������>TE���\��k������3-4�a�r}.�K\�M�Oe��	/O*�o/+o����O��\��k�e���I�$Cg4�id�9E������t��c
������0��������Wc$3����`W��W�+��#��N?\����$><�����(�����w~�S���������u~�y�)}uG�����)}�z�	�!��r��n6G�#�������J�������`y5�s�@z�C����C'X#��lB�/��jw����I��B#������7����x�M���@B�
���nD�Y��e.�eI~��V-�����_"�B�@P�QCa�W+2M�hh"0\I�U��#d#&M��QCF��Mk��0�Q��_�c �J�1_�&���+g�u���������0j���mw�z}����(�lc�U9hb�6�� �z�M����&�B����s�����cgx�t�V�����&p��x��lc��r������0r�r�g�m���}����[-�O�P6��6���F�3����V9��2��k����@���������(\c�U.�(6������_,Q=��Q==��A*���"(�1���&��>v��u�\3���@�z��;K�#�-����W�����$ �`�}��DP���OO
"����LB�����:��JP'C�
������-��<���v!N/ON�
`	�9 r/\(��i���_��f(@M��j�\�*����}����q/���T�=?������R�;T����~10�731y��}8^���L�Q�1q�V�]�/4���D�(��O 6�����5�"���N�w��9=���f&N���,��E|�A�ap3�_���=�P���F����Jl�Z��%�oH��0��q�`�z�u��;��K���-��T��~	�LI<��CPaYYI�aTW��'�B�g'���c�Y�f�v��AO4��Wg
���\{p%�
�I������ �/�����t�U>B��Mv����2��Q��������1��^.c�d�B�NEi��4g��]4�f�7G����^�c#/��BN�;�C
�R��xO���g�P�����,3
�KN�]��3��Q
l�����u`sA���q�L�����@�
3!$7���q>��M1h;��L�#>���
����mC��V] Tm1�@��v�sB�Z8��k�bMlk�2{���������Z�5-+X�$�����\^/�=<����`��HlA2�7���3��'�����Z����7�z�B24����Y��7s����HN{�����x���������3:]cr��p�I��]T�8��k�������������z�}f'd�aHw��6��cd���&�TXr�5�9�6h� �A���1�)`/G%Nzn�����4���	����1�,�~�`Jv�DP;��E�^�RT��(�(�����\���MKoKX��'�k+��8��<��|7��
k����������[x�u*B�5�O1�^���4�*@�s���z���p�!G&����Z����t� k�#t�������Q9rs��3.�%��
~�|aZ�n���3j�����,�vd
��m����m
{���-@�ab�,�mry��[����7����e�[��d�:l���?'��(����Q�C=�����(j�MS���J)����8���� I��u�w���n�AV�E6�N��}|?'LQ'�2�v~�9�����c���w���)��s����Aw��bcx:����g�������4D�da��atm��$G���
����@	Y6��5�hy�����HGX9�>�j��,
�����}�<��i�^4+&-�{�\�����1i-n��RQ���3�����}������/g(�c����	��s��K�T��n�Y!�3�~�OLt��gVl��#&�=�L`%�;����?��;v|�0�X�d���R�o��@~�8h���K�7�U^G��,6��J��l���TY8g~<�[�>(v
��3����bpF
����h�/�n�Z\c����:7N�ck������M��F��Xd�{n?j�A[���h{O��
��	��;��Mo�����7�Z�+Om���>�=�}��zqA�e���y�*8�P�5�a�o�����f��m�lR�@���W% ���7�e%�o0n.c�6;����T����5L������b��# j�fL���{�1�o�#��r�n��c�5���_%�u=t��2q<4��pvR;�M��1��n����Q��{���g(/Z<�`�s�����������1�P���=c6��Q�i�6��PTO����/�������%(��A�����:���GU�m����Y`2rI��TaC�J���'tE��`�+J�m��Q�������VV��+Q�c[��{�!k3���0����U���.	�������'l���_���1'��>`��������a@�\(�K��vv�W|0�;���� ��W�!f-RO��b��k��u�M���Z��XL�rk6�@�M �~E��70k4�^�I�e"���#hf��V2��_����(��i�8H�>L�*G��PHl{�
@��[vx<�-E���������c"�z�v�T4�>�[��-�F�.i �T<�P�
F;��8��K�������I��J�%���.�3�����NGlo_��e?����#�=uU�� ���v�
�u��� �5g(����)8������|�`��,�)��U(�����x6Kc[[[���_�%���=�E��O�p(�C��W�?H���>C���:���Q�Zdnt�&H���t��t	���	����J��j��R
�QQ�~��6���k��F������r`��-��8C{�e����~K��>,0=i�����<��1��j����nk��:�&im����o=�!�h�#$����2�
�dE�bcW:c��[�<�Q<+Z[;�a+J��� �
���=e+���&n�H�
�V�����^>�l����A����
�M�l�h1�����[�al�����0���r}�O�[�����W]��Y}:���p�P5P���RJ�@��������<����(`���{mG�].����:N���-����y��y�Q:����%.C!W�E�Y{;��
l-������l�EtvUy�'9��
���h:����C���^���a_�(�W���A�)9�B:�U�
f����RA��e�u'��#��7u��'u��vn���BQwoJ��@��g0�R�������3Ra%)0���Is��7 ���0�9���^�i����p�GO2��UHL~�����0���6�%n�	��}�7�9�98��q=k�%�<f�]��^Z3g����e	e���p6dLk��zZy_Gg'��O#g@��_�~����hIMs�w;�v����f�������q��WE�P��Lhv�YkE�������K��`IZ��}��k�D'���(eT�*JWd��^�I���3�(�fR���V�������\�#*h�
�6���qw��(��\�7N����_�1�	#G����r�1��L����IP����0=��I\5��l4�0cTjY5�P�$�j���r�E��}��*!KBM��[��/���CCa/Pv3�����8���M�*6�����F�%�)-���b�_�;t03���r��ko��B/e���.w[�E��%���T�
(����+�Oi�"mf�\h�O�x��`Jh,?�������
�>���9�\;}R���hY�G|R�G� �7���j�3X���-o8tZweS��U�9���i+S��e�s�����B|+
}����h�7;}��0�����)r���_��zn��������$���O�aeU�wYY�i�,J==��@�.�#�,J>=��b���#�L�]y�P�/1��K�C0J����u�H���0��q��.�vD}�s/���a�fg\�85z�V�WK[8�NG�fb�X4�u?��:�E�����?�F��Ph)��KQ*��K�27��M�F������Q1c�;&�cd)��	yT@
4���m6Q������rY:Y�^$�1��w��*���zon�Y��x%F�p�9�����<����hd{f� �M�2����;����9�Y$�X�vw$+N�n�rn'��#Ss�����z��$�N	J����o��,�b�}]]-�7�~J�JC�I~h#������F����QdN����6W�S3��+%����0���������xnr��8�O�u*������
������,��G��h�N�u�\�8,>���W����vLQ/�x3:=�BC�J�T�#�s|"A!��lI�=^�B��B�����qa+����ZtS�[T��p8�
��	�-a���(���|���*��a��� �zq�SY�Ll��������M #wS�M��H��{A��~/�g�s��	@��	|��i�6��R��'u�QdI�h�������^������R��d�9�� uo�x�[�l*��g�e����b�tq��Q�M���&�X��B`�8��v�k�]���.���,-���;!�wu��{L+1��S��}����i�%>�&�Rz�^��^f����O,"�b:��V���+��-����Ww�B�v3�!�{'��M�����,#br"/nr��)g4�; K�I��wGe��E��L"�eN���O��l7!�f�__�de|��t����Q +�2�E��fz�P	�V)��f�&�$i�,R�K\�0LcY2����zu~�5���y�5if�(?5��C�����[�^B["6A$ v6w'���4#n7
� <3�t���
�H������
9��(��q������>��!~�6����������Eb�����v��������D���zdT,�X����	�x�yM2@X�w���m������s�Z��yNc�9�� $o����&�.��7P�*<%`��R��|��3�zn`���{�0���&�	����U��o

�:�Ec��el'�[~J/��=���I:������o���2�������#8�'��\�,�9&�@���\����9h%_��	A`a�_�G���h���B8���$�q��p��g�����67O��wuG\f}k-":�VZ&�3��w��74���n��46��6�7.��&�%	����!��tW��]�u��/��ysK�3�v�O?��]��4$�Q�M�������������W�l������?�t*`��E���i�28��I����^J~�b�tO��X��L�t��g��(�R�R�P�Ly�J�u��d�:Cw��,����L}A`��8qS��v�!����OKS��RV<-:��|��g6N������"�}���X���X���X���Yp��������t�����3�����X����X����YY����~b<������c��j�eW\v�eW\v�eW\6�e�~�>-.k8�=.;��V\v�eW\v�eW\v�e%�E;�c8EE,�O�)jcZ���]���]���
��?�����DvcZ���]���]���
���?�����Dv!�Zq��]q��]q��
s��?�����Dv!�Zq��]q��]q���\����8��j�O�Lv!�Zq��]q��]q���p�?�����Xv!�Zq��]q��]q���p��?�����dv1�Z1��]1��]1���0��?�����pv1�Z1��]1��]1������c��F���8�]��V\v�eW\v�eW\v�e#\��Og�3�G8�]��V\v�eW\v�eW\v�e#\��Og�s��8�]��V�v�hW�v�hW��?��,���������w�c��l��	�y���($r��v7[\ ;\|8��$��@�aa���8�2(�2(�����t;���"Z91,&�aqF*5����zqB��^f�4�EQ@��h���JD&utw2r�V���y�2���pBLo��}��bd7��h]��+�^�n�k�����D�_j�w�����23m/q^�_���N��B6����a�����@��f���L�[�	��1���G?'�#���������#|��H�_a�,Xo���nqZmf���p�R���M3���
k��-�?�������
E���}�-F�P�~����Ry���>TE��������^�%�
��i��!�	S���/p�nTv�����r�����p
X}Q?���ap����'��n`{d�����Y��������`��������p�k�.��[��]�CO�����	�������s">���u������h���sC+�W�7����\9D6�#��������C��R?;��
���=�����	@�����`����/����g��Q6q�G'���"jW�T.O��
�6���Fs4t����g������%��J�[�Dkk��Z|��*��9\�j|A�YQCo��UHz���H��;E~7������(�Z�7�du�:_6Q�^9�������?���{�Z�������\���<WDg9:]���)��6����h"����&��������Y{#����� ���}/�Q����������''��O��x-��=��t��> �xG��*2�S��uA���%�'�� ���wO�R�A%�{�J�|��cw>���5�	s�o����������o��������W��1�4,'�`��n�O� ����i�-{�����%���Y�.�)���v�c��D{�~�[���:W�#�R�#�wd�����hq������|�},���]~@�W�X������b�?/�31������-|����.�z�Vd��Y���' ���g�"jw���������7+4���W��� K����s�8��b��(dx6��+��k6_�l�
���E�J�[b���+�2>VU���(���`I�,X��D��;�[}��.-���/B��t��E=�^d@	�����$�J0��]�#��oH���&M\�G!..����b'����!����]���+|��4T�^t��������Q6���P$�Qp���c(5og��u�[Lo���T
�.O��g��V
�!{�Q��;}T!�N��;d�N�w����aM)����4��>��zc6���|�9��N����o{�L������(Nj�k��go�\T�1Id��La��E���/�B������C(C0�E���Y.*�?�h�z�q�9X\�0{����^}��	d�/.���#�=t�5����h�/'Z���h�l�����F&�W$��D9=��q5l�c}��o���'TM2���	��(<)JQ`^*��F�������ph�6r�O�| ��G��C�Zq���b������xhl�\&�Sn������1D���Q�C&6Y:��e4'n�#-��v����Q(2r���0IX����W�,��^�6����9��-��p��<����B�������&FQ���������=�B��R&J<�S�}��Q:���V�'��	A����(}������3ih2�F����ch����
n��-��������=��(��#
��
	��*�]�#D���a���Ql�9�����7��F��G��F��8_C��i3���v\��&7d���*���=�9�y��q%0�m�Q:C9J����O0,����L
��#7�����#Y�mZ�6��G���Y���{M�������;>��_3�<�%�v�	�A7Ez��^G1&~!�R���E�R1�4,N�Q���1���������_��q��wZc����������5c���������;X+*`V�>��QD��^\����;;��O�C��n>���Q��q������&����_��='��&+IlEW�����x�fnS
h���Wv�i����G�Q���D��Bc��,UD���"�z}��k��S�J��z���e�xh�����v���Oc�u��-���"e��p�3������!"9MzZ��h�rzA��L"��U���1�c��4�
���5�S�y~��8�~���rt	B�oP��y��NR�����6Z@A�,0
�$��W�
�#NW����6���H*��k)��s��xZBt��<��}�5��w�\H��j�dTK!��m�B����0}���k�&�Y�I������M�e���	�_� sk6��M�.}E��7��d�'���a�>�\U��/B/�`"/�d1�0 �FK�O�(`�5p��b���[��Y�=��)���9~��X|^(X��J��]���� ������e���?}T���h�=�����<���G�v�e���v
�'~��{{���B���c���;{��_D�!0F�A�������~z�i����7C�'�w�Nq�������Uj�������va����w���{�n�@X{�P8��D�p�9����}�t�e�>������[^�����1(�����A=q����:V��h���V��PX�7�/�P��C�~�^?�J����@K�h)���#O Z��c
5A�Fo
������9#{����7
�#�������8m��z�t�����m�o
���o�#�d����&`�r���*MloS�E�q��/���g�����(����]�vx~f;���v;��}����<m���7�n��|��;;�A�Y��;��e��=�������:�~B���_b�*�s�b�>�<R��N��cX
��g@/E5���3�H��u������N�X�iX��Cg�u�d�R�a�����hm
]#��\�����FJ���x�������k`�6��t����Oh������\q�n3�{���������3��
`s��b'b��3���+�%�sV�p������5�#2��� ��W�*"1�!�uY��7Bc0�]�	����+�bCJ�<��������g��47B���p�FC��h�^]��da����'��l9��f3�d���<��Zr}P�G���?��4x�r���bT�����+!��67�o�xyx8�j���u�+xz{����%���W5o��9a��v�0"^����s6���������~����$����jT�����@om�����f����e�`�AC6J�0�x�s��������
�2��MV�o�3�a���#�����������a���hdk$6iv�M,�Ut�����h[������+�M�/<	q(6677���=�%����b��M)`���]'���>����Y��U�#�]���,9�t��� ��W���W�!v[�u�A�����j��nu���Q�v��^��9��X���FV�zEa��b+���}�G���Cl���zxL�K�bX*�wsV��LYl��lTZ-v"�!�^�h�����'[�>A�y�y#l-�9��Q���P+[C�a�H�t�C�$�C� .��{?���
az^=�{����7i��rYZ3�(�F
SN�m����~�y���`�5s���xa��OD�jNz+% k��W��)���y��[������	(�J�)A�P>����$	�!���E�+��]�k��o��v��������"������j��)+�9�^��o9.&c"%����p���^��O�eE:?�H*9���|C:�����H�?����#DX��������Y����L'�xX�uz�������DI�K���}e>[g?�7P����&�?�#�fp����;(��./3"D#PABT�������:�6����3���Wh��s��e��g���~��M$����8s�������������E4��a'������gc@F��]�F�Y�>���#���������H����y�FF���)���WE'yR����|>?i������<���
cn���\��o0.Z���b�*����r��b%�
�i��"�y&,�Y��5Gi���������%u��Z�����N�%�������7O�Sr<�J��f~��������B1qq�V.�	B�����k��O����Ops�Wj+����;�������s���z�`�����^5���`�	x��ww�dE���������!����g��Z�H���N�`w��v��^��
�g
{e���Y����nZ�8�n�z�����N�������h�m�����[h�k��M�Oi&Cp9��-���������n,6����x'���(����Q��U��j�e3�������Vyg�����b�`������)�FDk��G�j�!t&�$a��a%��T��k����7�������o�dd�p�����6�q�m>d�Y*tm6C���c����6��+������a����8��Y��=���m�TA��n���zNX����=\O�����
�����n����a��
��J��j�;�.aV�R�V,��|�J�<CY_n�Z����I9:60����<<�S�,�R��l3��J�GM�;s'�2�J�c���r`��u���!��8�G�"Ys[��xzz��o��M���?�C��la>�t���9���S�����alP��C-��q��������rQ�-Tl�s/�?��H'���T5�f����Y�?;�m���6d��@u4V����AG��^�J��is;��C�BVq��h�5�,�i�4��ck����H�[��H�{�S��T���O�(O=�8��(��}����J������	e��ir	
��A5F,*�Yw�"�IOB%U 
��iR*>�-�1*�6������l���n�������{���;Xw���)�;�YX<p%������p�����-V�]8p�0ewwEs�=�H��+�]�_7
�����
�[.a]��?:�����a�x��~���P\�S����P�_���4}$v���tVCU��
@��z�����������,�����*��E4��Aw��>���g�Gyk�fu������
����G��1��k��\��V&:NH��4��o���IqV�~�a��X�FU�s�no#U���-�.��?�}s���C������c��c<���#g��eK���QD����/���@�hdC�4��K����]X�-�r���,�|�b��\t��U��JFM���jo����n;�,���g
��d���Z��Ml�n{�O��=���m,��;�P@0���6�F������>��?��a6��*�w�@�sO��|��_�.���6�����A��
t3���v'���R�M�C\�T\5��E?�[���<�����.��hb6������r�):�;�|���_v��R�5���z���Z�#��"�8;`�	6n�Dc��N��B��-��yA;Y��:��)
.R
�l:����%J�m��@�;/����P
��G?�)�h��5c�J�n��oL@L���5Y��-vo��l&l!����1���kx]�u�mN��~c����@������v�5�����L|�%v$_�h�6�A���/8��|~������oN;+�5��=�b�D��}�"�,-���G0K�R��N��V�����p���c�U�FK5$��VN�����z�.1����Y������
���^���3��w1��k�9�	�^��KdZ^��T�r���8|�AcAM����M���r�p��������=�S���*���_��!����a����1\�"<O��q�^y!_=���i����V�����m��<�ctY�s�!���`+OQ}j���E�-D��j!e��B�1��D�����8���� u��������m>�3~����eI����)���B�;���]��?��������A�*K{��������,v� �����S<(�,��?)���sX(�����[����h)Bh)L�\G���g�����^�<-3��WL��]o������I�Ue�T!���mt���:�����*�����?#�9/w��D�� j�z���^�vJ��v�������y�k;������7MGUJv�����6�b�	�R�5�����8�f�;)n����N�\�w������g���B�����!g$�G�\$�?Lg�S��x���9�1�t� �\PL���ryN�`b�g�_��j"���qV�����C�������Hu����(��F���.3����$����|��%����}�����5���^��L	���
�3��z�;�� �F��nJ�������i
L���4���n{��J/�*s�`�,�N
�A������rz|R=W)B�U+������������$w��a����������L���HvR�����ku,����9}9��N.�����7�O�AN��o�Be	����K����7�vR;����R�	���m�D1�*������f������X:q(��cL A����sj(�R,����Q?�>��3�@�d��]�����)[{�\���+�J����(���E��NuD����0��\��%���w�]%��>#ZM8���GN���M��{�N��K����s|*��5�Q�-� +B����%�_+�����I��;/��)�~�����C��^�����e	�r�G�5d3:��(�i��Q�����*Zvk���m�����W>������SQ����v�v7��/�2�gwp��A���a?�f�����A�p��P}�0�PR3��Tc�teN]����CL�U�Y�gu�NjN���1{������*$��~
:[V2><>���������EG�
��`|>�5P50B��)tYE4Q��N�*#3�b��nn�5�~��H���h���\W�����q��y��	��9��AV5f�KFU{x����+��A�Y5�LP�o�p������ D�/�q������?�{��(�ms:cP�pM�����)�ml@��H��Am���j/��B���wJ)(��C�_����-:�~�0d��L��7�����)�� ������7rL�eNI������=t]��t~L1�']kN~/u�v��J{���|�P.�m���Oz;���^�@i�d)'���>�=0e�Z��/0��
���s���3-i�!U��z��
��o�J5f�m��������(��iQ��Y>\���=����)k�������E�o�����{���8;���j��1�J|	�%h��<���6�^���������������� ����9�s���8��\=��GjQoT�vNN\��]��_�`���SQn�w��6����f+�����I-���K]�W��s��\�T�h��������������*a�m02��������m�fb�������b4���[�������K�������������^�*u��;m:��n��m�,���wX,��������R���.u����z���-�����<='ll��W5L8GH;��:v�� ��v�|/<`��������_;�L	��%N`N*;�]j�^~�X�7J6W,��)�7��d���-�jl
�*����t+Z���u�0�pY@#���0��G�r�ha�IB��	/�(v`7[e�����������n����"��"P�� ��	b5����#�l}<�:�s�������9��?+��U��\c_��;b[X�<��vU����>�2�vCi��(�������s�_��j�>���M'�����������{�P�W;m��4��Zd!���RC���B�0")hu�__���
�L�����5��fNF
�V�^s��.�8�{(E���$�i��#�*A����Tb"z'�x���~pC(�H����f�x����r�)�=%�R�;�Z�q��m���@���j���(hR;a;��f�����������>�M_�4�����R
wZ��}�F���S(��w��^�����X�V����]�����qR�0�3n����f������ f��nC����_[;�J>�.�2�MS���k�+Cmq���UL<�
����~}+��9�-��z�I�um�w/�'���c[���B���WO�j�$��'��UI�ts
�F��N�����6��W&�<+�V�����:V6��h"R�`���J��w��uCr-tK:q��R.������B�4��lXnH��RI���Uh�V>_����e��J�����d7
����!k_�6����k(a�B�S:E�EO��	7�2�a�)J	p��n���_�[ �`�&aYF����a
SE��kB�}��V��Z���r:I)�y��������g���EZ��T��1�"� ;6������[mk�Fal��s�[.4��\}���
�y��c�AQG�z���mF�Y����y�6�BY���k����p;W>����_��}�6��d�K\�Q��5�����!��
W�T.O����`������X�����52D���A!�&���!�cL�*��Or�m5/o�^�x���haK��|�\�l������!4'�W���^�����w���U*�Z�o'��2�%��%���|���t`M�I�/!2i��uBI$
�I�����bNg�������1e���O�q8���He�����K�(��_1=#~g_���r1c��)�jc�r��Ph�����B>�j��c��LCn!
]�-'|)r��"s���Z�8��:������i��A�����Y�	��1�,��xk���gj�S�j�4�)k]
�8�����=vZ����ZI8���8b�bo|u���7���#e{q��+����%�w��[���lonoH�f���M��Z;��U���������d�-w��Ra���,�[�v{g�Uj��hU����~����m/�������'�2�[���{���f��
��5Z�lon�n�|_{_��y���/�y�9��c�~����k����|g����f{?(G,�nW�L��~����]b]��o���m�9a�\�D��w��_)�J�:�__��dc���-k������e~�����g��&E�61�IQp������`��|��N������g����w�������-�����PF�����sXpHQcT���`T(s�<�B��
c��a�rX�r�_�jN�&���f@R>M8I�*N����Z�v���:?�Ibu@b3���@S���w#�7X���YRs�$���GH�m�6AU_
�O��O�7�N�mJ��:G%$h�X��aMI�����sh��Q�Pk�A�CS)�j-��N_"tg�:���+�C������8��y����1���VZ_4��x��w[��Is&9%�TPO�
�Q�uPd�~&��!��fM�����������H-<����bP��,��a�.��-� ��?n�$�"�k��!<�uh�%�o�9��� �����h�7��9�F���,��?y�m?�/�}�"�z=�5��;��{��D<D��'?-���v_�9����q��#��t�@�	��p�����������F��2v��M�zQ0C)cNH��/�Qh���$wl��`�3RDBG,#�z���_N��Cg�d�\
eq�>���=���+Ka��pw|q�E�T|@��`s�����xFN3�&�K��r�(�)!Q6�N�P41��J"�?^��r�d`+�
$������{�x��*r=����S�������Az��CY�����4�]��0��S�uE\�z&+�l�kVO������/b��y�s�gY���#0T��4/�C�h����g���?�3��sUQ���X�*H+��Z�������.�JV1:U,�:�4���RM��%�i9�V>ou�V�i�
�	�O	-%A%�������+�K�Zl�~����`G]o�_vz ������!g���[�y���J�I:}��nrO~��H�����N(T��Bb���M���b)c��� ��`��.��w(�U��� b�����bw?C��V����jr ��g}�2���P9mP�D�m4��;��x��x�Vt�� �5�����d�iP[�al��:���>D!��J6L�*��&�
�E���Z��N��b�j!=����A�
�6�(��0��V�|��=.��42��	����B�?e��: 5a
[op�GC� ~l�sp�_(�7d�����2#&z�6�x�H�����lw�u��d�����
�]��@~C�,)d���D��f�8
�t�_x�m���������r"�����V�m�o>��aV�yVt;��A����Nc��A�x6��G>b�5�/�^l����y����4�p�\�)E���i���9u�@;A$�T����u��3�00�Wo����y�3�0(�zl\��{�UfCkf��F
�h����c��������c#�oB�\/�W��_���1m���C����3%i�=U���uAZ\5�cf�����k:��p��+V\�B�]�V���,y�A�G���8������F��d,[�#u����=k�n)��c��a�vZ/���Zt\�m+������\���c���d`!K�l�<���V�%�)�����?��aP%XX��<ak
vf�����M����N�y�2YXFA�_$�&����h��hw3�8}XU!��-Fpc��
od�b9QAz���v����3B����'Y����"�����#���b���v�_���^��y�Qq�.��P���b��(���"�A��:�Z����UQe����F��e���CA��h{!��6�I�����5t���%�lG��0r����k��I?+�
<z<2�	�Lf:����
��F=�dvs�vk���uo`�G@C����/�nG����^9qtV=?�6�qr��QI��,�����o4�mA���J%^��E��G.�3�
�	&�c��+?Dk������X���
�Hs�0��;�A`LJ���:���4R
�yR��$�:���Ia'��������w3�*�nl�$08
M8#/�FGl�����
������V����FZPt��(x���e��D����mkK>�6�@}V���(�,D��d k	�f-LV
Z���QS���6��kL��b*���"1��k2�����5��Y
��;��a�H�Kb���,�S�lb�'���t��C���~�EO��6��V]=Be����'aC`��K0�r~��"��K#��B!��7%&�%JFJLV�,&����K<��|`*���U	Y3��S������\�=Qy�X�'N�����K`��*���~*3�X���Ka��)@g�+�\�,H�9�T�j�<� a�G��ZV.$�fj�k���6�b�5YB�Wx�-��F
�nun�nF���
Y�URT���T>�v*�D��7X�/L?U)
&!G
� �]������G���\ig~tk���R�9��V�����LI[�G�v��`T�oyEM���������}<U1�qN�I�)���G�=NRK-��.��Ea��B����4V����ojx5�����RIU����z\���N�����8��	��l��5����%.�_'�sc����_K�;;�&��ha%J�D�S ����������%��4w(��'�(ZV�`^���l��I+��5L���`����&~|/�4C�����~���][��s�c�����_&��7����S ����2�B=4��j]\�o�N����g��g���7'g�xIzk���i���F)YZ������y�(::$��^��66t�����}�z��~����%��F��v��R9?����������	���gg��������X7��z5��^{����?����TE/���R_�JZ}�R��}��^���G�*��>c��U�B�!Tc]}x�8�85V��Y�����
�c����	���g�U�G	�������*<���|Yx_UY4����xC���U��
��V'�*�O����������x0u��dn��k.����e78P��1��)��V��#S9���Y�n�F�^������<�Ra'/��Ke�^&�>W��b�wG'}���B�a�r��=b�zy�CVE�xu-Y�G =4��H	�0�����Gs�`Y(���g=!-��%�v�^.K�!��~���tYY�Kl��Z�2I�!����q4b�+\B�w� ,�������m��&����c9�_y�Q�k�Qz%.@B��7�������8��H����k�������>�)3~�b���A�a�:q�Hc����w&�AL�w�GS�\��`�H�V��_��:���9h�=&g���	�LA!�v����/S�p���0���u������o�*�.j�S]�vY��2��,���������r�8�UN�����j�<����V��R��Z�����[-������[���^�@���p��&���5����m��� `�>���A�q����6�����S����k�s��5����������`����+^�|�g���A�0a ��)����'���_<�h���c��eb�0>j??Fct~�uj�?��k��s�����K�M4��=2��K9k7!&-�[���k��}Xov%'Qg
��	��P��o��34_8�mD� ��h/�0"��w����Y��}�mM�E���[i������,�V:o����
j�\sY����K��P�c����?#�k7���������	�C��QDe7c3������z�UO+��b�O7�bg'��b^����L�$:���|	#��>x�7�Be"��xY����TA�U���C��v)����/�(�z�����Q�����ql|6���0����l�BL�����DX��:���t2w����F�����@��?K�"��iw[����5���:�w��Nh�@��;-�����x��JN$<L�K���r�:�����>��8`���Q�} ���� ito��p�%Y'�U���g� Z���	%p�%h�Z�������?�9c�����^���8�q2R4pAM�;]^'Ef�
�������6���C����;�����a�	c����G �/q��c�o��8�J��DyL!&�����T�r�����Ocp�)�d��>��W��������Y�'E���BZ=�@���0��J�s����MJh���$�
��B�#�������c^(�TL��zJ�C>�mg�m;�[t�"$Z8obOssf]F]Y�1r�x�:��G"���<
�W�dD91�u�L���g���������$liP�fRws��3�9�r���%c�+j$}M'5/b{����(n��e�����rX3T~
.�)����ic��Gb���$���I�$���lI�9�,A��3�#��P���(.DiQ/j8�fF�5�������IaY�Z��M�ua�<�M������5u
��NC����#�rQ�1��d)`�<jo0�)��!�u:�(��� .�:W��#�Y���7��!(���
�A�#�y����Q��J���:9��vB#?��n[�Jh�X����*E`��ElaZ��x�@uM(
�6d�x��F�Z0CxW��5����"�j<1��E����2��4��e��d�3���9m�����3�6�\���L�����h,T\u��c�$�>p9���:��=��L��W����r��b����vB��<��5�xj����Ak��m�-��������m����f�Xd�9>B��D����2�JH�+p%@�P���x��@E,��X�����*��d���x6a�dz�0d������)ye��Y
��0X��L�6�o�����@��:�,��#���x�A����Q{�H��[zr�Tj���(�X�����������.��������n��f�#�X���o�p->�Y�K�/EI(K�4]��H��ZEU=�0�'U�01SLU��<1nbX�
�ZV��b�i������	�-�0���$	�����VQ����*��/�@��A��,��q~�]�y*ZV�u�y����GH��Z��>-�}N��fq���B��6N��c��T�`��R����<�_�q�����T���W�U�A%��k�
����h�sa���%P����k�h��^�7�1|��w���
��]���P����c������}��B� ���)3�vvs��r
����e~RK�W�D^FM���Q���80.��o��>R2+^+A#���.�q1���"�����L���&��(~&����
S�����42u2���q)�p�Kw�D������[[�F�R,���6��]�$d�A������]�[@�6[��I���RL�g	u����I9��Q�>'52����E�Z������������3e����W�GG�������ZW!Q��d�$s��$��f��1���g'��,����Ko�j;��@��1;�	��b�q�CG�.���[���G2Y�j[j�uU�hA�sA"A�>{D��F����IMX������J���R�����%(�����?��
0aAF*��hX`�4�.���H�(�I#!Un�H�wc���$O�bW��f����
��y%2i�P��?�	���a�d���IJk��2�M��0>�)��'}��H���Y�������	E�B��!���1d
�v�	�#�r����yW2�Rl��A�c��w(~���F�9.U�w�2�S^�����= �G��X��|��I�|��!��4��Gqu�@���i�WJ��.!�b:��Xx�n]�xF�S��'���b��Q�'���[`}h���GtP\;=���&��'�N!<��ke�ADrQT[��%�Ms'0E�}Uh�?��CP�X�����Y����gt����L�}�w�I�@{�;;9��K�z��2�@��B�s�p��q�(�R^�<����9�cm��U�7!��,=v$��*�'\$B�C<\>DQ�U�X��J��)�)�2%�j����X(��m� �7���P�X����c�f��m���#�_�k��}�(�?�j��f�����=N&���D��b���xS��c&���u�\x�x���9L��|�t�798��.�[s�"
�d��#S��~(� q�N��aa,$��w��5`�.4v����W�����p�Xc�Ss�������(��\�*,kYB����3����n�iod#�&�&:��BeL]S��}Toqy''\t��48"����������QLL1���prtSZ�zOx��	me��<�e�fxn���w�W?��Hg2�s2,;�����#�W(�J��G /��*
}��P&�U�9O����"]5��iO�J��lL�X0{����o	���7�T`�U8�d������4L������8����LU�g��t�����K���>�1�;6�q����iO#x	�����&������38)+���7y��s1M]��M�1�|��'S&=�;R��:J�4e)�"���rFS�s�yAiV��SZsMg��w�����H=f�����lt�n�zZt�Q�L����%�t�L0���p�^�n���C9v���E�jvN�*�>T�+��s������?�.��H�1�*����P��?/C�;K
�����l�EM�	<�����(.�9���c~N������dF��D�@Ty8q%�Z�a��lR�q��	?���>#yU8{��|"92���7�CZz� ��T�Su}&_�.:D�����\l9A����PRd�����n:W��_�nW���{1���9�/����V����Q��*~yW=��u��AVO�1<����s��.P�7I��N��kv�?��qGAC"�Z�T+u*h�'�M�e���o���zU��+.f�NC����F �?��eaq�V�
P���Mlh6���P�>�0�M/7g�8�S2J$�_sf
"����q}��F"����'�Y�ya�<��MO�?��A���&,�m7�W�Z����
T�&o�O������������)9u�IZ�����R���I�qF@��Cr�2s��V���4��p�m{(�{�w�l��r`�|�/[���Sah�E�9�Ot���IQl�f��T9'$�iM�9�mt�Q�I:�M�o�LM]�G�n�VT]6���Z8������3f4��Az%s
�o��
��H[��c��Iap�7���?�����'F9u���f�b�I�kGK�~���f_�I�13z�@A#�PB(��Tp�R�A��)]��w�c[R�
�h�y�[
�4��=t]�%<�&?@Z�D���M"R���Hu��7�����K>����X�����z�Q9=N��{�l�x�OX��R#����tY	���S����j*�s	%g7\S6���}�����
�'�����$g>�A�"��N�\Z��T�b$��9u)��-=T)���/�. ����W.h�dH:2���
�	�WU����B��.������7tCF���!9�S^�k�|�4��B����ZN�C5P���.��7	\.:��uD��6!@^�8��d_�v��z�!(��+�rR���Z�9��Bl���K>�ehn>7-������a�1�a�j&?Fj���4�V<���yc`�1%��������1jM#D�T@3���QRO�����W�iWG�e�1����$�N��}STl��02\8Us�����Z�g��KN���e�9�/'5�����x�:�)���v��d�6�^b����$�G�=�[�A�j"��4L"$�����W�������w��1)"w���U
���z
[��b����:��K\TO�Gu*Ga7�-c�D4*3G�6sJ	�8*>���.����A���������������CJ���>�q6�����-����p�� ���]�{^�_���N�*��d�E�N��)�;����m15�m����=����8��w�j+;���Z��-obr�TWx��-%a����������5Q��C���\[�n�+��(�����v^�nO�h��o:]�L��K��N�D�Bd6�����S�Q�m#�7�?`&�����n`��tk�&�����FFM�{7y4a����.���������mE���@� ��l��5}�o�WtP�7�g�Q���{79��.��=l��QxM��h��=�����lW=�&�x��|	{�[���<�1��AvLN���b���&��ZX�e�)3n/���G
�)�4���N�gu�HA�
v�d0JYpr��=;Jl����E	�L��1`6@1�����1�-����S��[bC��+)Okz�ro� �~�L!�c��$><�]4cxtlI�~<����Y��`PZ?�~������������J�3y�p1�yq��8<
������zj�L>�q�lp�P8�Gf�&m�L�Q����77�}�3Uad�&�l`e�!�[C-���.~��h�X��'r��(B�zJ�7�;��ny��d��n� S����(�AP\��A1�������|�i����]OMF���T��)%X�������pT���>MQ��
�dg�*KE���/Yf����8��jJJ�����0Et�����U�n\R��J%G1����HHRn��'%6P<+���"�*��X:��z��U�AJq�6m�(J����,����KA*�b��B����K�h\6	�%f���U"�X�\��'��v�A�m-�QZ�C!�I:o�<blL�Vl"lq�����_H<������g�m*�X���0�����R&2�u6s��S��F�md��i&���s=�>N�)R�[i�F���:.�\1��h"�� �,5�����������Q���n}�|�k�,������g6������7�*��HN�s������`C�\%����e�������a�0Sm1
=CX�7Q��i��h�4��C�M ���P���/���w�>��v��28%e�H$��
Q. ��@^h� ��l������/_m��*�N�$�g�Hrw��M�:J�{�OT����4�%sFM��8;	��F����f
�m3yW�'���x�����tR#���+T�o�����
UES��[ggb�\����~��HXQ�hKw�$3��1���<K��i�K��L�'�$��������Sr�r��l>%&E��tn�{	�������iY�T}y}�o��0��i�t�>DD��>�Hu�����	(�;����N<#��R�'`������A��
�Do��;��m����;^��}a�77�\���}����zK�/����H���e	DQ��.���%O�>pd��"�,z���E��O,JY�U���Y����X����

�pI��,z����!(��z�kA.��LBy~q�������o�1��Q_F�������{``�N�i�}����GS���B_�^���1�B1�PM���k����n9���4�!�
@��
���I�������)��e2�hq<r�z���Qy`��}������i�����iuQD�������@��@>���3��W�"��**`���W,�_x4�T�;1��n;�#���������,W�c%���#MZ���z�-��?q%*�L\�x�uVB��W6���$����S����1��E^�p6�;rk�z�H���1�I"��{�#�H�a��E@�
����5��>�9���f��g�)�K�.qA�����_�hL�����n��q��z9��O�$�[��P�C;(��I�����6���d�!4ZD�"�I���:�U������MIDG�QJf�,Y6��1�`_J&��CN�f���@� #�z���� ����`Gf�j���:gc��9����R��@���H<���f'P���p�����f����?g���Gn��
!�\Z��������0���8�.���(�a�u
�/�;(��`?�v"�7/�!����L�e{�������L��z4T�`l��]$�������m��~|G~3��N��q�$�RX.��+
e	d:����15=u�S�Qt�����_j%%2���opCT77y�B���L�������De�-�M�X�6���������������Wv�v8��g�7o#��b�a�c$dg+���9Ykj���[#�b^��B$��o����	�M��B
�nH=��x
Q{No0����+�����L{���z` X�edq���ng^Kw��{�����I�F
V������/@h��������'�].4����i��zT99b��EFn���wuSY����M"���*�� ,r����!�$�P1 	����^#(��:�D
���C�eF#���F�>@�)�� �|��:�aF�7�.�k�F&9��,4h�r�Fa����Z[_V�,��jU�#K���vqg�N��ny]�j��������?�����-�P
Da��a�O��~`����H%�Q^U_���k)'��<��2�5
f���{�jF��P��H�)���+���� ���K�a�����mhh�@)���x���V����g
��&�`��[��h��M5�Sc�6i�h'��Zo�f������c$��i�?��)a$�O�Br|��S�o����tR1�P,����0�DH�Z�0�Q'��S���6�U���\Oy-r�9��~�D��i���@����Am�g6peD�Nr����\!@�)�F]������o!kb�#�@96I�Y� ��7DJ���A.IK�� 
)���Y��m�X%l����N������(���8-��p�n{7X�.:���6z�� Y�"���7Cj5��m������R�KW����P�b�|����!,�1���2Y����c��%*Cl�JU	N7AKm>����2����+�+#7Rd�T���Zo|��\Vg���
U�2�P2���
�����L!]�L�#'%-,�[X�9��C�����rV��A=#b?��S�H�t�l����\�@`�����vmN����v������L7��ze���`��O���)�O���I�E�������T�uX������R��,-��sV	]���l����~O2��������	�l�.�������O� '
0'y0�',�Gn��K��[���B�R�B�'���M���PP(M<�E\4�����I�6$SAs��Z������o8����T�<	|��������|r�����2]�<�3�/OL�f}�^]9Cs��<;eZ�R��;8��<��"����2��m"�1�h���L=%���>�w�S<����H���W��$���&������0Zd����*����>��+��u�4Xd2	�2�q�h&�q�RnZ��6����M��8D��\d���;;���d����I�r����aC��������]�~����$�T=@�������[�k�h4�B��Z���1]^jG�����&i�|<8�V{��Z���l�;�)*��C�k��9	W���:$iB�_��H�$���;S���>��'�m��������.Gyl��Ea����b%h+����������{���F��?��
G����JBU��<����L��.�}/&��YlbX�de������fM?��\e_�o�\�g����p������
F�DA����k9��Y���.Ng�8$��Fr��cf�N�j��.D���;������.rFY���8��cjW	�1H^���0�z�ixc��Dd�!����j���������/g�K!'5�����'?!DH��7o.FP�p_~� �g�D����~�����%��H�L`lx[=�WAd��if�+=�[BC�i�cl�n���#:�c 8v�Nt�OJ�����Z5�}|��{a�0�x���@u��R���a��72��?���6�lQ���=1��m\��-E����_���t,���=�����.,����An������B��KS�GD���*M �
�	���b��������_V���)����E���B�P���
e�4.��7@f���v����1���o����VJ����Y�-�����)���W*Z��X�B�X�����_
���^�/���$�_n�W�v?������������[��go�rZ��^��nZ���)��:v�.X;��}���g �=Q(�P�`�c3��n�l����]�/G�;�����g��|���^?6~(�����@F��bAX�����U[��Ba�bLgo���*��w�Pzn��EL��C$���b����A#����^���X�&{���@���:��`����q@vm���A]��.��o���u�E�7E��@3x�y�b~������J���MI_k�?������>�K���z���ooo��z��A*����I=����nA��C��t�����$���%(MAL�������B+o9��g���<:Eyf;��N����W������9������"�k��w�|��X�)�Z��]a����������,=����K�9~P�a�q,�����a��pn���x�K�A��D����zz\==��qzv��rr������7�������-|�)�����V��}���t8��(hv����u�����&b+��P��T��X_�{L-Jn��)sI�A������y���^��y�0��w@sj����^�)�:������<�;h��9��3'��+�����/]���
]���'�uY�yL�qQ=Bg���\���0|�D����|����=l��&=����2���������b%>�_���G�lM�A� �����N����7Y��)����_���[��G�_�X.5�N���Wn��Ngw����-��e;�����na�9U��
`$N�+<�|��������� ��?
��W;9Q,Qh��W�����&��i�/@K!�e>�(������v��8��
���P�Z��c������J@�^��2r�uR�T9Y7�����cr���� ~����fv�Q����
{vW|�0g(���v�m����t�M���<8E��Fcc����`;�UT�$H���d{����F���rP�\g��4p�U[O[%��Io8���F�#u�0T�PR�����
�����>����cK�c�Nz�>6"�Hx���%=_�o�n:�s�/���p�"�������CNi�����b�4qq�dG�6�+��u0L��^����[�O����bw�/��4�@U��w����"���klG��%KQ��t���K���O�����VBJXV�i��v������V�� �'�� �'�B�j�B���SS%`�5
d�����5�]��t)��N=��,a$�u��I�9�h{���F�$�Y��m���6!�}���+J�3D"�������}4�!z��=�M�)q��b�7&

����Xv<�y��91�]wtk�%��_�45�7(AQ���6����pn�F0Ir���]5�NE'��P0�^� �s����=T�e[�nZC��Qz>�)}[�����������9��;-���+Lz�1��F�y�)5�u�)�v��'�����+~�~A�:0<����qv�m���E�bw?��;^"W�;T�MF��5�J�#��uDr������2�`�$Mm�����ZG��?�����[t�C����7�H�t��,��3Ra�h�9�pb�0	��4�hH���\iY�U���?i^V��V���)�0�=��N_*~�l-�	��������`d>�������P��}�j�5�����h�
���oy"?Nk'���z�HF���9"Go��\�,�0����A8H����q�C�w��Gg����j�~y^m����>�����:���=�*�!���C�i
�0>3�f�g��b'��>�1��F@��a�g�.���O�����$S�)_��)Tn��+��I�k�2Z�5�$���C���>��y�A��pp�Z���n�n��2���1��o����>����^�L>$I���o��` �������z�h0l|v)T������N�ec�^9[��0�j3	=�������,`��F�E��$�J�7��ZO`�5��������\"�C�9J4�fWJ*�H�_�}O&�d�H=�@�B�������<�D���l:������BJ��}u�#�"�+�1�!�2�'Q���:�<k<E����f��n��zJ�#t� A�����g���}���z����������5�U#%�?�#p���w�1=5�`V�w�_���/ {g�Hy7L$����s7�j6e�FFg@Hmxt��h3�19nY�S�N��g�����P��s���6V
@��W�{}1"8`Ou
�#�g2�S �U�����
i����N�kd�@�pDQ[Q������=J�Y�j���LNy��p��o*���H:�.RK��������*����vn+�/��B���,�����zO��t=���~+P��I7����q��n�*��o-I�C�Prz��m"��j<D��+�����w�{R�����_j2#�,q�pD�n��IN�j���U1��f�r��SZ1��7g��o�����y�0x�Sl���"����Mu5�a�`����&^�
�����Jn,���Q�����5/$���8�S;=���u�iDr��E�N �0_`���+i3L�-k��l.�����N�����X;����"�����J%����G��$�(N�b����9�������!�l�����j)�l�h�
C|��i��*����� b��(3Q[����&��Kg�F��\��1�����Q�sW5�6���`?�)Ab��.ZL,kg7W.<8����,��+k�tU�.�?Z*n��7 �l��j��^d����4�P�q�k�
^_r����K�;�v?#�/����D����$�������O������]��QPz����S2��#�+�jC1���������!=�=c�Z��d���-]�FS�+c����]�*�����U_���R��T�W�{�OX��,]���$�>����W��WAJJ��Bsd?Z]*�������0�������n�MA��@����p�.�������17v�g&1�/A���`Wra�T�@��7+sH�?g��Y,k��������3�y���W�1�p�(���G��F6����.��uZ�����
">�B���)�x$��|������hYwB&��x�Fq�L�S�La�����k����R�����[����[�O���`2w@�H�����Z�"�;��;HK���������C�#%)����yGa��/"�2�t���\^����\��Z������:7NF��B;���i%K<��99Y"�	y<6a�a���%:F���}>H�o���G�������u���	����5�5H���){p-y�`Y����u_8x��;�\�B�r���ap��QMQ��iVd;,�����@�$��~>>�z�~�v:2�}.���@��@������z��JRI�6PaD>K���/	5~F���lC�X
k+��K�aj�2��s6�p�ah��j5 yd��q��+��=�)�i��*�o�HN15�`fr�n��������8XK�,�.���Bd��"�]�:0���������fb\��h;Q�D��	b��!`����,�f�]D��u��I$�J���Dl7����H3����������
��:��oM���&��^�����Z2k�P�#���^�*���f��1]��18��&hH
���Im��
�B�`�i����	?88ro����2��l�LBc�>�a�������g�����y��d8r?�1���!yN���
�\��(�vZ9���"�9��b6�P�xm���)�Ls��f�Y��4��(�������A�������W(��3�h
Mp�
�=@���'k����zN�EG."N��������7��G�R9{A0�y�tKy}+�x�|!	���L���*3����>�.g[��8���u��B���*A�9/�:��uVo�"���-�.�S�YE�8�����v������k��{&N�M��O�]�j�5:|1|F�D�]E��`?:�w�����S���/��(  �t`�����d���%��$�'�N9^HO~�P��?��5�������9��K�'���R�5�|IY��K������x�%h����}B��������S��9��vpS��>��N��=L8T��V�|J����a����u�JX��;��?��nM���ja���<�o�Q��>OZN�b}*G�./�>wG�-�$�y�
��|(�2#JA�q^_bE��)�(5D�w�������A��)�.��ot)���~�:@j��+�~��h�PR�]Q���v�j�H"g_�I��9������;���N�V��qS�RR�
�B9:K3[@1��4 7��l�BY��4i�>��������6�}�g�D�O�Fhb�����i�V$���6����6��l���;��'�3�[��2Y�Upq�������+���<���������Cclq�d����K%�\���eQl�H��f]����s�U�V�\�����^XY�0OO]ZM��`��V�6��[`�����J��w�'3!��Qs��/�n����xd��q2��;�5�<�I�1��x�A����rJ�����b��2�=k�@"S1:���L��^F_\��d�2��r�e�'�_\���~G���6�
9���f<@^I���Q�� C0�o���e:�&f������t�va��+��1Q�m��p�}�������6���"���`�x����	�+7�;&��7��1����J�W.Z��(�G������{���?�^�v���F~�ZfL��'Gg�'$�����4x�>�%=N@i=� g�7e�5��-7�PB�q����;}	E(3�o=�+FoP{�,����Y���������vT�7�*x?��E����~���r(��IE�b�������xZ���e-��v�q��71VG[��v�i��H�~W�3��2�gdz��g����h7��5�t��.��}f����Lc<4U�������CS���["�^��J�"��L	���E�eh���j�����^�9i���{������~+���zHgK2�!��3UD���k4%y��ZP����#c�kkr4��c�����J�G��.e�|��10�z�eO���sA���:�R����Z�J�J=�&���Xe�Gtq�"x �|vv���d�n\ �>-����
�Z�?�D���:;-=����������N���=���;��9^�#	epj�X�hA�����'fDU��K���i�B�Fl����yZy_�|{'���0J�?��$��lP>�s4�"%�4xq�t�������3�|�'A���6��WG����Y����E�T��Y@H��
���0���px������'�Fmt��15!+]�+���D0�(���~�sz�-��
H���O�SW�i�Q�]����5rLW����^��?�:I��$l�Nv���c�Y��Z�`n�xN��h�p�"��$W�/�2�TI� �$��V�c��M�Y���&2\ea�5�FY���������.O�1EAd\���WOd�	�|���H��l�qFY��6���EQ�!"���=�k&�Sv��ml��1������B���&G�����F�/�e�Q�Ru�8�QEzB�47]!��������������`a^��vP�����7��Y�����0(�3I�X��f��5i1�x�uL/1%���1�(��)��������C6��e`[�lH�*"(�g��\z�������dm$�z�&9�a�:�M��HC��73A��!�,f�����",�&���FK6G��W���|�	2^��
�y,��l��������Uy�L6z����l�9�zh!g@��i�zM���6i�V�aNqFo��0�}���t!in�tG��8uSo���4��n,��"��BD�2�0����v��4p��v��H�<U�����tbQ�!:v����6|h��f-���Z3�y���=��X��/�U����L��L�~C��IZ�����R�NC�[�b�|2���@8���O����(���z��!Y}6;E(	��I���"p;�����er��*���T] ��/�h�e'�M�t��+���uJ4��r>���t�(yiHU�^�e�>kX�N9�����L�!�n����4s�S�BNy���C��L�V9��v��4�ZS'z�5��P����[�j���}�����Vo��m`2A\]�G����8����gq7�x3I�rj6�PY.�;���_t��bs�����\Nh(!�x�!���d����1rN����&0�1x����*��6_MG��2���S _�|1��DWB���*�����3�����)m����w�R� ��Hx���Er��M�9�e-^��2����d�����Z���;rP�;�z<�������	�|�������mo�������/V�lYV�����U�������]�X�����[��J���V������=g��s�t�������{_����:*xX�9,���b����RZ�����+� j�������z]u����_"��!�eJ�����02�J�a
=
"��L�l�4���C���N����w���h�c�_0m�Tog���z]��I���
�$�;�r<O[s�K������m���B>�������Z��3&4��nB:��vw��>9U���y������vqg���R'C�����������h���F/^>7F��K8�|>������I�{����/�Q����kK�����^�y�!��`2z���%6�
iY�7tC�%'���N�Mj�{�{��|*^��"�;�|N_�0x��g����$����l}�M�_L<����	�-+����6@8RjH�
:,��RD��g���@�6���yI>y���� ��e�)��1�^��o{M��:r�
L�����.��������,Lk���a�������n|A��b��m��j�0��.��b��B�	�$�\��w*�E��~��/�������S%t��f��YP������[b��br�L�m���5�~���f.�V��g�_ ��t��.�k�n��\���8��ygj��gt������Rn���t ����pC��t)qG(����%�������l�[��=�>��=[-������{�\�x;�����M
���:�i9��������Ds�
����[���N�S����Rg�X,w�����x��J���2q��]
����%8Bw����������K�����Z�WQ����*'����x�?E�
�HK��DU����F��-�"�?�������	�^\���k>�,^\�3����DV�KU�����
���+��jq���7^�����4����V'2���G����#U~��i-��0�Z��S<'�p�<����.�.|U�+�#��A��rCO������*y�b��<4�Bc4�@��G�\p�C[&[
��{<�z��H�M1U���0�?@P|��d�q��Mi+4�.����E���^rC�r8������ 2��c���4��K��nNv[XT�hX6�R%c�������D|�!�Z������5��yt@�{�b/A����b#t���|�����xj�b�����$/������^�$�~��@�+��
���;27��n Jyl���V���p�����������Z�t��x{~v�A���J����6u@S�d���w���
�
{�d�2i\@�����j�.��H����;�_�PG�a;2d�����g����h�"]��5�m$P��DxW�nW��7(I#��F��b
��I �����&S��
����4�2o�bL
e���QrU�4�|�M���n�Tr-�7��tjiL{��� �H�6]�@��mz��W.!z�~��#�/�� L�m��+�7	�&u���t�i�+k	��P�gIF�p��V�g��}9zW��h���@�1��8������`�(a�]�i��4p�4l�	'�7����>����M�g�X�U3���ty(R�H���FO����{^!O-2������F���6\A�EPsT�{�mF� ��r('��O��x
��8;���me���N��r�����f[�G_6�y�~�|������;�<;��&�Qd'�oM^����8Kq8kV��1���������>����A(����O����PiI$ ����S��nw{HW��(�����v�r�0�� �����}�(��P�y�d��-9;1"�D/?Q�%��^8���������p����dB��{������w���%3�����i�&|��H��0�)���*s���,�,\
J�3����F
�uI����V4��[\��������i���)S���"OS4��"����//���y�`���N����'�(��0���v�������]7	z�Jn�����r��?�(�(���* ����,[�<�fG�qh/.;a����?G�
�cj���]�lL�\<N9�h8L'�&�����"��B����%��$[���k�rMi+�i]�h���O���9��,���@J�c�+a��� H{��5��r�X4�8��)�yy ��8il��s����=�
+�G��j{�����?�,���%^��M��r�n�����o�r�����k����l����v��hO�si�mu��H�R�($Y�����4�"�����BPH=J�H_�QLqi��}�q!�����aN����(��e4Bs�vG��t���
9��L@�D��c�/�3d]|��8��4'~�����|�`N�����4��7o.����#��&t�"�Q�������@�D��el�Em��F�I����GM�}�m%��e9��N2qs��/O����@
���������^�h��I�S�z6n6/�tx9S�%\H����]�Ct�&.��{���*�ZLP*DT���������
��~9�Z�Y������^������I�=�h�Gd1C�P�����C3�Oq��f����NxvY��|R}x���l$��sv|�n-�,eyO�+t4�0V4t"�l{N�x����4��%�q�X*w��9��.Dq��(@8	z	�����\���q�0>>P~A����" ��d7��n�Ue[o@y�)RR��b�$;�@S�����j���b_�q/v`$��,�t�<���#�+^J�������z�N�m� �R����nk���l��D��"j��������7���@i/�0��W$��d��)�	N��kH��R����_.����vk���)v���tNi9��sJ-Yx@�����D.}�.9:;=�<?�~�[d�$x��!�{_�]$��-��^�.���a��vm�mB�Zg]�p���
���R@'��-Od<t��o?+��Z��8� �f�>t{�w�J6�������.�t�'��U�]F�
x���|z6q� e���X���Mc��M�R5v���}!���N.u�Q���S�����%@�KvI$�7��&Qc��t�c�%�{Hh�<����e@F���
`L�<
=���$��Ew�~#)��o��v�c��;����*�Z�$
��D)���q�J�;t]���x�6Kk��g���s4���o�mi�T���7�2h(���s�i�,��zI_��~�AC�������X�R�K
�����ieUr|=)�	eC.�G������a�����z������>�%���U��6���4����G�E2���AB�����������l�W�*��
2l�����i�cC�[�������2@&�-��]�bnh�6������(e��b�4�]R�U%$���p%������h�0���A0���raH0���Y�$�q7�����&��N��s�i����!����_����`z6F��>����x+H�.i�0QN�l��;��-o�&?\��Q/'gl8��\,9�(�����v)���JuIw�:��
���;#g(J��B���R�����h8�1��5oa2ar#G�mj�#���R�u?;q}k���C
dx����y�d��!�$a��!���}{����:��!�Y?�1��0L��I�<[P�>A��xzg& qhI��^_T��l���������)�\��f�8��s�7����Y� ���q��K���tz�k��B�+�i�I��L!b[��-��E���/�D	L9�k���a5��g�����H/=S5�`V46&����Ro�Xx	��%�K�	q��n)�1�>����_�?1�O|9G�R���^;�
<���
��U�8�����/�����i:�WO�	�;b9!&k�x��t)4�b������	������)Q�T7�Q�vzQ=�c�3��<~�2���wlfB���J\J�(�h�j4	;(���qc
f����q���n;}m��ZA���Ih�_��T
q���$af��Lh����)#J
,��>�u��YX����P�)�D�2�������
���(90uV1!�J�Lt��sPK%?�1���^�
-M�2���E���	�K�DT��1���F���A]=��mG�sF������G����{��	U�tNf���bE������:����&������*tv���l1}e{:$0%P�aXd2>UdI�����H��!������c�$ z7a[��?��L�����hfzNG"@���>��1�b3������,<�#�=�D�lp�t���l���B���y�T����ir�F�CR�?�U��qg��v����=���<yr"U��M;R-�\
����As���A�j�C������r���%+<�V0���oJ�~P�~D!pP=;���[Pa�%fAK��T���N��?�)B�"S(���Ry.�s����@�C<"���B�mx�j���� |�J��m��G �IA�%�
� G�6��0*�1a����<�d ��,��q�G.`��HK�&=
KLz��Z����mC~�?���J���>m�D����
1�����Yj��aG0Fd!nFaRH���.�3[���Q�]U����T��--1*D����������I&��u0��Y,8~���ke!XD�{<�]����.��X��!���>K��D���g�xOEYjtn\o��U\����b�e	�`��	g.�W*��� �#���pR:��VZ�rnZ�b�qAo�+$�r�����������
s�@��7�P����VX�X(!�-�����%IX�yx�,I�.m
Cpw:N,�/�i�"��q���t�POFH2Dw���
k�f�S��A=������c���g����}�l�db��Y�i�
F�F�$�+H}w�Ni��
.��D9�#Q~L�%yw��7aA��W'�-�i��L�%�pQ������}�,���z�T���b]�wi�������'����3|���������,S���
I���2uK)II����%2a�K�7C6Q����0�#"
�TWj$az�T��k�8�N�.�/"���I��s��6�`�H
���&�T�I]���!��aX7FY>��8p�S�h�J
^�������J���B�lJ�&j
�S
���8�`����0����R��%��%�TcA.i.!����t����5���0'G��WV?xV���4}(��M�#.�hC��A�~����-�`Z��,��b�ty�����Em�����;�n�
���=t}D��)��Y�i����t��U��D���	�.a�J���I�J������g�z����z	_yL����6��F����TC�"wx����du��*��fMV�D��#�3���~�t���B����[`�������`<2F>G��j�v�����7|:C����L��P�4�.+F��3hD��A���S��d�st#YC��	9=� �{Qc�	�:����
+%��B�5D���j�T?_�V�����3�����FQ?�o�R*���o��/xk5��c�9A����)W�@���!-)RZIC��>�OV$r�U7�t.L����]F���u���H��-��}�U���9��4���V���8�H���H�-��-Ks��l�o�&�-���^��l���0�����'����i�0�	t�K��_�����_�!����
[���1v�A�B�H������������D������$m���%��`s(��Fc�o�@Zd�Q�����|�����A�������2Z.��BI����
�#!����q���r-*�l|Lh��*'�������r���}1	������\,��+�W>_Y����'l��,X�����{��\�~�R��P�G�1�����$�$vT8=��A��<)�@P�����e�G�q�sBc$z�E��0��F+����2�G�S���7��W�m�vWR}*��u���J�UJ�����-Y:2$
#M�$�j>�096���������zx~�J�^q'r�4��Uz@rAf�t����TF���1E#JS�j�ij��':�cc��A�5BW��5bI;;�9���|6-������������<�B5����a�,ISW+1q�c�O�:il%�'����H=�o�m�����x��s�D�F��� �2�D�������B���s�LJ2?{��`1��t�����
W7	�����6�cMZ�d���	�S��iQ���y���)�h�U^<�
{�K__�]�H+���a�����<���!���NY�������=1�J#s�<=��tJ�����?�|������f���<���>�����������G���{��3�t=Q����'U�sC<��>�QwD^�Mr����k!�����q�������D��8S;�M?�{�1�}�=��_��������3��,	���T=�xM+':��y<�>�%29U4ZN�j�9��8���]���md�&e������;oc�M�5t��c&���MZ]��\�L}�T�>sC���O�fD�^W��<��O���iLh��B"�'�	�}���l�����I/���1�SC�&�2T�X�B$(0�54���w��~6���`i���'�B�����#3�$Wt;���g�|
�wG.GG�*jn�O�|�����������}��-���:���k�?�'y�Z�:����@������Qk��r������(�����Y����j��K��q^�]��Z�F~��(5�������>m'.�����h�YPdC&o�#�����Ab�I����� J�N�y�=Xa��	�1-�������6�W��L��@��;���*��G0�1���U��q�.1�X��3�`������)��J�"���CA��00F!�����a�������-��q
������O�����L�W~��xA�mD���&���+�+��/=����Vw��7�����
P���2{��"F.;�{�h�����j�X����^5N"�������8i�_9�R���G4��-����0�p\�1��
�FG�:<A��D��#s#���x�5F��~b������������
}������F�.)�2���Z,j��*����S_zM�XJ����@�p�R���������rd�H�mK�f2��Pj��/:F9X&�K
n�JJ�q����@
�M��x�0�����<��@��eb����)(XTNL����y��������6n�����j���8GK��R����/N���>��������^B�5�Yt��������-%������:�,���>�BJ�S��$�t������m��&d���X�T��h(LF�	ok]�2��~����%"��2U�p~3��~�j����{���zR5_�joo��b���[��Y�}����LA����b�y�P��������K(�R���P<O����D����i���O�f�ZV�/V�P(��v����B�X�-�E�5i�o��!�r;���������N�� %�)t������
����k���}��n����Vk���vJ-���������!�'��������
`$N�+5{�������������4x�~t���	�$*�!T/��!�W*���A��~1n�������J���(�?�J���"@K����|��,��Q
J��$�q��s@��.��a@UY�V���:/��8��6��n�X��)��a���I�.�\K���Wi�������:;��yl��/#��A9w�<���=��=�0����\{����^V���W^�������0n�h�8�&������|�"��h���=��%��Ip4R�	?�
�^;HK{]B��:W�Z1p���#k���AK�P���e}���,��o�����v�v�!�K����:�z�%�.����e������z�c�N�=�V�B_N
,���j��E)��I���6)��>O���5�D\����:����,f�V@�P�;
�� ����������h4��?w�m���k�^aB�1n���b���)����IINsoX��F N��}j8���f71�������������]�j\]�=��O���������<����,m���@������I)�,������?h�(:.�+��s��������lesb���~og�����
�9��L��w��Z��v>����4[��V+�x{ZK��{Z)L��_�,�_N��N��������iG��{}k��y�5P�t�r/�E�T��~����}�����(���3w�Y �~?�2��������'Pf�����Z;�g�|���o���:��d�4�t�T��#������|'�����G�%(���T�nzuN�9��C�{����;b��e���w�J��n������~��O����O���c�Z��� �`t�E�����v��?���8��*�SE��������g|����ib�������7��7�-�06�K!�=C��h�$`�+�0.',xCQ��&���A�+�w���1*���t������^u����}��o����~�R��U�~uZ�:r����X�\`��x6�`�����`6>�����_30x������a�5�#������6y�`��+��`.t&?Nk'���R7��j��H����*
�C2��|Y.N`m�y@����r��"�s�[��M���Z�7�0�	Z^���9>5����o���\���a*Hp�~C�|��\]w���V���(�~|����M�2�p�������j�M�R�<�6N����������lNW@i���l�\��g���K�7�Dk6�YB�-�O��!�x%`j�@�rr����!��@b���v�:�m��
~�8x�KP��%(rVq9k ��
���p(���WB��?���z�����\CCC���zEl�x����'� $�(���5��5�l�,>1Vl)����
��LZ9E-�j"'h����-aeUc�$z-��b��;���G���7��
��h��e�7�`C�H�$�<U@���#�m,p|A_Z�BH��}*�4��Dye��s#�<�9m?�[o�����F������g��?�[[���y���z1^Mk��]:��&�A���&�r����K�{w�|K�}�����%m���`��A_�A����8�4�$[0��C�1(b�<'�G���ut��1������%c�n,�m@x���(�C��~J"[/@dG���H�h�����Kb�*��_���*����l�u�qMH2��J
m�F�����X4��8�"���uit��k(����{H/�m���zI�s�==1v�1�5B����&@��������}mQj�{��i���=lgI�����@�6�l��H�q��B�����F��]����Lt8�3�)�Y�Z3����1����RJ��V��d���r��x�O�sb��O�'�K ���)�Q��&�,:��7���<����������1H��O�(��d��E���^=o`����#7����F���\��������P-�{�������� ���bD�E)������>�b�I����Lw@���V�g����1���,)E	�p��:h�����i�����i���F��!M�|#�d�=��F��by�.��tf!�����������c|B�3���������%�u��R�LxY.r�����-~��������x�l��,�t���
���a�t�Rm��;����{5~����5�������m��2���Z�L���(E�|�)mq�����mm���B����p�\�D��)��]O�k��'��7b��I���[4Ca�����M$��J�J �)+��C���7�~
������E�r�k(n8q@,�`��Nj)����AcX"�k?�%�B7������	������A�T��"���u��	�*���������9��b�14���p�2�K!� a���^P��n�7�n@�C�D��A���1�,*�x6��5�A���]���8���vbv#�f�n�	�$�����G�����j�����	n�1�. OM����W(�����}��.7w��3��
M84
Y�an
�����X��=K�i���q��^iTO�O+��X��9m��N����n[���{U�����S�Q =�o���*]T8���u��z�����B���>�9�q�%Q����H�_�q��zToT�����|X;M||z&$3�LX��/�!&�N�8qd���X)o���K"<�1�A�A|�&>�T���w�V�K���Qk��H����#6�@�?��U�#]�F�PKZ�%�u0�(���<�^r�!}��>��o���7����,���l�g���U�3A��'�U�v�Q�*#��<�8�\����
����$�{��A�G���<�I��^��[U2:|�����V~v�&$'n��`���>��KE��:d`�^����%��]���������cx�����z��x�/%�u.��_�>��wk�67�1���M'
�@�.@�E1��c�	������=��������#�ky��!�R*����%+g�W�s��z�K�b���9�T?y3�3~���N|T����!����BgW�7����h��O�y�-����[���
 -+�#��q���d�	�!�a�Q��U3yv�vy�(8$G�������J���2���AH�x�Oo���"���p�h�P�#&�z�hC2��Z��h�22^�H.��W��E5��W��cm(4��+��m1������Fu>���?@Nt��)��!}.�����BDN$<��%mX'�`�`K���IxNuh��?M����
���G����j���8	��y����z1:����B�(x�P�-�Dd�)��yj��@+
�p�s����`[^	��'�]o
�EA�F��=�
_@u2�x%;��?'.FGJ�cs��IkO���x��ot��k�1����#@�*�JD��������a�~8?r��C�Lm������4m���@E
�_����a4$�i�i�\-�r��8x���cf[f��Dp0i]��v�`�OT���BQ����4D<�W���%�O$�a:�f,�|	�#'�%����E��8 ��?���h�\��~n�%�����
H�e�s����z4��u{v\����K��_fj�*���5�\��������z���Q9{��Ml"��� >#���I:�n��z�\���H�[l'@O��d�{Ew�-P��G�d���;�p�Y��V��,>&y�4Y
S�Fel,���tp��}s�:��;����
���a�kk�]*	(��,&�`T��@�6�S��L���Hy�GR��s.��;�N��+���#|�I������qQ=�<��k�W/����z��q��z^��L������0=����#�K��i�U�t@��r����"m$�B��8n��^\��
���zN�FI���
H�����-R���"�)FW-8B
|*�u������S;I��"�K���h(TP�<�@��x"J�A!gTR���Z�.�����Z��a�hm���R�Y5���g��M��
���;D���"�����Gz�I��sP� (vwr���v��}p��<
��$���b��:�y�O�^qz���q4Rg�Z5��9y�ED�����:�d�g`}o���!^��R����\��`���BO�4*�QrJ���/f�$|r?��hU`5t�o���R��FI�)�����Q�mM�^r#B���Bz�O�:!L����3������ �<��x$#��Tk$-��H��]��<�<�iZ^}njj�,V�r"��GFq�M�V��,t���\x�����P%�..k�#�����}�h�9(��,b�	�&��mK)z��}H�}�������t.��*M����
� $`�@���w�mO�F.iVO��=
C��T5�!�R�@�������	�p�7����I��.F�g�S�G��
���&G]��a��n���}�dF��=��:�d~s����n�}�����%��q,*���O~�H/_���@����iQ� pJ�%�A�`H~O�@��UTNN�#Rr�
!!�*��3��e�t�p&��������������YQ��U �ASr6������
��b�n$��n��a�������Wa���8��,��H��~PE'��d
�x�^N
�����1^]��Z���'�m�$5^�H���x�� ��7��I��i4���u��|���7�t\���!��5��%3�`�\(E>��i'�u:h��e�m�2���R�
f7�~@w�b�TSX���5��`"����X���f'9�O���`,HM�|	!���{>s�-�v
;F�!
E|iB��l4���WN4���@�P&���bG�������]���C��T��)*u��_��l�$�~��H�^`�X
��gE�+��D7 x�]"���gY��?��Y���jg�Y�����Z���X��������N~/H7�-y2�f���q�0k�Xd"���_�K�^�d�PWVP��?'pZ��u�s��S�1�����;����At?��]3�z+�a�S�<���
�����n�}%���Y1@��V���:I��G���`
�DS�\�����"$O�?�b����CL�~��DJ����/�#
�^�f��o��N��:��sox4����G.^c�Z����$��"d#�D���)�sy2%*j6-��9YdSf(c�u���gApH�-����L������B�6�R
���+#�EU��G��8e�#^t$d��n)����1��- ��%�,i�8�
 '�{<�g�`8�Q��O<�3���%�|���iTd���P�[���rc�����:�\`i�93����� "�6�o�o�Y�����HF�9C�g��o�d8���1�Yf���\Dc��pA`
yY�!?"���+a�[S�����w�9��12it�c+�B����+)
�*Zo1�bh(D��/�m_��$5��{Tf@��y���lK����_3Bw=o���l������J�Li��=�eE�*gj!\�l����V��Hc���H��H/Q����K�
��%S��5�Y�$�q��N���W�.�j���h��`T<�eH}O�#�9yU@���R���X�����H�An%E�wJ�H������#�G-��NC���4���KF�N�
��*��������DM��cR��or�gw$������*���������KD�Y�c�`�5����TF��p�(S� PT�@�L�Y6F���l�:a�n����]���@B����4�2]�q��|����v�_cl��V��Su���emPT)4�i���H��+wbtk�0����������lU]_d)<Z���y��&�iu<�=��"�@��m$V&Q���T(�<�	D~-�~3���*p@]�t�X��TpG�X�t�"�_�I��,�I
mI�r��\6�}���NV�G��d@4(�F!
�������?4�Un��1����g��,w���CA�)���C��U{�`����1��^���
]�y35:��u��YD5��������k��
�TG�4�d1�J��r�9H��8�������Z�����q�Tb~.��_�����\b���-k�;�7xZ#���	<)�D����g�����^��h<�YCO��o�&��@�c��3l�i%�Y����:������M�r���U�:�>��9O��wNt�_cB���b�o-N��0AgSlI�|*�E|,ei���h��
�g|���)v*VC�*�C���UV���*�_X�O����zr����'��Tu���g�6�j/��Oj	X���}���l��/ 
/��e��u��OFx�C�o����j�4+��X4�&��qG(}2��mt{h�T���H(kf�#�j���C
�����������2�;���X;=���
���
'_8]�J����1F�r�����G<1!�������"m��w�'��8k�:�gh>K�����u�}M�$�sZ �X�y�M�n��
�K!���NGhMw^������������qqq�d�T�a��l����C�6��FP����@"K����uJj$H�����o��4?a/��"��
���3m�4�����u��g]�v��8I,��7�����d}9��7�	Z��rz���f#�	��I�:�����F���^�����X�_)G�9eS��!���.�R6�� � a"C
�JO��pV^���sp����qI\9TBi���lf��]C�}&����������a{
�pe-��yV�����X�����p�Z�����W������:�;�)	��<�bL�����p������E���?�%]�i@�0J���o�F��8�$K[@b�2
�!UDsiR���}������������"�;W+�����_C�=
��Q{A��;Y��c��_�V,�$)V~�N�pz�����{m�	��;'���s~ZQ�
�a-�k�-����ZLD�bB�YpB���<"�	
���{Q;djt��$��;W������_��t���@�}�_\���;����1#Uka�L���_������"D���v6��+��I:��2Uq�
��3?Z_#| ����V�"Q_6��br��6�&
�Qos���M�������:�-)��R�o����O�6}r���U���V�P6Juy}���6�$���^��	P��fY������R��/��2N���t��q��hwxCjN����>S��#�n^��s��5��A����7C
V���X��K��a�1
d}������N��n�|�_�~��xR�����=U�	>������xap%��&\������zh�/X��>������Ys�G$�FJ��B��J��z=�0r
�Ra�|49K��p��p�I�����oC���m|�����E|�59:$�E����C^xr��lO��g����� ����k�@�C�rF�������^��H������KJ'U������#�N��� �GF������'�����e�HejH�����daf��F�r��*����lR��1�������md�2M��Y������OjG�z���z�6���k�����;������y�,�d�	b�� Q,`Z�W����u����1���%����z�a�2�p�"��3��h?����a�3F��-����d2�)��9_���$7|���6�g�k�=�h�:4���QQ��}����U�H��I$�J����p�@C`\��������\gXQ���������Y��$g���s[���?��/��\�lL2���@J46,>�������I'M)���<y-��8��H6�T��g!�����dS��?�C�:�e�t�_���?A"�uE�^��Z�`>���\��=;�5l�����03���N3z"��V����%�
DQ��
�l�a�@AT�������j���U�{@�������\�$������G-��*������2��za"�����e`��T�E���b6\��uF� �������o�0 m��.|���=�"�
�����aI+Z�AZ���9���N_��!)r2Tz��`LF����\?�t�Ea'���8;�Yx��$5+7��{�Fs�v�
�?#g�><�4���Z�3���jF�_�i��L����NRcL�E]�c��{�g���������1�@0$#0A��4�����'N@�0ecPX,�����
3����~=��7��%�]5]������\	�.Y���"N��������(�H)&S��\�(����:���K�juS�����q;����Z�nO��9���	yy$��3��A�G	
�s
X���3P��r���G[���|���P���}�jy��4�{Y��>BVg���?�H��#���]����I
��=vT���0�� ���P���I"�B9z�`�������y
�'7Y����"��b����I�96&�89�u�����"�/^��/��"���(�U��*+�<qE�3}��eN�f4��@�O|��'n��6��v�d[������S�p�����0�9�T(����&�t�I��:Ga�:�;�U��W,.P2�����)���=����
r��s�=�+����H����N�K��e���lNp~����R����a�w�����8�Z�h�*������3%k�47������bs��KB�� �W(eJ�H�>����'����"%�*v��r��t'_�*���e,>���P>2\Z��S's"�'@H�&�]al:
o&�u+��7��au9_��dH�������U��_�M
�;:����c��*�D�����Y������Je&��M�����������m:b/���^i�u���s�x�v~���Q�6/��n����MO/;���+��4��HZ��^F�6~{#v�!e������3������u
�6a��7F�E6|1�Zj������C+�| d�t���Ox������td�6z$���Iz+�Rk������^���K�;x���~T��Q���H�5��(9�$�7��t+�����Cr	�<�^� �Y���a��q@�i�f���{7�����������H�Hh)��,k�R��NI)�`��^�#�����Z����������K��r+�`�}r�:	���?W�&z��j��O�5��P�7Vs��)���-S� �>���t0,Qq�X�4{wU
Ks����K�����o��*o:4��I������Wen�+'���}���
�:U�S:�Op7��,�$��x��������Y@��(��)����.@��������7��3��Br�?Bo3b-�He���A	�z �Inf�I������PX^�)>s�:c�}�S��D����
��sB$)'L��[���rz��x��n� a <�(Y�eVt�E8���k��~!�o|�%�����Rg����Q������z�'A/��K�%xC�	4!/�y_`�st������0L��������P
���7�@��6�\�}�8!�y!\�R�y���>�����-��-�'����fO0��K>k`Dl��

C� ��|a��l�|V1��B�HL=�3���j2��}\}@�O����y�zPr}$/A�J���Cd������o���r!k��-������g��<l�
�)T~�5a�����&���j�<���#���Sc���|~L�A��i'�\��|>gS$pK���kqMo���d�IB����ooO#9����S�*���BX�.;�K���,������A0H�E��l�����[�fz�r69�J���e�Z]U]�+m������C��p��=[�=�u��]��4kpg�����w�����aAb^o��F�����43`a���Z��/�<�V�������f���h�\3�9����[���j���������0���X����0�=H�L��
�EI=bT?�/X�Xm���B[�=@�A��e��i8�B��{*���[�k��w���8��iK��S&��=$������{�s�S���_�}��d���LOP���'tp+�S�s���N��{L��������O.n��E��W��)���Pp���s�����w���j������ ��W��4q���K���J�+����7���faXA�Z�\��1
���L���������@��v���pzC>����^Xs�;���m!�9�m>!�FV��z�����0���1��
�v.m1�vM�7'�����J��}T�,��}���>�M4L�7I����o#�����T�1�����k)����L.��:���s�Nj�>�M�.CG�M��~�*����%�a��{r���/��q��pL��gkK���+����������!�tg�k W�;;O��������V}��F-�;�z+|����D�Z��K�ub�f���o����;��;��U�����~���������A�7�@�J��n~o���-K��C+Z�u����G�{������`��{�����;<�l��v����zlA�y�Pmo?�����5�����H�w��
���>��L�q��{q��'?��=S�9H�;;@�{
�U����������>��^k��������P��O+�'�/gY*\��Y�Z���V�B�
������]k*�}�����&������\k*�������2��7�]cD����F��kX;�@���$�0���0tF4�2����k�UM�):�A����$���C���L�������F�����"T�^C�����Q|�@
)w:�q�������������l,%���_��F�o�'���}��#m���I��x�P"�"��/%���q�vn�Y�������a0���$�Kf�����0[���5k��O4��4$�3��,��������G���@���,�Gw��Df��"7�����kh���,'ly���"�����`To��F���9��P1c�&���5]��=:s��J��t}fU#2����IXA�� x%m���H�A��o]����U��~?��w��P^k�{m��=��P�Z������P�^m���W�ke����Y}�>����`�k�xu�bL�?�4.�����i��e��������?�E��y�6j����'��������Al��<����R�������$���M_J�k��s_�)�rW*�k����zhU�oU����y8�,����������s_�e_�;������!���r���GGCqA���SjP��@�R�	��vMv�y*��O'W��GV���H����,�k�{?7���k�����<*�M�������}$�HY����d5�Iqs��v��QfR��X[#!ik�j8S���7���<k�_���>8�v��r��x�x�����*�Z�^\�}k��Wz'��Rd��}r[XS���S��4F��M�z=��T�{�S�4u��q7�f�e�����e*��9��i2bY}V(V+|rGU�BG��mO��h&^�QR�<�
w_�:�5~�|�=~����s� *P�h��O��R��i�'�pLI��OA�`�6�f���zCh���n	�A.M���;U$�&6D�LWF�Po���)�&|�r1z+���c���`M�
����(����'�*����ztM�Th�2 �F��f�*H������7#��c�g��>_/L��T����+�\�[����C��������hg��������n��vz��0TSv�r��)����)�UR{P�-mW��4��Lsg��~�N]�I�\1�g1?�p|
��,���l��w����Y�M�,g��#L^���d^�%�xoTO�D�B/g64����I4�f���}���*��lx3v�������o����DpfB���+����2��v�������C��*�`���R�uyC�`���Lo���$U�sO��������{��������p�<E�X�������8=��8����]�����������%��D��i���5r�������R��]<V���q�q�&sBh��E���H&6�5���I2B���(�59�>�P�����$��Y?�0.�^c�N����-���oo$�cQ�W~7.BP{����|���g�[#�����`h7��%F�u_O�� -n�QL[!�����pv������h3��jw����:�w��~���%�g����+5���u��gM��}�>�X�;�>������&c5�a�N��	�2\p��x�Do�p��N��s'���rW�Y�q����-0t��,�{�-��sz�1� Y��S���y|w_�'��hv�M�����C�Fm�@���K���MRM�uK=�^���7��^i��
=-}�*k��K^����;CW�1Fc�U6n9������m������������K�F�����
���7�J���B����dF�����w'����\"��!�@�
"o&tl4s��@E�������.z����1^����\���y��I���yU���5k��F��zwq�h�k��R-�o������F��]Fg��E������*����w\c��u��r�h�P(��v��Y���n����f|Z,*]A����V��xJ��b�����w�bz
���U����'�'h
���f����lo��6���@.�b�1��	iQ����Ht�F9y��}3F��j��t�\�E�vQm����)��`�1���Lj���������V��E�=���&�*Z�Q�k�d�B<M���`}�7��wJ�C�x���M�d@e�t(�R	W�w��Z��d����~���E��\�����HQ����b�{���X���?e��[���1������I4������R]��S��9�z[�E0Q����B��)�p�8a�@�G,HzA��y��� ����B I�\]�<�r|�L"�7��%���8��dh��
Y���q���_����6���{��=O��E"9D�;���4�4�/�	���)��P*������F+�<-�y,P���z�8���]R��K�x���M9�����2�_��{*v��0�����%�T�0K�vaJ]'9<j9`��%$F
>�b|��M�VO���$��9P��>��V��2� ��'����1�T<�
I��GN9��y�q\��)b���2�:��>i��U\�X�������U�W
�*YxA����G�������U��LB
�.
X�/�\{b'�G�J�.8�����X
;vl=��`��-�Z�/�bHX\A3B���vr.[���b����.,[[�F$D�b�=_K�������t]z�f���	��+�eT~Aj�����@���_����E�xL����#"W�<�B�����C"�����6�`vxRu�~�uR�C�n�'Ng���E��_�9als��VC
v����(��N�������Z�-��5��_�����5�NI=d��<fc�����+T�����U*��|��x�z�a����x�B�����v���r��4�j`��k
�@�����R\�v���Y�g�|I�!Y�B�,���<(`�����������.���`WA<|����z�F�+�+���o&���������9��f
6�"}���Z���qiWm��+��YA�����N#�}t�2>!��`f���*Ko��P�{�I0���,|/kR�	��k/:����L�6T�7�L��S|�p��Ik��Q7I�(<i��d�����)�Q�'���_R�m�l���GJT�������,�jl<?=��������2�������Y��:��bJ���w�>������������ZGKu)����e��p]=�&Q`>A�p���-���![�����2LZ�e|�>d���~^�0)�e^�����w�Xq!�;��5�hK�9�7�vhk��y�\g2nI��>�{(�.=#r��H�(���I&��G��t��}!C�L�?/�����q_���A<����
@����)K��
D��LK�|�,�Q�B����M*�6`,��Xi����Y>��
J��T����h�l�fA�=L���;^�>��Me.�YP����~n&~���C���+�2�'�����)����+�]C�Z�<E<�� '�
�������jN^iAd@�Fi���G�p6��!y��w���}d��SB��������H�HZM:�1��E���v~�s��Vu�Kk��$�t����L8ac��������?-w����Q��&�3�D����Z'�+JH��	1|��n���r������y�\������-#)�8��N�Y'��fEg��a.��������y_b�������<���Vv��_woa�����sf�ktA������������������)�������K��o?F�BrD��z���	��h4OkM����9 #8�f����d~�'�iD-��������O�^�j�����n�/������8�Z��q8���@��u����7aq�3t�$�� �����f���Xf��j6���]P<��w/kc��P�h��P3��������O�� �j'����Njm���11}������)�e<���Z_��	kz1�_e����*��K$������j������1��>%S7Dd�����Ta���J���@���s��W�]��N��������&�h�������������vFf��
!��=:(�t�z8f�����Jj�}�aq��aw�U���^�����lo�m��5�Z{^Uk��g����*z���/x�JWT��g�j���Ez�����CW��;E������I�����U�G�l��O�)V:`��Z�����aR��!���u��W��}���)�u�W}���G�Zo���Y�����w��~r�o}�����n��K��9~4�Q%��}����o7� �Q����S�}�2��3Z�}G��z��_l/����K��Qk��D�N_����Z��^����*��XaP�w��`���������I�.S#�9���\� �3��Q������<ss��e�2kJ?D]���%d���
��ZZ������;��@�&��V�H�������G���z��AB�MTc�}{�d����2�3o$�u.&�A]n���/��[�	|d���'�K/�4�SO�*X����e����a�v����N�����vn$��`�	�8v�@k��t���������E7K���s�����jW��3\�}k����K�5�<�y�?B�8�Q��j
�'�E��*,��Ul�O��Y��59{��=���M����� �7���|~��h��^������'�$�y�xh�2��vQ���WB��##R��:�YpM�����b��e�=N_�c��C�d��=�h��_n<8L53�x��Dwvd8�_2\�Xr�[����i&��3M�f&�S'����,���>�� 4��8�z������C������}o���3AR�	���p���/�{������#8�V��������k�0�
��q��'���
B\�P3�K������z��������qk� ���p��9%����s.��l�5�$s��;��9i��	�8v��T�~���.��)$~���g�i�U��YA�k?���%b�@��[�lH�Yn�Q*
��>eOgs[�����z���i��Q'���9��������f��D���G���Xho������&/�i�Pn%>TO=U���0p�\�������-���m���U����N��i5�9.GO5��>���&�(��8���i���U�<��*�/�_e���R��HTm�^����	���3�+e9�1U��:+UFX=�TmC�q��`�5���Tb<!c�I@���>T�P-;_�����+HEI'���J	m}my�M�����c���A����m�/^��uB~��){����muB	�t����0�&�lg�/�3���9Z��V����x9�����������~�9E\c\q��x���V�u����������p�-��+�����_��|�-���?�4$;O��%���Xg���ALw� ��������������s4{�c��?�HVZyv�h�Oj.v�(�	V!��f�~�Y���o��u���b���zl�j�������0����l'��Vg�.�Tk��(��AR����������!{�{<�_�.��>��A;"�����,�:3YF|Ye�DFY�d�%��}m8� ��"ah�
����h(�;�@I�"�X����+�S�g�>��;�I��2���PjH}��
�e�ANL��;H��{y S�-�3�N����3m��[�.=q����1��<��p�{��4	eS[���D,k!���Y�@:?N�U&�ZA:���k-+����M��s���TT'k�rx�l���V�6�>@��U
T������!GH�ha��O������<�;e�l�^WZiceu���&c���3
���92�����
��$��qX����a�\�4�+y8��e��^�z�����]����`��q0:�/E@��>E����(M�G<S��������F��7�.;p�d'y ���e���$���W��pY���kL�>����xA�;�c��P�#�k5�����j�)��Sl����56"��63�ey����r�xCU�����y���P�*�G�"g�j�9V������ ���(HN�G^E�fdU�����J-�P��8������]N�P#���>�H�";����l8<�������N�xB���?�;E�ID�Z7"n��
������/)��XAJ��em:p���`�0
��_�������Zu�"p��Y�d�9Z�[��=������j��'y�������T�Yl;V�^�FB�S������d�����Bc**�%��2k����-M�)r��p��8�s���>���,Mz�6�3
o�B;4� �:�X|�����m}�"��S+��vgY]��Zt�l�H�)BF���6�x�_TC��2����tH"�/]�-�5w�b��;Y�|p<�v/v���������w����[�hs7�
�A6��A���_����x��v���`���~v$��Qr(�|��������R@$'��L����o���b�@��N�����Y��&��.���h7�I����n�?L(.�w�D��X��L�����D�����G�G}�f1���-I�R��To�8��9�C��@��M�$��L�
L5��A�m�����?7��������:s�I�z]������KM�+V���j/�"��5������[dy���@�;5miC����''3gu9�`��\����������X�����[�Dc�������h�b���C8g�E��������YS�N�q���~x��{d��B��6W8<�[8�b��*6\I�Z���sG[��{�A�fC�Y~A��F����*�DHX���>�)�2�����v�{Q��/�{������`�������D���D���R��l������Y�C����j���r@��5,��~eqnl����O�/
�q��:�~7�H(����$��f iF�	
m�b|�@t�Lj^t�����qwt�?��o��#/4����8�f�$���JB�b��Xg��"2�B��C�	e�?h\���#�l�����$N��l�Sa�-0%np�Lx$��_����O�I�L
/��_8v�_���9j\.!Mb�91�>��f�M0�U4"���d����;��z�C~o��7���
�������nrM���~�#h���k��(�tq�0�+Pe������IC������R�Q?uB�����c��
���Db�SJ����>[�N����KCy����[��i�T��[@�d���F����F}f�)��"y�Z%�M��~���9����!��o[�U���G�Z��Zjp�U*Za��T�q�!]Py%��t�O���<�&����u`�u��16��o���8�Y^�"�Mz5
zp1�w\���v�����pclp*��W�riy3��
���H���&Y�v����f^��l�q�E1~��.�W=��,��X0�����c/c�x�1L?��0���|B�1�	�-4�_ZdrC��]}����:pu��������@���!
�Lo����#b���(�������Q�u��qv�������QH�4]t$���o��g�=
�&,�TIP�*��
%k)E��W����(7+����
����Kcj�
_=��Wnqt-)6�����A��kf��e�_�0V)���~tm���`����b��z��]�pm|;��c���|������>)�����&�5N�3��
��9
��.�qz4��\����`t��UTL���o�����������
�"�Fi��m���u�n�u��t��I�	@����)q���� <k~���3Oh0X��:L�.d
�Z�	T2��JN�{P�*u��3V*�i�,/�������s"��
{��:@�:�i���;�"t�/7�F#S�q4�#�aLT��|���H;\�>
�.��^
Q�fJ��/�'��N��b���������1~4�Tq��s*K>|��1>�J�sL��v��(C��P.����U�y�B
s��c�#�'{I���u�v��7�#�����-_J��x�9#�ZUir���%����z2�qh.�����2�������$Nf��j�w^d�m`�62��[����d2��)H���e�xSd G�OLv�X��������4,s���%��O�b9Mm�[�����t��B7�������=jl��%��|�����6?�
}5��}�n��<Y]�e8_E/~�=��D����;�
�����W�Y�CU�=���I5F��12�Q'BT���Z�����x2#Luj{�
D��x��5�f����	�>�P�s�D��w�M�������4���Yz���D���2�W�u9������o�
�jC�]��9��D��BY"��T����m������	`$ �zA�����q�@�������*�$�A��y�]{F�kx$A'�agR$+����k;w���"��-�5) t����4z��q�T��>w��u]��b�o����"������r��5TV�d:�����V4D��3q,�;&�t,���n�0|�,���`KG<%[uu�~W�-���x3�wi
�����w#�n��Ay��H�%��?��Mm?M�:0�!��]r�/:�'w�"��S��?�x����G��1�SO��Um���7�P;��`��5�G����~?�~���<���v{�	�P�k�V�����_���	��K���=5�A����Q���R�����R$�apzX4I^�@�'4����o	4M����������R��I���=V�j�eX��b6��b��Q�!aS�f4k�������4��	A�H��a�@$�k.�?���y}�����`�1�lH���m$ks�\�t�b�F_f)$1a��!�;���z���?�}�%{�+��y��}����FZW��O1���uy�DP�f��D�>�`M���
���Du$rS����1�G2�4���G������?�������H����_����Yj���x)F����EO!#J.D��3F���r���9��tT}@,���}��#��W��=�PglVZ?���?u�\oH&��8?y�l���g?S���7�3(D�Pc���������b`��1�GD�B
�E���@�����T��!�"@?]�+��i�x�<�cv�&�\_�pX{6TI�'�&�k��2x
�.��/���!��#��V����y���Q���*��9�HU��s��k�P�{u��Z~�R~5���T+�+�@X��;E���e�Cl�S���e"��I��c�AQ�1����U3"L�t�a4��-"�f!��6+]���G���O���Q���n������xE��O�������$�:������������k��p�uu���y�]����+*|��$�&�
���[�fI"����u�^��@g����k������l�PxG���,rr�a����������Ri�����:��w&�w#]�����8s�jK��oK��xC}��n�7�C�lx����w��@��fO��P.�M���#�g�d��	;�Y*���U�u�N�(�����U��U:! �������`WNaffV#
���w�uA��5��Br�g���g�*����i{C�{!:"G��IO�E�<W+��e��;������B��Z2��b;��T���G�&������;�����]�\��I��I=�=���W��}��<�g,�u����%��"Tb;�\2��N{w=<�a�����j*� ��G&f���wF�7[����p� �7����������V���LMH^9L���=�[z����0����`���^�S�cz�e�bo�^�k����;��N�B����_������-Ol|�p
���K�����p���1�X�����X��?v���[2\�����G�@����p_���=���Az2t����@�w�/z������xg���Mtp�t�$�d^������L�hR.���f���d�n�6�q��Q�=���@�R���~���{2�ef�Qq��I�-Dy����lH�����W'�}�q�i�`$�'����5_5�o98\�q���U��U����UP�����.}�����d�'1�����f�b�!;X�������	\Z�_!j!|�Rd|Y�7�����F�
�E�"�WxJ��q�RA�����U���Z:@��>����xf��KL��t�$�\+������v|^5L����#S�B�!�b�8Fw*=u��VI����)k�+.:G�86B�� 6�`k5�,.)3p���_x���\���y7`
��z^���"��<r�&��NDd���p�S/�1M��������i�����X:z�Y�u��<�}���C3�%�L��4�K����
9R )���LR��C����Ai�B����A�(��&sB���S��kB:����.h"�����{��Q��Q%��5�N��xV�0�#���{��mka�'��P4_�9e��<CM\>Zh(���L#cbd���\���
^���v����ko�j���f�K&	�i;t��m��6��������x��q.��hCX�'x+Tt�N���qK3]�*`���x�[rmm���q�K�Q:�����Je�T�= ��U-���7k���o���|�QrAL�'�h��>����"����q��
j�k��b#A#3��3�����g�"Cc�(�� [F��y_5��v|5��jjT�����@��R\d���~��Q��������}�������i��qs���!z��>���zw>�jm�~�5�3�b�V�"�a��{����C�]&;[M���#F���W<u�t(���"�5X:�q[W	���:�:]f���9��!�D�`3!K���NF���Ol	��qf�Y�`���A����P���;�����h]m�n�Q!l<	O����@@n������o.�!�K��w0���"8��� �W�BHK�^~3|/�Y�7��W�Ru�/��g����X����/FL	���Zy�����N�	
����&6�*w�A���+�p8��AF���J������'��j���]���
H�1�J26�r�Qm���.��b�m�E/������������Wm�
B�l��zS�]���"�;d���#p6F"��n�0Q�im,"'��o���i���7
�eO"�\�1�w����}P������L��$�q�a[6k�*�{��������Pt�JO������2�Z'�~���g@���-�� �+�(�x+���IPOb�P��(�	Zn�$�B�����a�^iqk����m��^���EO4���X�h�����N��/�{i.�e��f�d��2����p9���N�G�#c�p�8�3�pT�W�g8w�#!_b�c��y��rt��0���4�3Z'l��FZ���
t}����qc�/��j�8�Y����3v/u���R��4�Yc`� L�n�e����P��T�ntD�� �<r�{*��wj��.�GtBY2���e�U�I���3��%o�#���W�	���*�_^���D�
���k�J R�d<���dH����?R������gOq�������{>���we.����j�&�aq;I���t�0�,|��x��������d����}=������\?]YP�28]��G�I]U����]Xz��Q�E\����+i?��6V�<�!^��`^�>������2v5�5%`r\a_�\U���5�fa�g���%BN����d(Y�.�7�D�����5a{��{����z�7�P`������4mv��w�I��O����n���	��4{��l$D����I6�m��m���|~�46����H��a�5h;���tD�3 Fj��T���f�L=YW�i�V��5����f��s��/�X�3������i�����Z��I�^�[��`]�@t�bJ�Wt�*9����
W��z2�4�vF���H#��
����w��x���l���t�q���e���8���Ae/-R/�
7�D�r�Bj�3��~�rh}�z�����''rb��7�ub�����D�K�V8�-e�4���ux���d�h%���3���o�����%^9z����c�c]�<���������7@��E�5�1�
2r�t=���A�?w����p��
7(���%���e��������Tw���*��0�}������?�?���'7�6�Y�n����f���Z�k�v�y�s�~��5��(��E� P\��������F��Ju�sIu�.N��U�9Mu�!��������e
4C[���xw7��q
�8K�������H\��i6e���X���D�.`�D���_���kV94��9o@���Q���p&: }T���?�P9g�q;�-�]~Y���p��]�]�Id���9�)=���9��`�e�k�)�S*�����eU57W����f��>�&������z���L����TDc�W�Zk�����>��D���b���	���&��QQ�k��D*;'����u��P�n�j����7F����_>C�iA�0*����c9���� �vG�s��'�����f��j��:���9�gu�q{j�u�;���(5Gw��!��I�@�S��)�m�'_�qX�2��q�0�+�"e����;��B1����������6&�XV,C�1�/��&�:�jL(;^�����1[Yi\|�B=�{����|�����C:����~+���]|�(	@p	 Sm��*:5�V��Me���?U�v��Yo�l����l\��|S����da�0������G�&T�s0��[����F���F#q(��=
��f���&#z��,)�0�����Vxf�=N���'&h��#T)��L>7rKJ�����C�A8��F�(SKN:��\S����)A
7.��L.���X4���\-�2�Sb�/����Jk�����!���b�>Y��������[W1<��Y���i�����\��$R�6
���r��/��3�J��]=�v���s�OAP^�&�{��.���j=�2�������
h�3U~C��`���j��PS��#�����2�3'�%����MD~C��d�G/A�P_3�d�n��\n����FY�l�j��la�#����wD��hn��M�^����}���������`��FZy������R�Z��@!����Ngx{#.#��M5�� ���Z�;���v���u�5-~���=r:m����D�,7s�s)Nf�����hJ��9�O�����u���Y.K�T#����C&l���\�:W|KL��g�q�c���3e2�czwRm��I�/�����z�XE��i�i6~,������G���Qk����*���1q(,�������/�x*����C_��_[HyS��7��<�3�l����G3P�S��Duh����@��q���}����Gb�
��1Cs������h =l����j�@�'��>����Z�Y��VH���/�����,C�j<9�� �4G������v/���\���Y[K��,�N&�s��D�WT(�[�}��bJ�j*����X�@��Y��Q	-$��n�d���%�G\4'��8�ZkSKU�	@�k� #��#�o�lc��
�'�cF���y�k��;���,`�������-6b��y�KT�Y|d=22���M��ug���� '��$2��P��:bl�o���Q���dAL�/
5b@C\��+��q�q���d�\�p� &ro<C��TT$����i��Y�42R��'����+���]/+W��
^h&Y_�z��v���t�`SO
�E����fhw��5j���$�Ldf�}�J%�t7P����W�^,?�S�5�������0�0������f���
���$Bx����P����?52�����"U��&�n8�^=K5C�R����2��8������d����95E/a\Q+��3����3�]h��]S����������$�y�^��xw!3�MgH0M��;$'d�f�IP��V@4��P�h�O�����1�1F8?9�K�4��:�^}�	.��FKP��7���!�|7�^J�����w�!
�g�T?��Q^�M��*����!O@�ywqVk��Z6/���tA�U@�}+.	��M��\�Y����qV�)0�������~�8�`�XN� L��Xd�Dw�Kj���g�4�y~n�,�-s�7���y���v�����i�hw��,G�����0��R�^
^��Hy�Lg��Y����	�"����QV���c��MYv,�����?}�B?����om���:���������]��j�����p.������!��J���4L
F$g>�e��P���=f}�)H�p�*�u�/�,�[���a���! _�d6e^K�`�]�zG��<��r���6��(i5}��72w��kN�i�� ���`�dUr��bk�/~� �Q_���
�H���)�-�2��H��z��cR�tG�f!#L~�E��K�DHf��8��c�B�o��@J�z�O5�����f4~h�nWK�,*�:�B�^��Y�f���>Q�uJ��b�**��o��
������g�Pk����
f�M)%ya������)T������?@��x��������
#7��G��B1�����qs�H���u����8W��3��t0�s]C:<�B��u=L���p�"0��������O64VAx�M���2��5�?v.��V�sZ{U}w���`���*P��������yOR^�@����WCG
Or)L�TT��%C�Qg�VQ"��� oNu.�a����#���c������ghBo��(���n���P�cdS}��)�FJ�4!7��#�88P&�:X��`�OaY�#����_��9�`���AJ�21}��%�\v{�k���)��XE�/���s'�ml"[����\�P�7�����M�,���B��K
i�\'���Pr�y}�\v��!���[`���*���>���	"@#���i��������.q��I��m������>��p���S�p|m���Lai��1��i���������7	��j�j�qRn��!\u�^�;��,�/t������y��l8Zm(Ml|CIpNL��B�V�����8��0�o�M��sE���3j<��ek���������a����2���AM_1�*a��I���E9�zRD���/
f��"e���J��������n�<a���O���
����
��8|�t��w0$��/�F�F�LG@]�5����$�0=7:���w)�w���h���P��j?\�d�I���w���qt�E��J�����~tf4�����u��o�p�z�R�!�&�J����b>�(�����2�6�o�)~;u���n����4���!v�yy����d~6��.b1���3�-�f?cM���%�u�"e_��Wat?!���3�1���#��g�"�]�
�-�{3��p����������b|R)��L&	�g<[�� ���:i��'�����I��9��fv�B+`0���{uEQ�WLr���j�''!��1����_r:$�T��)�~$e���1{H����M%�L]hVp��Eb�+v]nX�A���
���������Z���9sZ,����;|��9
h�����y��N��B���F�)�x�����F��BW��T�l���=U�`���R�@�#>,���[������k��-���VTL���BW\���*%D�3�=.:?zH0'��K]
nhV��j:f8�+rQ"��y'�e�Y-:�]5��3�qx~b�3z����(�|���qxe����7��u����1#\4��������"o(1��/H{�$���H�P;;�������9�
F���j�IWM0!��*kl��oCJ��]�+;��2��Z�?��2m�k�WY�AFWDwE���M���fM��g��5�1�VY@}��P�[af=�M{S�m|��7��]��
]_�=�]-�q��1����_3����7���7���i�'�/����n��Ot�yv\������Y���\����~^|vp��\���Z���]H��Q��������f�C���
���,�*9�����R��C�>%��PD!�[�����b�
D]M��0��z�����nY��r��OR�E{�.�
���CF4^�LP�E�/����$�!;��$Gr]O_\�-S�`��x"�Dw8��mF�g����=V����u��g�]�����W�Hv!�Sh]�GmI1�'����vq�^��:9=�b��'�&���PV>�C��i�|Y�o�+���W�=��Z��������W+����R�%t63��-����O�=��������PC�1������|��	4�d����W�i��c�9$-{�Ok���[P���-v�c^���k�)���7�/@)�>h��s�8?�������S@
�C`-"�s�"���)��l���g\����$�=M$�\��Yt�q�R` ]�|;{�8�,��,)���qF�V�*�'x���
L�����d����3s�
[Ly������4l$�cF�s�bA)�V�Yf������j���}2�������.�pY��kt�3$���M�%�Yi����bY�!A�TF�98K*3�)K*���]�@�z����F����&c������t��,m��5w��z[�'<�C������#�c�<��3*�	
m>^����[(3a����e?�o(��e/����?F��pza��UO�B����j\p`���;�_����7��Z�������!�g���`du�&*FL���U�,��`�b�):d�0I����&�������LrYU�}����"Y���I���2�E��WW�HC657O���I��bx��M�Rz`d��zF�6�r�����Zf������S����S�QA�cZ���ug+T���tL]���qE` �,����'#�
,H<5!���W�"�/+��dM^��������Tq� '!�S��}G�W����j�/���A�{T'���d��pNv(P������"�+=�>�����7�e���f����Cr�g��k�i(qs[�o�'O6�m��d����]���%�o�t��r��H���kb�U�F������kp �6��R�������3�3NE���F[��������*�,�W�P���W�Z���j���,��m5�b<
~��y<��9���Y������r`!�bQoH��v�/�������z�D����<�_r��f��*��CF�O�TZtL���)��#E����O��FF�%#�D�W��d�X�P��tv��tup���tu|�;F�fC�Qy�D�"�A���v��	�,.���s8�\$xH���`���7�0R*F[�"*�yYQ���ob�b�w:[/9�R������J$��o�E���M��1��9��n�i�o��F��?��t�m�=�\*"�d)0E����7?&B����c�.��f��7�M�B`.q��{��~���-�W��1�����KA��
'�gYXBW\���������J��\<P���[?�|�D����i�����rC�=z�[���/9��w�H����<���U�h.�tt��Z�,�������N��LU$2�C���h�/Fa�����!nnhQ;,�����oa�Yk�k�� '3���D���M���L�#8+����v_^�y�L�y6��B�G��|<�i��e�DYWH��_*\H��a�����Bt9 R�k=�Oph��n�lB�H���'�k��+�O����i�0|�/=���$��Z�R[�����D�����y'!.�X)�����OI@�
o�����}�4��k!�c�u�u�����fpU����]Yd��;��S�CV	(K>��D]��
����(����6E�������%�S�s+�V&.�'��c6aE������:
�3����+sq^�	V��������	y����{3z�*�Ff����-"?0�RNr��_L�T�d)�s����p�� D�?5����1��x�Y��h"�R�w5����U���Z$�	�NW�$��f"���
��X�n���@I�&��X*��<����<Fo��|43��3]�S/5)��eNr��'�l�m�����h0�'��H��D��A��������R�/4/�6�t�	��P��_��?_�N�'oj��_:�����������hQ�Um����'S�6�IO(7��b4�b�$|x�8�u����^��k�&��6U��&a<q���w�M�hF`A;��\ ��w���k���53����T��24H��7+��'I���JN������
���e�1��I���L"C�D7I4+>�p������O�V�K��~�S�`D��?9��-����P���9�mq��{{�����@���z�Cf���V��\{�������U���/�/[�q����������eP��Qb������[���P�]|���<;�����tZ�eZ�j�]K%��8XCv��
^��TB"�����������V�bQ�nSP�)c��
���B���[RN���3�$�Z�>g�"d�N�DC��s���O(,M�u#�e��A@�D\4-�N����X���0o.DX0W��G6��X~����N8m)�=��S�e���m�����S�6�3����~$rY"iu���X,����,��}|�j&\�a���FBa�����qD�5&!��7��^9�hqJ�� ��^�D���S����K���� ��(��R�]W�5��{7���hW�FI��3�������g�j6"P��a�@:�1
II�.w��I����E.�8��EDW�~T�hJ9�o&��2%�
�6M��6@KH4 ��b�� G~ ��6^�z�eC]�����*b��=��@���p��<�����&��i�����M<����c>�O]�4����-#���k�����q������qi���{��*��f��%�������p������/$^�R���4������~W��E�A��jA�[�	��� 8��x��w��������e���VZ��iG��
����2�c\�zpS�R6��������G{�t�u�;�BS�O���vC�V��``<�I��i��M�_m�Kq���t�.��|����~�5,�b�^���HC��u5����d�{�1�@�|*ak��Tq����{�ri�5v)=:8:������ry08���=�=�S�������������������'��wT:P����������%J�Z7%�7�k����������>z��`�f U����k]sy������:���t��n���=��J�������JwF��t�y��1r�������!��)9e��>���!�H���p�Q�U�_�Q�az�WX��)Oc���z�%���t����T���a���X?��8���J��9o�;�w
`�N3�m�QU�g���.����)'~�c�`�X
F]�H8��	���'��P8e$���Z�����A=��l�0�b���c�}���v}@~��w��[�ip�O��&��3���/���^���������t�;�J��������<��w�`y�2�O���b�x���K���d���q�s�uR=�6;�����������j 2���V	�;�dtk���	�N�9oO���dCWvQ�,�\R-�]7�Low'���{;��e��E���e��U�7�^&�����`��	�<+�pWkS�/y��^TaK/��p���s�����]�JR� PC�B�\}-�����`���{�s�����#X�Q���l8���'�e;�|��'����GYL�_p��v��B��f�]���	N�N���s�]�X0�:��F�,4�&�����pj�0����,�%S�����I��������G���@���+�]���l2�z�f����Y�=���O�N���'krq���i�LQ������r|�4����,1�'��\���������v{��ryo����|�����������6�m��#=�
G8���������P���]T�tW����AaK���<<�5�h<U�q
TA1��,,��P�T�Y�?�����x0^�O��D��?P�C10��'~6X�4h�"}����d��%yYG�ue�F�0(��\%�~Q���+�����}�Ug�����=�W�:��B��w��������;��_��`N�x���������	\�)���.�b�������'�>��x����� P�'?v�9�����'�]����s��e�� �D��*"
��jo��0�[����#q��B<D��J�y�7D����h���v�9�F�79���Nz���&�<�����	&��!@��B*��������r���0@�'���,!X)�U�q�>�'Z���Y�.WH�"���F���w������?:>��?:>�_�ML�%��g!=SI���fp���V�eI�D8���s`@}%���A���FG��R�b�OBw�+�4�������v��K-:�V�+�U����r�����4��
)�B:X��bxH��e�v�f��q��.�0l���G�j�:�Z1d���:�
�H5��B��zR�i�/nd����T:Ld�h�R��E��n0�}H��<,U��X������>�CBk�}wJF9O`lO���;u�w!���K�2D��v�qt���S��B�o��G�p�z�AL�?��	u/a����� ���=T�����6�H�h�Ev_�����ZY��"�S�b|�(#E���RD���]^�w���A����s��?��"�j�Jy9�w?�#�>�p����9E�(�����j������6����{g0������������x�����f��nT��i�D��]6���u�p�%��q��u�;�^��jSp{��M�Xq��8��\S�3������Y���x��0Y�����J��S��3!O�k�)PF���v�2�rh]��`���u��������T��E5���(1������p����CB��0�|���zz��o4��1*N<�f�E����)�<|��co���$&�)�������>����^���?0��><:x��Io����m���?l<F+��h�3����C���l�����g��vG�6Uz����#��`?���7�������[��]p�D�XS_���UY�u��uz�esh�x{�_�����������Q�`�j
��@.Z�,8j�w!(#��k Q�HRJV�M�w�IR:�4��o���^3��g�x����ZR�E�^_U�����p!x9�8�\�;@�>����j�5�rpl�d��o/��x������� �S�/NOb�����=�����
�f�!nIc�Y�Q��)h��*R�(�$�'�>��^�"U��J��sE�(�Q
��k|)u {�:�a������Q�*)�/	��dh�Su9?��t�+q���Z����/o;���v*{��^�?��W��*�G;��>[���g3����6Z�IA�"���~_���y��vt����*}������$
mQo�����Q���y����f��^�Fo�-en����Yc`z(LMf�����\�]������Rk���w���������A+�`��3H�G���d)��)<��Fz����q�XQ�����H�vg�:�����@��|M��J9G���4��%�4I�������D��y#���c0��L%��f�!K�KP�[%����eXo��ro�x����W����@EY��De{��H
~�F� +x�9��.1aM4BA�m��Zv)x�I)t�F�^Y���Pi����}����X���[�i�{�I����ih-�J����]{��c�+y���6<&A�������r�-�������$C�gL�����>���[��BTRp{Q�A��
=�%�EY��r������m4����[	�B��
=�"�����Y�����c@�~��B�]�!�bp.^�tG������l\��6�E�-����s�S�-��`����r��\Y[����o����}����������o����}���������?�����LX
#170Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#169)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

Attached is a rebased patch (v22a).

Ragards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v22a.tar.gzapplication/gzip; name=IVM_patches_v22a.tar.gzDownload
#171Andy Fan
zhihui.fan1213@gmail.com
In reply to: Yugo NAGATA (#170)
Re: Implementing Incremental View Maintenance

On Tue, Feb 16, 2021 at 9:33 AM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi,

Attached is a rebased patch (v22a).

Thanks for the patch. Will you think posting a patch with the latest commit
at that
time is helpful? If so, when others want to review it, they know which
commit to
apply the patch without asking for a new rebase usually.

--
Best Regards
Andy Fan (https://www.aliyun.com/)

#172Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Andy Fan (#171)
Re: Implementing Incremental View Maintenance

On Thu, 18 Feb 2021 19:38:44 +0800
Andy Fan <zhihui.fan1213@gmail.com> wrote:

On Tue, Feb 16, 2021 at 9:33 AM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi,

Attached is a rebased patch (v22a).

Thanks for the patch. Will you think posting a patch with the latest commit
at that
time is helpful? If so, when others want to review it, they know which
commit to
apply the patch without asking for a new rebase usually.

I rebased the patch because cfbot failed.
http://cfbot.cputube.org/

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#173Andrew Dunstan
andrew@dunslane.net
In reply to: Yugo NAGATA (#172)
Re: Implementing Incremental View Maintenance

On 2/18/21 9:01 PM, Yugo NAGATA wrote:

On Thu, 18 Feb 2021 19:38:44 +0800
Andy Fan <zhihui.fan1213@gmail.com> wrote:

On Tue, Feb 16, 2021 at 9:33 AM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi,

Attached is a rebased patch (v22a).

Thanks for the patch. Will you think posting a patch with the latest commit
at that
time is helpful? If so, when others want to review it, they know which
commit to
apply the patch without asking for a new rebase usually.

I rebased the patch because cfbot failed.
http://cfbot.cputube.org/

It's bitrotted a bit more dues to commits bb437f995d and 25936fd46c

(A useful feature of the cfbot might be to notify the authors and
reviewers when it detects bitrot for a previously passing entry.)

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#174Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Andrew Dunstan (#173)
Re: Implementing Incremental View Maintenance

On Mon, 8 Mar 2021 15:42:00 -0500
Andrew Dunstan <andrew@dunslane.net> wrote:

On 2/18/21 9:01 PM, Yugo NAGATA wrote:

On Thu, 18 Feb 2021 19:38:44 +0800
Andy Fan <zhihui.fan1213@gmail.com> wrote:

On Tue, Feb 16, 2021 at 9:33 AM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi,

Attached is a rebased patch (v22a).

Thanks for the patch. Will you think posting a patch with the latest commit
at that
time is helpful? If so, when others want to review it, they know which
commit to
apply the patch without asking for a new rebase usually.

I rebased the patch because cfbot failed.
http://cfbot.cputube.org/

It's bitrotted a bit more dues to commits bb437f995d and 25936fd46c

Thank you for letting me konw. I'll rebase it soon.

(A useful feature of the cfbot might be to notify the authors and
reviewers when it detects bitrot for a previously passing entry.)

+1
The feature notifying it authors seems to me nice.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#175Thomas Munro
thomas.munro@gmail.com
In reply to: Yugo NAGATA (#174)
Re: Implementing Incremental View Maintenance

On Tue, Mar 9, 2021 at 1:22 PM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Mon, 8 Mar 2021 15:42:00 -0500
Andrew Dunstan <andrew@dunslane.net> wrote:

(A useful feature of the cfbot might be to notify the authors and
reviewers when it detects bitrot for a previously passing entry.)

+1
The feature notifying it authors seems to me nice.

Nice idea. I was initially afraid of teaching cfbot to send email,
for fear of creating an out of control spam machine. Probably the
main thing would be the ability to interact with it to turn it on/off.
It's probably time to move forward with the plan of pushing the
results into a commitfest.postgresql.org API, and then making Magnus
et al write the email spam code with a preferences screen linked to
your community account :-D

#176tsunakawa.takay@fujitsu.com
tsunakawa.takay@fujitsu.com
In reply to: Thomas Munro (#175)
RE: Implementing Incremental View Maintenance

From: Thomas Munro <thomas.munro@gmail.com>

It's probably time to move forward with the plan of pushing the
results into a commitfest.postgresql.org API, and then making Magnus
et al write the email spam code with a preferences screen linked to
your community account :-D

+1
I wish to see all the patch status information on the CF app.

Regards
Takayuki Tsunakawa

#177Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#174)
1 attachment(s)
Re: Implementing Incremental View Maintenance

On Tue, 9 Mar 2021 09:20:49 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Mon, 8 Mar 2021 15:42:00 -0500
Andrew Dunstan <andrew@dunslane.net> wrote:

On 2/18/21 9:01 PM, Yugo NAGATA wrote:

On Thu, 18 Feb 2021 19:38:44 +0800
Andy Fan <zhihui.fan1213@gmail.com> wrote:

On Tue, Feb 16, 2021 at 9:33 AM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi,

Attached is a rebased patch (v22a).

Thanks for the patch. Will you think posting a patch with the latest commit
at that
time is helpful? If so, when others want to review it, they know which
commit to
apply the patch without asking for a new rebase usually.

I rebased the patch because cfbot failed.
http://cfbot.cputube.org/

It's bitrotted a bit more dues to commits bb437f995d and 25936fd46c

Thank you for letting me konw. I'll rebase it soon.

Done. Attached is a rebased patch set.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v22b.tar.gzapplication/gzip; name=IVM_patches_v22b.tar.gzDownload
#178Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#177)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

I rebased the patch because the cfbot failed.

Regards,
Yugo Nagata

On Tue, 9 Mar 2021 17:27:50 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Tue, 9 Mar 2021 09:20:49 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Mon, 8 Mar 2021 15:42:00 -0500
Andrew Dunstan <andrew@dunslane.net> wrote:

On 2/18/21 9:01 PM, Yugo NAGATA wrote:

On Thu, 18 Feb 2021 19:38:44 +0800
Andy Fan <zhihui.fan1213@gmail.com> wrote:

On Tue, Feb 16, 2021 at 9:33 AM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi,

Attached is a rebased patch (v22a).

Thanks for the patch. Will you think posting a patch with the latest commit
at that
time is helpful? If so, when others want to review it, they know which
commit to
apply the patch without asking for a new rebase usually.

I rebased the patch because cfbot failed.
http://cfbot.cputube.org/

It's bitrotted a bit more dues to commits bb437f995d and 25936fd46c

Thank you for letting me konw. I'll rebase it soon.

Done. Attached is a rebased patch set.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v22c.tar.gzapplication/gzip; name=IVM_patches_v22c.tar.gzDownload
��ym`�<ks����
�b��6����>��c���~dg���)JH��XHD~������3z�������������{NG33��x8��4������a���6��g|<Su����z��~�����|��o���1#3`������y��=t����[�?0C���[���,�]������������I-
L/t"��j�9vyX�{�S'0ys �[����jSmj���ZmC���A�����S6�4���l�v�=�
S���a����D�����c��f���|��6S�}��4`a�����F���4#�����������n��O�W�c3���M�T�9v�-�P;LH�}�`��(��|��[�>������;��
��oFR�"�I�d(�����J%R�P*���aZt�{v�x8��g��}�E������7�n�p./y�"�z�^���l�����<��=��,���7p��4�9S�>�^d"�*<�n��k�����\�_@i�-sr|����b�<�~���"�`��+��
3�S`FWf�B�����sy\f]��%���'�E���#Z��b�V+�0�c���5,:5=;l��u����Vy��p�r�6_�sp��*E8M�S�WE��+&�J�2H����w�\,��d�j�K'bf�t�r���5uMUT���z����d<1�
��������g��'V���j�U�SU\��g��F�����$��P<��q:q����.�s,6.\�+��	Y�]�������(9�Zf`����C�$|�>��Cr��`�a(�6pAYwQ�)V�9)���rk#�����r�D:��% i\Zf��<��V�:F�M�I��Ti�F����G?G������A�z� ���~��R�BsY`0B�U���6�
��A��,�/����[;j��WC0�f�<:��9^4�AD��������������v�X��n��(L�a�v��	����|��0g���������1K�$sG ���0��G�����1{,�f$�L`��5bT���\fQ�WWC�blW���@S�A`��\��!��[���D�1��Z[p&�#CU������������1#�s!��hU;�%����	���c��#T���{���c�i7��������8�1�D��p��"$��K@][up��7�V����X�>!pU����A��a�u!�����vV��J\�����k���(�ZgEAo*z��ff���n�kN�K����*Qd_P���������dr����q�}�1�3�}c������l����[�������C!X����y����Lph�<��� $���H*aF���
��s$l/F`�.��N�� /!DD������)�B�����L�~;!!
�}�;@�
�.�;�ZUv4��H!K|����A?����z�d�Ox������_�������w]4��W�H���A�[�@p��@wH��BuA^KY��YW�O��
0o��n�����M�(�.{B��������CX������k-.�����*�V�}k��H �H��^��:���;q�0�Q�@O�^	��R����L����[������B<�L7������#�$�N3&�L�|��Zb���L��"{�O��"�Jo��y���@����?�;�X0c���h�"k���-���%i�z���n4�?[	a>B�PBE)��Ft��@s�.#X�k`����g��*�H���;(y �r���T��������Nd@��<p&�"��~
��Y>���O�}0`o�(=�s`� ���� N���Z����k�e���T���^��h�1�b����v�\���	
 9P5��d�l4^>HE3#��x�����+i$���P��PY�<����~	L�0���]�OaK���l���b4d�a<����������p_��d�x�o}Ge�&�r��T�����q����.�����F�>���"��%��v�|���-�Y���T�v��`$���3K\y��k+���0^���-�;84%��Y��'���TZ	�W|m��yY�V����:ep�R��P?0@Vj:�0��dl�z]m�5�V[�tV+5���
�P�4����$k�<�����g�c����Bg����{�6��Ki���)�!�3����9%�d���Jv�G�(�,	H��#�
�5g�����������������������m�v4����l�Q��j0p}�g��@�������b����|���?����5�m�5+F���;��j�{{���j�����Y�S�N�b�s+��5����}eo]��#�?��@(�J�C �����,x���u�l����a�,)3//�`�'`����wv4����%Dp��3u\3p�^
��|LUI���b�5���)���P}�c�Rk�B��!U�D{���%D�'�,T�I!�)��d��.a���
$pa��Eu��"@�x�������B�O��n�[���l�B��]�����m�Qy��t}M�������N)����r_(~n�
�`����H���Dr�0����������QR��n)e(����������4�eLl�^�h����
��ES)iCW)3
�Ui)���*XXy97����C	���t�yP:�m�����f`����;���b���\M���p�PS�*#�1`��i��K5���X���� ��pL#12��W �Tx���"]�$�(��#/
%bx����;z�=^������hp���y�=.����������V$5`�2�I���������.��M�a�\��������M����H�V��v�vU�b��`���fl�*��2u��������a�1xw��2��>��P���k����ZB�s-r#�sk��
�y�2*_����r�x��bE�,�[3�!�"L@d�����LW�����R���-(884'�9H�";a�fz^����G9&�::=��'a?���U��wJ��F��JU�=�=�9}��1e���D5����&�#�%a��xm�aE�����$�Zat���������5��V#�^}��������C����`xi'����P%��Z���xR	7�������X�\ $Y�QF�d#"���(�$P�">E=M��{*TT����a�t|�p|c������%#���h@>S~2J�!����jU52�<�gj��k~?2�����vG���H���]+)_	/D;��l����>%������!�G�(�ad��PQ�QDK��7v��JdK���99y������$��1��,��6��g��G������Y���O���qAqAO�2��)�3��% IVWz�C�
�E!�RX\JRd�t��|�&&���{�8�yw�1S��I�bs������t���(L�;����������LG�x|��(��������qM���Y��M:�q�����&W�fS�f�^
hCR�j�jJ5iJ5	�m�D\��hJ��3������j"H_�����}�[�Q
�c?�/2g���������X��N��b���K6���������~�hWbg1< ����]���==��u:�#��w6���O\�v�C�I�J�iL��82D�d�[f���=����:����D��%��#�\�>d�^��7�`t��&������������{�=��������z[��
5�r��M���jj��i�����4U[�z�:������0%w������hF�%:G��2��n&�C*���Q?i�J����R(3�&��[��0>o��u�bR�@��9���]a���2)���W�f��|2P����H12l�A.k42@�ei�c(�?:<�m�D���D>�dP�(rg�1!��Fg$�-0\�%*���y"���'\��������R78(��=��E�eH��w=��g��o�(�G#�^�ch��]�+��'��������V;�����M����.w��{`Z�(�S���_�Y��l���H��nL��i�t�d{g��v'��F�������pW:������v���x�<7]9K��R���3�h�����������Q�7��fe��8���R��:��p���dw�#��3����W�������������n�5uS���e�L��i�l�OT0_cnE�@��-��������m�Z�N���TRV��J�R�R��E�����
�l�C���F&���Dl���j�Stq��������C�"������m��`H�n�c�A��M~vs���]m���g��Y�}&�O�|2�EdS4��k������a!9
��B@:��C��Aze�������uS5��X�t��7l~�����q�_7/:q��������S��%	���9���/������@B�X��?��'o������t���]9�C��e���']6����#�����~����
��!��
�3�>�\t�
c%��*����ZU��6����^���F�Ph���d��M���'W��UEK�W�f2����Y{g���{6<<a�p��;<��w��}�u�i����T"YdN�S������f(`��n���y�,���]zgoKN����cg���px�Cn�;��3��#���1��WX�vb�v�9��)���:+��.�h
�3]�D��������^���_S/V��w�%����a��cjU�6aET�6�!"�u�!����k������^bf���~w�.�!�x�Ow�=��C����5c�m5��B��W��*tR���4�S��M��?��S��1��^��t������8}�i�=�������4FU��_d]�@�(~�L#]�h�g�c�/��~}��C�'
nO����8�`�>�K(9��2V7�
S������ON^����%=�������"X�;�f<�VfY�Hms��ow�@/X\�����;�Ftqg��Cw���|��!������.;:?�8=c#�EE��������e�gJ��	[)iv�����	��s��L>u�&��$D��"����zq���Q>�~d�g����Z��s��&
��X)Lo9�����i�u�So�,TH�m���!`�K������G�G�����������z'�go/�v���_`XF��)c��;=����+c5�/#�RHSL� �B@�-����{�(����O�Z���7���K�s��T�*D��7�5�M&�
��=���)���+B�AS0)M?m2{�#q2�_R��=ey>`#1r}@m+/���z�p���,eb�`��IF8�(�$�&�t<p*���%��B�L���p�L��OnjO����|�U.*x������T�B�tq6w���o�[��F�w���h!��)?%s:�
g*/1�m���=.�yX�a�����!3���W�84�S8��$�A��2������~.q�A��(.b�^W���0?���k�;�q��q��� ���3����8#o\V�6Z	�	����&�������W����_S�U����=����'�������[3�����4�dq��O��_w,���z�KK���,y$���s�q
(�����@����y��}�KfV��"�$���m�*����2"r�j��H�����P�d�|�YK[
G{��8Y%�J+��_����Wo����s3(�"����	����c�QwC�+�
����4	k���g"<��l<#���J�u�I�h�"��t�������rgQ�X�����<�D6v����*-P<09��@b6	�wC9M��}(~���h{`�Em,�an���&{��s����\��SDx���
�]�5���6`u>�����l*��c�NHWbF��1�i�:k����x����mm��r�O���Y����
��=���I�Uy1���$�O���e���S4I����H3T�4���0���>��`��#���g"��lyVCM�*x����������Rd�����DV1j@��	����R��D��3�
*�#�,�I�>K��=��R��[�J@E�r)�RP�yiP�Td��V#��Z�i����z�-�@�'��'o�(=j�>��#�Y|S�.;y6-�6_!G�I
|����Y�l�`�~o=#�O�hh��c�����S��U0��b�'��t^���B �BE>����>�4��v��Ir��ex��TE�D��TH��8j�27��M)��/7*�orbT��0e�������:\@
�P�*�=�)���'��&�7�� ��)I\	�H���;����d1��r2���!��F�YY$��'�;<)��77���=�U)	�S����#�!]���H�#C]db�9"��������i����uLuK��5��;�������<V�������������ks��-�7�����e�|�wY�)�������e�<F�B�C�I��S��F�vv�gz�
����S��?
�f�a�Z~�VP-�7�K�{�=?D�<D� z>>���������4g9��d��s���K�������H�r�i��\�O��*3�dR��H�%%�/v)����d�7*��@P��'&��c���-�G�`,���Z�t��Y ��A�,�6iD�s��X���������k�������a�@��V�x�B��U/���5|"�B>I�J8���\4�L9^��\hVi�s2�'Js�r�<HP��8�7�e"���1<g���s�����C�� B8QiG*���UPHZ�8�=�+��/|���bD��B����JF�FHe��T.�2I!
�x1g�d���F��z��+l���B�L�_��
�P����}�N�h������a��B�R��0
�!'>��/R�Fd�[h��6���H-�C9RK����&��x �(���`A�i���AxFO�� �P$l#O]�)+1c��S����IE2{��e.hqZ�&IM�Kq ��R�Pj�����lE�`K�m$[��_jx)E2�S<�b ��'�j��"V����~�_�����P
�.}��3#�)N]���L����u7���e*CrLxJk��(�.@^-�!�:�5j����� ���dPd��e���[F���%�G�{X�JRp��]�D����fra�V���.-K'[�>e��1�yq�����=�Eq6��C���j{���7����U��V��Wu6U9US����t:��2�P9?���m!&�G����JL����ehM�=�NY��o��U������#���V�KS��U@��'���R���O-`�	I���������l\�^�&���!S�Jw�9���db1�H����`~a�4�0��Z$$"���6��GH0"��1�r�P��+_	yQ�S�Y1��Y�?zI���yFD�M9��������/��Z��tU�h�6:1��$kb%TQ�5��d��8b�H��b
��k~��b�mQ���v�x�3��-���:�iv"%>j�����)�{pZ�-D:i�F�N{���g�$�m&�y J{ol��������-+�s���8p8p�p 3v��<Yq������)GrVX��b
����O�C��w��QB|O�S�]]�W�k)�w/2����J9!/o�u����r��}����f=���R��*9T*������R�C�t�X��3A@Y�)�����6�.��X���/+'(v���8�^�C)�N�<��37�PT���&�$D$lL�a�g)++�MX�e�@'��l�/��rAwY�������r��|�����,qC�u�''/��������P��M�5w��4o��G�{g�}WfbnJ���E�A����fJ�)Hq����$7�T����H�G�>�e#�����x�T��H�OW`�V��\����ym}���1~��_kG�h������5\�SN*��&�n�>��<������@��������Xj��[k�9�"�_�j�zra�@n���]��l�(�F��H������������T
����!��`�^������_�[�D�;���O?���z�����"8����s���?��������A�|��<X�}�~'���(���-����C����s����Z����x[���|��{B�����6���P{��'�����$s`�?T?�>xbX�J����l>(���n�^��qg���C�����gO��Y)��2��Y��W�[���jy'6���-��y�uS2^�������lE�r��$Y��j�;����4 �'�Z����],<�"��Xmq
��1���$�bp���K��
�F���V e�>�,�q�6NL�t
��v����SFD�'@q���nP�B��'q|}�P�l��g��R0o��6��TTL!��'�
�/g���'�+O���'������Cn�	<{Q[|Ve�`I�������uy��v��z���Y,��I[��/������~�#�Y-��2��������^hG<U3�c�����
p��.Jzz�����d����!�nf���mY�������V� ��B����)�wa���]�{tK���������(}�6���TO�^�T!�tl�3X����G���d�[������f��-��/�b�������zR{�X��.n�����E��d�_�����9�|K��h���	_�<������o�?��9��nA^��:a�~,��!p�O���
=�mti���m4�3�<EK���dO�z'���%4�$����h�����}�v�?����\]?1S�y����-tI�[�14.D�pF�v{�C�5gQ��6�Iw�ai�(q����3��k��X���n�����'aY{b������L��al��=�Ie��&�f�[�0���&Y�����k[{�lK��&���l{�s]�u+<���c��f���X��Z�2�:�]���l�i�|�t�+�����6sW��E����V�4������0+;���<.7�����3���";gb�.�����,�����V�o��.
���l�SF�Z��i�,h	�����!�������}��M�����U�����m�Vw,�a��Li�5�����C��������@9�Ie����s�������}D� w���Q=]w��(Zmn .+��;�_{�0{�m����ZrL�#!�w��x��X9<��Tk�*������Z9�=��`�iY�]V���U����qex��1<.
�\F��KJ�]���X�Yf�]z�=�s�wa{[z~V������2<=��fe�z���gf�j~YefZ"�'Jo���'�7e*�
�z��|3�?<����;��f��������0���������������c��#[���0wsXX�\W�T��?F����*[�R'��V�Pv���V4U�_e+Z"��2�n��&�E����|����he�X�&������������nTz`s��R�,���\+_�G�R���*��*��C��t���w�����;	��k�=�&��La�[�����M�(@~�	ywf�gy���LK�$�k�g0{,��k��f*��ruY�w��O���e=9���1L��R��8�>u(.7��-�,��r�hr�.�j������������hg�i�nQJ�������
�z@(./�z1���4���J�<��y����ZZ����
z�6���b���+��~�2�~����9�����A�?{v��#B������E�#�w�Wi��-+dk�W��4@�Lt�e�{��H�MM��H�����D�����[��5��MnY�DO5��!���Y����ee�zb��y�RKS�V�;���������9=g�H��]�~�f�'1��6:�m��q��'8����R'�J��`f����4�x�J�L�=�������IgiY�����?=M���2�<1��������;0�<��[�d��>o{��=9�����=�-b��X���rf=�1����3+���rf=$���0�)��U��@Z�i-/Lk�X������.s��3 �L��/��0e'�.���>�S^>����wd����B����	S=dX]}����c��L��T����������gI�nEZeIZ�������z���t���l��he[Y���agu����w�\�����Or���.����?���S�(��)���<|J��v�UJ�eB�qb�f7S?�%]��e*�
~y��Oy����0�	gYAqO�1����P�{6+���V�6+G��I�c��?@D���
X����,����O1��6��2�U��;�B#�gt-'��Yc������'���H������o�E���o,$s.4V�(����i���V	>25���
3m�O&xi����iV�,�����<kS�������i2�oR�N�If�X8�x4�)y&I#/E>v&�����$�<i-��*�/�9��p�I�b�#}
��$��i����R�2g1'�R����f��<a�PZ	YX��5���%L!rBhh=�vpl$�ols�ol���m�2����-���o�Mr�o���p1���S�����o;��g���������H�f4�<=O��y<^;]o$|�?������}1�Z=[������#k�3^���������<����_��w���#�����y�qy~Z?}+
�T�S*��
�jj���I�����O����2--C��z�7�g���R����i�p�����C�:CM��%��[}[��I\��t�X�M�����,��kN#
����s�eST1�i���Y�^_�}��U�iC���������3�c�@��:�������s�m���F��oj�����o.O�u@b~�t��?��E�\���u�S�l��������[$70�S�5��:�v���|R3��g����F��sP���
K��v��:&�p�j�IS
J�?*��#��i\E����&^	���~����5$���e#�F���M�����jN���r��������M��7������M���N����Mtv�5����]�}��Wg�l\L?��A�c��#(6����{v�F����V_����(�������F��u�ko��'
�^�(wy���5�l���nD����\����|%��Z�����Z|��
�A-FM��_E��4����H�p%IW��O���<�4arF
�7������,,����yU"���B� 7Q7���Q��G '�?����a�49*/:��q��k�Q����r���-��A:��%Z(
A�W-�j��g���5�����w�t�V���v&p��x��lc��r������0r�r�g�m���}����[mk@�P6=�6���G�;���V9��2��k����@������o3cQ���9�^�Pl>�_D�E�D���O�0.J���|��������O��r�ll���e��,a����+^!r�s�����-�i~$U@���>=5�$�2�k0	-�^o��?����N=�������-��<���~!N/ON�
`	�9 r/\(��i���_��f(@M�����P��Z���I������|^*���]~@��jk���*tL�
u������<���>���m�������Wk��/f��D�$Z�O ������5�"���N�w���}���g&N����8Q������4f�)��sO{����C�.:/�����-6�8p��!Q�^������)���$Ww�y�g�7[�U�(+�����x ����������� 5O���N�5>������f'���<h�}��f�s!��"�J�)"��!�7@�_�y-d�����~�������'�e�������k�cL_�\�����&����[M*h�:�
�hl�f�������F^]��wl���F�����6���3��Yf���8:�<md�e�6�,��3D��������d��Y���@(fBHnL�|�A�b�v�k�8G|:=k�ua
:�("��
�@�:b��(sm{sB��p�M)���Z�6��e$���a����1���kZV��Iz�Yy�\n?�=<����`��HlA2�����J��L��I�}�������Z#A!�SdZ��,����iB�X$�=���7PM���*���fQb���1�Ic���h�.��?����5����v�Fk�x��E��>����0�;M������12�Lwg�
,9���f4��� Ef������'=7���a�	q�����A�J�{?T0%�@"��@��"h/E9�eM�x�����c�i�����%,������KO�K�QH��<��	c�|29q\�8��Bq��N�Bh�F�)��k�����V(q.B�s].?���y��"T�X8���d-g��b�}9b;1�"Gn.�t�A�������/L���u�VF����u�����������c�MaO��%�3Ll~���M.O�u�A�~2������{���LP���S���v���~=�}h�]q�Em�i���P)��:g��b��&�3��N������4�
��F�)"U�����)��R����7't���pc{~��z?E�s.��=���Zl�C����q�L�qy3���F���,�C�>����o�x���Yn�	��e�oP3��;����t���`�F��b�`z�����v��:�@�Y1i��ph[�r3�O~N@����Y?0KE9>>F�D�F�c����^����lOB�p��N��2y�M�����B~g�
f�: ��"�?�4��i<�����83������Z�&�4o����i��5S4%����
~s`�����@c6^���r�J�d�!DWBd�%.�R���8{�����A����������bpF
����h�/�N�Z\c�����7v�c{������M��F��Xdu�� j�B[�����6O��
��	��3��Mo�����7�Z�+Om���>�=�}��vqA�e���y��8�P�5/��7G�^���~��F�_�H~�����������77�)�6�����T����5L����<x1t�53��
����7k��ne�H���6�}q��2���l���C{f��N�G���4�n�i�R��!J_r��>\���A+�����}N���t)���^�6���w���g��9<���#M�J�����)�<?�E��>�P<��7(���z� �������P�6LF.����*�c�Si9������,pE	��;j��t�<����+�����^�C��L$�?��*��0@U�;5�K��,�.����	�������|�Iq�md�Fz���,AP"
���}��]����k��2�,��b�Y����z�X����z]i��
��h1���M(�aH�_���
���Wln���������x���qn�W�!�5K���E��0������Q�8���!(�~�����$\<�n�}LD���O����B�g}k����H��#
d���*��h'g�s�>s��}1a����dIr����K�L�`&����W�vY�}��|d���������z��v������$����y�[��b�P�����+���������Ba�RY��C�Y��������/�����[�/��
���9�f���}�J�1���}�_�/�����R���?Ra��fB�$!��s>���J4�l��N[�r��g��;����;h�N����'��p���q�`M}�+F@�X�~z������~�N{����&�l����o=�����
��K�+��7&[�{���������S������������@<�[S��]4�#E$��N�W�v�|~�mw+���5��M��z�"z�x��[�a��3pF����8��R�=�e��S�>��Z�-�J���������{��j5�E=S=��T�)����<��<<O�Cz�����n�V`��)��|��k��v�h��<��7�1'���e(�
�����>��D��#5s �h���B���I��M��_1�M�v7��`���o9+��%�����:n4���XH�,�Y*h�\��rE}���Hb�-���I����u��BIw_,�u���E4�Y,.qb�IO��~���{�&&�����t�n���h���}�� ""pp�<	��b�Bb�������	e]��)�o�L���
s�8��Y��1�0���Y�,�w�a
������f9{�&5-�Hp{7x��bZ[������8:;�|9��U�����?FKj������t�;OV64�d�u����Kl���+�}BMeB���Z+R�����(R�����
���%���@���>t���RF%���4A��M���n�0O��Co&���i������d��.)JA���n����wQ\���n��3�9�:cF�l!�Lc�	����9W��?�>J����'q��.��4��= P�e��BE���ODPK�C!T�U������,	5��o�[<��nk
�}=��,��s���2'm��t�k�*����t@����cK�B|!����(2w�i�}J���u����m}���F��Oy�*l�.�bCE�0�>�m���qs�?�����)��D���~?r{7x���c�P�rmH�b���`��y8��t��8��Y�`i\���]���weS��U�5���i+S��e�s�����B|+
}����h�7;}��0�����)r���_���hC������$���B�aeU�wY�"���Y
�zz��v]�G�Y
�|z��@��G��x����N_b�����`�4��{���/��Qa2L��,�]������^n/}����2�qj���N���pti�N����h�A$�u�#>
%����9��R�
���\�G��	dn�����5�b�&'F��������Q�H�O�oj��e$����@�w���+����"��Q��#�T�%�xs���
A�_$
G��:�9J�&�l�FNg6R��\�A]�	���a���I��@��U��F��h�f��v���02�,`J���YL�xoJ��L?>}[x�l��� tu���<�)q+y����F�Y�c	��L�������M�|��6W��y����������@V^J�s/��^�MCn)��)w+@��\�>#�w]�v���hcdRx��C�w�c~z�a�����z���{���c"�x1�����@��)�2�@���	
�$e��Ho�
*%���xN����D��S����������m�]N@h�.4(?6@�����K��WQ�
Md��K�����gbc����n�n9��nR�F��-�G85��{�=[��fH���H�#>M��Yf�b�<���Bx KG�Gq�1������oZ�](���I&��HR���W��x�VP���;~�_�����P
�.-zB3�����x�d�]�,`c�����x��3��e��c��e��y'�����x�i%��xj����b�;����������J��Y)|*1�������*��JEllax����P���qH�����F�"g%�'���������l�M��d����A�Z~���&���k�S	23�S�,��MH������W)Y(5]+~oa����Fd�����B+�B��b9���V���$i�"Rn�J\�0Lc�0����Fm~�=����y��hf�(?5��C�����[�^B["A$ V6w'���4#n7
� <3�tg��
�H$�����
9��(��q������>��!~�6����w�����Eb�����v��������D���zdT*�X����	�x�yM2@X�w���m������s�Z��yNS�s�;AH��q�
zM�]�n�x�����_���J�u����;�|Z_���c��'�9���W}g�54d����������xn�)�`GbL���&�T��I*n�)w>��n3�#f���`��r}�P.�����pM�N����|��/$���~�]������k��~�>��A��mn��}s�/���<}���Mp������[i�.�H�'������/vOg��	m��1�W>6�(9H��w4�������7���)�+o�n��Y2�q�'~�����s�� ������m���n��`,���f$7��1�Ss|/b��O������=H�k��=����v��{�7��dR��7?��F��Z����Zf*��T����&������d�~mOf�����������+&%-}Z������i�q/�;�?���pb�E0\}��9��n��W,x��W,x��W,8��{vw�����������0���y�W,v�bW,v�bW,���b=���������wO���2��]q��]q��]q�0�u�{Z\�pz{:\v�A���������������J.�v��p��X^��S�"��b�+�b�+�b�+f�~";��=���"��b�+�b�+�b�+f�";��=���B���+.���+.���+.��";��=���B���+.���+.���+.+�,)��q$�
����B���+.���+.���+.��~*;��=���B���+.���+.���+.��0;��=���bF�b�+F�b�+F�b�+Fa�6;��=���bF�b�+F�b�+F�b�+F+-1��8����'q:��A���������������F�����Ngh�p:��A���������������F�����N�h�q:��Q����������(�5X,]��Ck���o��d����0��kQH��K�n.��@v�46�p�oIl��8�28�28�q�eP�eP���G��u
9E,��WJ R^iF*�	����La�8!\R/��?��(�jl	4pR�%"�:�;�c�Sv��@�s���C����F��v��Y��-�������;���Z0�_��q'���z�t�9�Zf��%�k������[Q�����R9��C���Ht�L:4�I{+>��:[���D{Dvg��\�]1���o�m��+�����@{�)M��L#^s�Bc"S�U��i�A;U]�`������F�3pW�9Ot��3�;bt;������)��H���CM�o�m�o.O�uXR~�t��?��M�����|���t��3���'��������0�S����y&��lT>��w�#�_�nd��(5oG�$E���^����_?�p���z�����z
��oeM�E�o�.G��Y�\���`�~_P�F����Z����������&���a�P~�.u�gj����9WO�-�J�?�'�GPl��m�3��`����/����g��Q6q�G'���"Cj��T/O�
�6���Fk��6S[���+������e��J�[�Dkk����2�U
�s�5�.�*�B3��������N���uw��n7���D3h%N����c�D����G=���$�7��Z�������w��<WDg9:]���)X�2����h!���Z&��������Y{#��8�� ��7�����������''Z�O��x-��{�3`�v1��P�!U/j��O�����/EQ;��Q;=��x����
*1�[TrP��;�IuV��M��|C|=��������~s�f�����T?������a9	�^o��?��������q����J�
���_�Sr��B�@����'����uu��W�#�R�#�wd�����hq������|�},���]~@�W�XZ��m�v�������Id�H����_��=�bd��U���' ���g�"jw���������?+4���_(sA���
��2+pT��p�Q��l��Wbs�X�|A�18����	"��������9�W�e|��R�EQ��%����Y�R#��]w�9��z�Z|'�E_�pA����z8�����%I2	�`����G(5����?$M��>�B\\�����Nt!#C�������+|����T�^t��������Q6���P$�Qp���c(5og��u�_Lo���T
�.O�g��V
�!{�Q��;}T!�N��;d�N�w�Vq���R�-��h�m}�;��l�W�Ps���`��5���L�����(N�����go�\�1Id��La��E���/�B������C�C0�E���Y.��?�h�z�q�9X\�0{����Q{��	d�/���#�����n�p����q�nv@�����\#�+�y�������6������;�y��&�X��g����0/���u�Q	�q��C�����&@.����q����s(B+�<�SL4�����-����t�\�Y��-����Q�2*cz��&Kg����
~d����N���2
EF��Q&	�Y��p�J�%��K��[z7�]�������g7W�S�1�V=9� ���"J1�}TYs�'Y(��P�D�g|
���C5J�?�J�D�~"!h�z����?��st&
M��hW��p-<�xW�������tAw���^:�'�5y���!A?R��kk�����M����i�}��i���`�ln�,��u���O���m���5�!�5���������A�Z���@�5Fi{r��9�5�`X�KA+��GN�Y!�sG�.�,��|�1��f)�5�&�w�7Vol��?��9/��SJ���(�K�v���0����|0�\(��K!�aqz����_�����F�4��z����������� &���#`�N��H����ZQ�Z��,�"�E�����e���y��tjV�ps��7G�^��0��X#�XzN�_0~i`���7��$�]-2��f�u7s�j@�������-��C��F�7c�`��~�T�V��t�l���W�*��k��o+8��g�����~���Os����-���"e��p�3�����c��&=��R�m9��mt&G�jG��
��?�G�@e����)�<?�E��>�P`9���7(���z� )�|UAw-� m��`g��X��+rp��I��Ra$�R��^��r��xZFt��<��}�5��w�\H��j�dTK!���B����0}���k�&�Y�I������M�e6���	�_� sk6��M�.}E��7��d�'���a�>�^U�k/B/�`"/�d1�0 �FK�O�(`�5p��R���/���e��4�@�i�a���7�R�y�P,lW;�������h��?:�����
Pg��R���������DZ���T����S0?�k�����b�P.��*����J��B�/���J!�_n�W�� �������������~��tw���B�.���b�.[����v�`ggo�}`[�=��{(�{�P8��D	�p�9����u����>���,���m7�i�z�T�C��srPO�m�(�b��T<�����A��~1n�������j���~^,�KV
+b�0�R���\�X���5@i�8�~g
:�o��mx���x4�D��P����^��~�r�����o{N����E������h0�m9l�Q�MloS�%�uz�/=���V
�%�@�
)����]��y~f;���q�]��}��f�<m���7���c�J�T9�vw���R��g��Ze:nZGz��0�	�����"�OnWl����G����n��p����h�h�������run]�����5���~��
��"����=�C�0�����������irm����a�okkk��8������~i�n������w������v
{0�-����v�6#P�G�Q�:�y���^�'P
[�n;k������]� G������o�9]���(�� 8�_��S�&R^W��#4���nh�}_��Ri��0��g?�?>�����
?�1`pNl
�W#�������*%P>���C��fh
�hO��NG����B��0'~TO	h��m��	�����%�WB:�mn�G����px�l-��W��*��0t@K����j�
Qs����>aD�"!����l�
���=�����P�JR�Q���u��:�Q;P��;Mk������,�sa>��O�A�����eG[.����gb����Fn����;eW�z��G�3>���Hl����X���"�3������e�W ���_x�Pllnnd�q��e�}��m����R����N,j�}�=(��+s��)�Z�g�U���������Gd��x�m!��VAkk��qC:�M#NFM�PzE���^w`�6FY��xs���(�����my�
>�����+���r���+��
�����F���a'�r�v�fj��hp��d�7�;�v����Z�S.�
��5d��D�}�H@R<
�B����O���������O�����>*G��5#�2i�0��A��v(nm�'�����[3�L�f�.�DD����R��t�Z���N>������/w&I@�V��H	����=���I��9m�X4��z��5��H|q������&@�������7�����oOY������~�q1E)��47��6�p�t�M}&/+���FRI�J�
���7&�n"}�o�o�`e���o��gM�'��&��a�������j"*F%q.=����Q��mM����@��j�S�����e.�
(�g���� !��fd�
|��_�Ay����R4jM4Ue�9���vz���PK�A�&��Hb�9��}zr��m�x����V������D��@���1 #[�.t#�,H��Q���q�w�����d����C��Q'������a�k"��<�_4�[>��4U�
����p�q����7���U/��7-���^�X��?8��w��(l��� Hs5$A�3a9�*8��9J�,E�^$�4�.������n��4ub/����U=`�y�����W�4��t��>=�������z�Nf���_��h~
��
^x����R[Y���f�a����h����X�Lv�$�oq>��U�3�'����w��1���nye�}�?��������N�����:{�N�]��
���A�*����m�{�������bQ��7P�T�����N���8����r��g38������
��R��
L��J�!x[���s�#�b+^p�������f��P���3�G����LV��,[����T��+��������u��Vy��I��bmD���{��v��]������+��L?S����
h�]w,e�g�q��f��:��4
��1>l��A�|�9�T��l2&�
���,��V�'�p�?:W���=��-\�`?/gQ��Y����E`�6�� [D7��n=',�Z�F�.��X@������F_l7	z�vk������{%XI5�l�0�x��a��V`����5�P������V�#�c�E��
`lv7��&����5�L<PV����i�[�a]IA`�Z�Pl��8:�5D��;��[$kN��aCAO��[�����gt��m�-������'[q��+ m�1������?����g�C����|)��R)�B��{��y�D8A����	��6�������(��������!c��������
:�.���W��O����z��3�D��f�L;������N�!a�S �([���4<4�.�S&N36�(c��2$��zr��Ij|B�$�\B�4��B��h�X�3�Mz*�i��O�R��l!�Q��)�U�V;;����p
���w�x�����[`��������'1w�����K,��WXw���/�p�W4w	��)1pw�.�������������fX����.�o��>lo��oX3��w�0[9J�>������QQ��j��v�!��=����5��
c���^��{��J�`^D=4p���z������Z=�}���+/�7��W������F��)sY�[��8!�z��:���T&�Y��e�	�c%U��m���PT�]���|����M�u��d�#���j�x�i�F�gwdK�(��QD����/6���@�hdC�4��K�����x�W�����h-.���r�i&��o���0a_-��9^yV���5�nk��%�m���H�W��������o[�z��r��C����t�{Nr���R���~fSK��z_�����C����E���
mS���DA?�@�1���hw���,�8e�����U��_�����j�#z������&f3�^O0�^K��N�R�����|�nUv��Ay�5���z���Z���?�|���y�?��=��B:���������g����a?S�\&�H��1
]��K������w_`+�O%��~nS����iq�6�"�������~�kk��aa��J�-�L�B,O�cZ��6p��c�:�d��`O9
�^1��m�cl�I����&#J�`"���"m�w��r�[:h������=��Vw2���J��X1B����g�(Z�V�8�Y���v�x@�W���������6�X�)�(�-�y
�?�p��'�c(<��%�:�����A�Z�&f<��a���bs�s*=�������^�S}�������5iv��/6i(�}e� ~��������=�S����2�_��!����a����1\�"@O��q�Q}!_=���i����V�����kzct!�1����o��[s���S��Z��y�c���ZH�:,���G"�H@�R�xC�P{I���?!�������6���?�t	���?�$K�#�`���P��������������"���N�mX�Vw�������Ngg��[j����{���2��
;��R��!8���=����"�����ut�	�%����U��2S�xu��$������(>���]�Q�Ne
2�l��A��q������On����+��W�3������pW���A�}B�7�A���o��m�H�����?zN���Xh:*�R���_(����sM(�c�	���	�3j��S�iq���+����y�R����v�� h$�aH��Q���P�����`%���v�qL ���8�"�*�D�'���������u�t���)8���|�v65l���B}������6U*f������|�����d]�?mk���60�)�����x���p��~�f�a���H��
A)�QX���K��I���&���m�{R��Ve��^���I�>,
�k����'�s�<�]�z\;o~��N���K�Nw�6~������(P��q��&u|�l���B�?����S������*-��4M�����.T�0�G��@N~�o'��k+���J��&L���h�.|�����^@��5�SJ�j\=��T�Q{[;���] ��oi�3�����1�t|A���� �h�Ji��+`��v��2�� r1��9�l��M��'$�>$�wy=�pt���WI>���Vb#���s|�E��������������
?k�,��p������k6�a����k����sR�����wD�������/������$#19C�D�����}M����2�t�w���:�����~��0x��WF��=��ge��:|9b*J>����Ym�&!�C��Q����9m�_3?�g����T?(�����j&?��
,������7�p@���J4����M����>&c���[6�������b2><>���}�yt���c�����|k�j`������>wr�
2�(���_�(q�A��|��D*x����u�P>lpz=�-A��>���-OdUcf�dT���
��]
����<0�U#���������M�/B���7��x����C��g��"�6��3�A��uz�(�b��D���Z�����B�)����
�H����������l�b����G	C�/�!�38h��B�.B���v��x#��<&�"���������{?�������&���O�d�+�N�[��K;���]�����Oz;���^�@Y����^��>�=0e�Z��/0��M���s���3-i�!U��z��
��o�J5f�m�������(��iK���A>\���=����)k���[������������R>����cw|��Z�/a���+l��~x��{��_���3�`#GJ
�r�t��v�^�����Z���s�$��E�QU�99qy:w}gp�[��6LE�
�����Jl����TO��&�{�.um_��]�]��\S}�5������VS����%�����m6��P?:������u>�I����+�"�����������=��+���v�]����J�X���R���.W���B��R��{��r������
+uVp7��RWb1V�����"M��
����s���,}���s��s��h`��2��k{����VK:��q^����m���0�HY�F�����������=�}�ds�Jj�bz��H�����A����������L�����`\�	3	��1��,c/z�P���&�$D����R�b��r�\����������ng'1=Lr#���H�8�
?H��l>\!d�a���Kd����DxX�Y��Y�l��%��������c�hW�;o���q?�u�J��E����\�s��|u:���g
|����[�5&��w}���x���V�Hei2%���$B��3l������taDR�����*�%����/��j*��������=k���*����|~g��_�m���fRZIG>U�����'3��;�w(�;DW���B)�@�V��6k�������LY�)Q�B������gN��������eE�@��	�i+��T�M�~�u�6}���sk�Rv[-k��o�+��~g�T�t[�tlHk-�i%YI,�YK�����EH5����Cs����� f��nC����_[;�J>�.�2�MS���c��D-q���UL<�
����~}+��9�-��z�I�um��2�'���my���[�!P���N���$��'��UI�ts
�F��N�����A��c�+z�ok�����Q+YK=�����?��@3�R�e��m5~��\��N\h������"��PA:M|<�D:��TR���B��k[�|��m�u��u'P���&��Q��X��l�O��@go�k(a�B�S:E�EO���6�2�a�)J	pm�n��z_�[ �p�&aYF���)/�������5������u�[i���Zb9�����xt]m����3J{�"��s�	���jK�[S
H�(��=�KR��(��t��JzLh(nK(���6op��Q�Q=9{�Q�r���A�<@�E��������gg�������/��>��@L2�e������b��BS�c��ko��'�LWF0�ZR��D,a�C��Mm��0�*W��\9x�Dg�Q�Yk���B������E���bM&ef������99;���������f��z�����^�����^���.P��re��,P_S�%�N]�e[8�?H�����8@*���(z�5��:�$�&p��rD�����q���jX���a��2T����8,���5��M�������������'���L��BS������7J���V�l��
�|��������"c���.��<������	�&h_8�����lN�?�^���0��2j�gE&���J�H)���?���N��e��Nh�P@��{������v���%��x�����$�� 2����Pl���nl)���Z�����d��e�oue����9�m�"a��7�7��[�)�v#��Riwge�}�?���T�K��b�[����.H�{��%���N�S>h:��BkQ�����7+=���tX�=��O�����+u�7g@�p����m�<K���/���9�n`�~�-���g9���|{����f{?(GL���k�]�b����������T��D������n�r���;E��l��#���W}�1c��6�5�{��z��?�����3BU�"C[�;�(8��l�S0bj>pV'k�������������������P�����]�(=����`z/F�d�@F��2'��.�m�0fF+�u+'@���dj��*i4 ����4.��9���k�-��3�N�m���/q�3�����
V�ja���:�g8���q�B�MP�W��������
�����@	��F��$����0�)	�s���~��u8
j�3��h*U�
����K�n3U'8r�#V(����g<2��c(����h�������K��L�<J%���������b�L&$cC���x�����������xn�[����Y���>�X�[B-@6X{�Y�M���[Cx���X[��ts�cQ7�;7��0o09s)�b=Yi�j[~"�_��E��z�kB�90v$9m�`w�2���}W�|Z0l���Bs4�[��*IG����Lr��4�)A��/�-q��!���e�|e����`$$�R���>��_"����M��$9��Xg����XF�u{���B={�Cc��	eq�>���=����La�����
*> �c��|�@{B<#��F�%l{6������(�['(��X%�
���_9	|2����������u�c�y����s���X�W[��W�� =����G�A�D��.��V���)��".T=��L6�5����������i��\�YVt\���!�#������Zdp���-�m���L���\U�vt>��
�
����������.+�����Re��������P��^w�P�v�J>�����V��{�	�O	-%A%��������'��JZl��;��`G]o�_v� �8�����f��[�yC�m'�$�=�y/�'`
a$����Tw'�t�w!�z����{��9�LY8k�0��.	�w(��M��� b�����b�>C��V����jr ��g�2���P9��+{"�6����-��G<C�
:�b�����h4p}2�4�-�06���gd]�P�h��
�-����cSnQ���R��;�����KH�p� oRN����*�=Lk�U��'y�K%��d��y�@���Oi�HM������P&������&��M� �����������^8���&|-��~�q0
C{������@r��(���8K��w#-Q��:��)�����t:�)b`{dz��H�+/���x����zx�{�����#hV:�7h�5<(�F=��G���&����Mx��1��1��B��3��h��?��9��h'�$������N���=���!������|S�6.�kx������Ay�c��/(���2Z3t7�hXGc'o�E��K/($x�|Y}R�z����������	c&�
�&-'�)I�q�RF^�������q37�V�X��O�c^]��Br������h����1I��d���=�]g��Dl	��9B�������S�����i����j�q�������
�"%6��^n7Y.e#��>%.�_�BO�h/KN�C<U��5����1�`g&l�L����+�����/��e��E�@�oBm?*q���V/C�3��QrAAw��nl��_���[,'�H��Y��<�5~FH��0�$+tU]d��By9z$4T��3���k���x���>1�;*N�%���R��B� ��]#�Q�3(3���*�\UV���ot]��_=����~�i�����)~QC)=mP�vD>�#���P�f�����R����#��p�d�3	�����od��Jf7Wk���]�&y4�1�3��������+'��j�G�&0N�>�"i�.��l�=�F��
yKP&(�R��W�7@�9��C��tCu�	��D�����Z(.a�;4b�g�i.f�@�`'��1�IIb�UW�u�F��3O
��$3@G�=)����\����4�n&ST���
���	g��P�������5�a�6rrw�
77��H�����q��L����U��mm�g�& ��*y^���H��d-������A�2$�b�y>��Wt�)SQLbPR$��~MF}rY|����2KB�rbG\0b!"�aIl���s��M,wb������z� �����H���`�����G�"0(sO���|��`��n�E���F ��B��oJL^K�����,YL��/{�x�M��TWG��f�A��+@��u?��{��6��O�$-�K���<�U�wM:�Tfd����KJ��2��v��3���=g���*Z
�G$����_���d�l@�p�~�nC*�_�%T|�7�B/h���VQ���et�Q��U\%EE�_I5�ch�rH��C�E����S��`r���
"������U,����;��[��gn�������N��gJ�<�������|�+zh�x ���}������	�s�N�n/M�>���7I-A�4�:���md
�R�jn��X�~�g�����Bf�ZJ%MT��G��q��Q?=j ���@|&���@>�d�������h����C�~1,E����H:���(-
YN�d#�7���:���S�������{t�P,���y�6�E4&��
�0��-�1@PNJ������0��[�[�9z�tm_��Q��ncF��@��4��.O����v{x�
���uq��Y?m���U�������U�%�i���g����di�����k���`���z�������J��R�Y�C�q|F�iRWj�r�x(���).J����;>��^>�$<+E�������#�Oc�W���F�}����D��VR�hT�H}o(i��J��F�Y�z���?�������V�m��P�u��m����XU�kdU��&�*<��*?�$<+E�EW%��z�&���,���Yd1�i|Ue����/�
%�*V��*<KZUx����g<I�JC���������d�>��9C�U�r63d����@�"�8��Dw������.��*�s�5j������%_���
;�x1U]*��2����:^���;z8�l�������������(���kA��5��5FGJ��A���ul�m<���B�P�<��i�-q�g��rY:�����������\b�����I

Q0����^�:�*�[�a	��l4^��l��5Y�t�������_�����+q�������\������q^D�H|��X�4��_���O��C�
|�C���G��l�3��br��=���#]@��.��R�T�����W�v�oR8}��	�LA!�v�S�2�G����
�y���Yw��������z����?���.���Z�2���x���?�TO�������C1����h������p������$r�w�V�x��g�V{j�W6P�!y;\|��!�.hMr�����_��,�GB�`A?H�8DP���[R���sjT��~-x���F�Qx�1 �Q�����_��o�,��9�&��w0"��������'�<{���L��G��'���!b����@�Z�������0'�k�08/���dA���#C[��+�&D�u�:�����$���	�1�>t ����t�������$����F���Q�Y<����X������q+M��Z�������J�m�i8��v�5�����U=&o�zq�3B�~�?J(���9����ETv36C�Nk��>iT�P����&6��Tp#(vvr-��:�g�������!_��"��M�M�P��{��"����*�j��sh���<1��"��w8p?�;��|����c���al�aw��d�b�8��8��(}�$���4�AMD�����
tG02�6�X(�Y�M���5�$���L������uBk����n�����{_Ur"�9`ZN�<���x���~��o�Op4�s>���DH5��)H�[9/\lI��y��E��6����aB	�f	���:1@�2����i�X#<r'E�Wz�0�q��
\P��N��I�YcC$)�<���8��5����1�Gi<���^�v��q���K������%���99Q�G������/���\c�r|��w�4Y�������U�=�i `b�he������x!���X���a��B%��9�n��&%�P�`�i��m���t�W�|AE�1/q*&I�=��!������-�`-��7����9�.��,���L�K�|�#�G�`����U<QN�At���u��Y2(��2/�4-6�[�����+�LDz��\�(r��;���I_�I�����ct=��}B��ff�����KbJ��:�G��&������	�l{�,	=d9K�z�;�D�<������,Th�)��DZ��N���i
�3c&1!hRGX�6d��i]3�v��;"=+bM�B+���.�����&ETf�&Y
X"���d�+�f����(�@<����bP��g�:���F�>�ae���A>H{>���3�2�8P	�X'g���Nh������Z	-���[��,��-LK���n�	�I��L��Z�H]f�j3������C`U$Y�G <�\�c
+YyAC�R!��NA��cq�}����A|x�������#���Y={����N��q�t�d��.�vyC���I���P���N'VL9V��O�z�����sNM���<h�<�
���@R#�B7 ���yT�L�L5�G(�P�(���TV	ipE�( J8Y�t�2��!���haB����o��l�+���g��2z�\���C�T ���]pK�@��
D��#Ld����@A.����"�S�P��7��-�MXw2<��d��F��-/��Y
�8���LG6��o������@��:����$���x�E���7�\qba���(�� �I1b�1!F�4��1I[��>��eiw��x�]��3V08��7XM��U��L�b���r��@�s��$�p�UL��y�1ei�"3E��<��FVae���UX��(��&�l:�X
�P�
2[�I2�����h`��L	+��2	�N�D��8�>����T,��`��@�?.�Bv#W��EJ�0�)l�P@$/���Ex�
���1�i8�/PB�R�\�n
�>�d�"0������"���	7���QbM��;��,Z�|��lr	������2Y�&�����a���]#��^�#�sG��*�kF3=��0�&�5,���P�g�u�#��^����E��8�f������/���E��'hT�*2~�yx)����Y�`���La_�3�	}EJ&��h��W?�
��X������A�fyJ�bq�8t�p�KwE������[[�D�R��Y�!���eaHB� �b!1KO�5�;a���%��
(��l�P��������sJ'39�%[��U-j8���+��k;S�+y�yt��a�y}/j
��I�\2�Jr�|(�7�1��NP�Y$�Cq���.�v�/��#�8,��*�.�Z=�;����d�X������������D������=b�2������N��`��NS��F1�WEKP&�1~�Ch�`2���DT����<i,>]�s��1N�FB��������#	I�BE��5
��i%F���� Ff���$���!^Y_7�6.���y-����%e����a��(S�NyM��8��
rd#J�	��p���Cwm-,b���/:�F��xu�~s�dH������
�P�����o^�0�:�l@����L�����<x�<������H*���id��u�(�������M�#��@���w	�$�yg����g���9
zB�n^��
e���T�-�>��t����
��>�wQ��h�{�����3� "�(�-�����9S�a2�<�*��z��F�ymy�F�lpc{��	e��<'��_��0�_�Wbg7W,�R��w�L9Pf��!�d$����xK��9�>O[���x��	�g!������W��E(_�o�p�U�p�E����ba_2�S�������$�	��6c���Bb�AL�D�DG��A�o`��'b��:��[�
��D���Q?���DQ�1PU#9[6XF���y�zYB$/*����7�	?f���L1P������>���TO��Jw�Y��������5�)�T.������zB��;v��`c��c�F�v����4�z|��S�k���&��`/W9�E)r�baY����^7Q���E�rzv��;�TtjU�2��)�
�>������M�|L�rSRb�T����\�.�J���|85:i�o=JG$���J�H��u389�������b��3��9���@A���+�z���%
���F���\�����|���=�9�J�E
�z��An�R�(�:��7u�[Igy
/^/�ol��9
�G8a��}5D(��3b SU��6(]������Cdx{F|�a���
u�m(�w��^�h��{9��<�}l���U�J*��M���\LS��gD��2c���IO���)���*MY���H���������f^P�U|A���\�Y���(���'R����us5��;���~��B����n	4�=Ls�1\�W�[��@��P�]<�w���������j���.e�K����Kb �iD��bi�3T9�E����P��R��"��)�dQ�F�z��A�+�q�<����>��2�&�0+Q%UN\	3��y�6:��c���`��4���H^���>_I���t�����+��C������<�/HR�<��F_^�H.���n �[8)���	�U�C7��k^�/N�'Ou���h���l���u|��?_=�^��/�j�����4��	<�'����s���
��&I���q-�N��5�(hH�a_��Z���Bm]�$�)���W��y����bx|�����ih�9��(D�G?�,l"���j�jB���
��8
���������JF���k�L!��R����!������Cd����D��0/�x��<��)��G�p hpE@�	K�F��F����+�����&���b��$��y7�7a�nnmJNu���x����TE �xv�QP������LE���{+M 0\j;.����]�%��X����V����C��tM�D���ndR���f5�E�		}Z�R���|�p�Nj���:S@So����U��|!����x�<���5g��_���#�B� ��V:���fR���
��~�������QN]'d�Y�Xz������������m�����M�������!(!Sk*8O�� �U�4�."�JmI�7��I�5�t�h��p�s���T��I�i��7�H��#���PSJ�.�4':�cm3�6R��UVD��8�n��%�M��
<acJ����
�e%��DO(�����c�%���pM�>�/�n�7��dhF�����(
����mri5RjP5��0��(��-=T	���/� ����W.h�dH:2���
Y	ygU����B��!������7tCF���!9�S^�k�|�4��B����ZN�C5Pj��/��\:��uD��!@^�8��d
_�v��z�!(��+�rR���Z�9��Bl���K>�ehn>7-������a�1�f�j&?Fj
��4�V<���yc`�1%��������1jM#D�T@3�
��QRO����3P�iWG�e�1����$v��}tl��02\8Us�������g��KN���e�9�/'5�����x�:�)���v��d�6�^b����$�G�=�[�A�j"��64L"$�����_�������w��2)"w���U
eR��z
[���b����:��K\�NjG
*Ga7�-c��D4*3G�6sJ	�8�>���.����A�������������e[%�Lf�8@D����&
�lRA8T�vh���f}�5.�O��o]c2��"t��������������dB�M����t�X��;O����@A-���715f�+�rI���0ab�J]UG�^���d���av�-�7��G\��ie�L'/}�'E4��7��Q&�����x'F�z!2k�Z���������0S���{r7�a
�O��@�<{�o##������<�0ad�N�Z��a��R����n�Igi��Z��>�7�J������(�C�����F���6xO�(�&[d����V��F6��Z�Z<	��r��=�-]~����I� ;&'	r�Y1�zr�
B]-��2����H�#��e�[\����Z���
;J��,8�
����%�f@R��H�B�0�Znqy����z����n�-�������5=o��f�H?M�	����	E�\��Y�1���
:�$s?�jn��,��Zq0(��m�Fekm���C�NR�o�w%��<�D����8J��������F=5G&��F6��c(l�#3�f�6�7F�(��������6���02ec6���������XhY��N�:8S���9rB!@=%���|Mw"�<�I2�N
L7U��wT�M
�)p\��A9����y��|�i����]OMF���T��)���������pT���>MQ��
�dg�*��e���/��@��Aqv�����%y�c��z1i+�$���$��&J�b�qm#����D�OJl�xV�JE�Udu�R�U����*R�30�i�G�P����U�Jj�a���/6�/��������e��^b���/����Aq'W����k�]�d[z���P�e���v���U�[�p�s��'ut��{��q�J<$23L-�q����c���!���?�Qe�|�E�	!��\OD��S�@���VZl��f����"W=>�H(;�,KM�y82�7�t !�B����W_$���3�)asF����n-��t���v�J�)�����6n7�&���-W	rq�nY��*)�%uos������!,��(l�4�{�m�����&�-wD�X��/���w�>��v��28%e�H$��
Q. ��@^h�"��l������/_m��*�N�$�g�Hrw��M�:J�{�OT�z��4rQ���6'qv.�G�Bi�-��f��VW�����'�y#��F��98V�,��c�y9���������f��(q��N�&�����n^If�c~U%2x6�H��z��9��O�I��
���5������C�$|JL�������#�����N��������al��><��}��$�}���Q�_PBw���[3�xF�*�O�0����K��C+8����Gw.l��
5�_w�^���>oN����
��-�c���B_��
;���_����U]���+��}����EJY��\���X�����-���*�9����'���*�Y�K�CP�s���\.��������A
�o��9�^c�]������K�����j��n�h�T�1�������b�P5b��b�����a�x����x���4�!�
@��
���I�������)��e2�hq<r�z���Qy`��}������i�����iuQD�������@��@>���3��W�"��**`���W,�_x4�T�;1��N'�#�����w���,W�c%���#MZ���z�-�{0q%*�L\�x�uVB��W6���$����S���oq��E^�p6�;rg�z�H��1�I"��{�#�H�a��E@�
����5��>�9���f��g�)�K�.qA�����_�hL�����n��q��z9��O�$�[��P�C�(��I�����6��d�!4ZD�"�I���:�U������MIDG�QJf�,Y6�0��@J&��CN�f���@� #�z���� ���)�`Gf�j�C�:gc��7����R��@���H<���f'P���p����*�P?H������Kn��
!�\Z��������0���8�.���(�a�u
�/�;(��`?�v"�7/�!����L�e{�������L��z4T�`l��]$�������m��A|G~3��N��q�$�RX.��+
e	d:����15=u�S�Qt�����_j%%2���opCT77y�B���L��m����De�-�M�X�6���������w�����Wv�v8��g�7o#��b�a�c$dg+���9Ykj���[#�b^��B$��o����	�M��B
�nH=��x
Q{v8����+����L{���z` X�edqW�ng^�K7��{�����I�F
V���C���/@���������'�].4����i��vT=9b��EFn��{�uSY����M"���*�� ,r�����y����_y@��QS�k"��Z���2���lK�U����VP>�x����������I��F�"4q�e��vtIO��/�`��f�*C����g9=�Y��w��nO���?ax��f���v�h��b(������'�~?0���R���(���@���l����|�mU�3w	o��P5#MZ(�I�)���+���� ���K�a�����mhh�@)���x���V����g
��&�`��[��h��M5�Sc�6i�h'��Zo�f����P��H&
��8�w�����>�
��%O��qj�I��C���?���!k���F���3L��;<G5�6s=��������1�����'#�R������
:��#s���`u�g�����MD����Zx����$�g���;�)�b�$-�[8@�(���VT~d��)`���v;�:uC�jH
����m����I���`y�`��G����N�d9�l�����TZ�i�_Zx[��nH]3d,�&�wC	��)��2������F��d9
���g���=*U%8�D-��H�R��Xk�����H��R���Z���zrY�Q��*T%�TB�T�Jf(4��s��2�t%3�������naS��Z	I
?��*Y]^���A��sO!"��-���s��y~�KF�u�9�H���c����>5f����":	��v)��a�`������Q�~�n�l<H�_�%_��Y*W���ri� W,��V�-�=�����I�t�}W�xw�8�m���������	�D�$�F|������p��zk�S]�aS��X��$>W�i�4

��'���f6��4���d*h�~Zo4�qY;�
grQ���Y�'�o��8����O�~��5S�+��{F���	�lx�����������)*�����3�,B��I,#��&2�������S�h�x�:�3�������x|eO�\n2<���
�EVn�����N\��x��2�^�I�E&�0�,���f�G/����*�a#�h]��O�sA�*�E�m��Q[��HF�0~�.��1	64���/]�8��@����5�@RN����N��Q���F��@C/|��%������7���>��=��Y���`[�UDh�5";0�����t_	�B�$\��2 ��I��������I�=w�
}}��O����#�=C���� �����&0!G�J�V/���C+�A�>�-���?��z��[yu%���q�rKn��������,61,W�2�����LR���~b.����7i.�3L�I�^�n_�P_#���������LW���K���6	����z����@�G��Z��Qgqv>��x*����QVq@�(��&���Us��v���%L�^a�Es�oHc|����$z�$q�(���R�IM#��{�F���GR�t������7��_<�������8����rp5�$���	�"�
A`k���l?� ���'xKh3�4a�M���z[��cD�7�c�D���$)��C8�U����i�V��g(�MTAm+���FJ~#����>�j�0��I��C���eo�Rt������H�R�[��L�,������^n���`����/��`��1�~D�������h� ��-�K��^�����G��$PCdy6P�R���P(��acm���>�3�v��o����`4f_���?���J��f���&��
~�v
�'��K��_��B�\������R(�����"
�1�B��v|�n��M{����A���-�
����eu;v���_����A�\n�vZ��^ko�]�Ab�����'
�C�O�`5���C��>���x������~����]�-��o����������a[�
��T(Dq��P9,V�V��PX��������j���(����"��7v
�N����#`����SH�\_o�Qo�N�Ic[�fK�����st��� �������aN�k������D$��B�G���;KQ�H`N��q�Qq�����������F���r��x�p�'���������[�e��s9��<U�-����������w����P���0j<On_[�+�sV�|�~f���N������S���������jM-�����b��o�v��b>�S.����2�����omm��#���������a�B#>��e�#���l��x�8�����>�N�k�G�5O���WO��X~���Y��Q;�@��O�"e��6Zk��y.`]_��|x�fwp@�����n��[����������"�c�hQv9EHL�KB���^>T�u�u����q���ZS�H����Jv�}p������w�I���M�9��]y�U���i�Ux�o�}q����`�����_l��%4��[;�6��X��&���]���pV5�q.��_�7�����_�lOC�O�K���H���)�H���DW��P�O�R�&�;;�b1"��������$���v����^y�[h�Z�;����v����N�X���mM��~�S�
�^���|�r}�������c;'J% 
m��J��B����&��i�/�J!�e>����\�����(�^~2�dq5����f���@kx~��)�	F6bN����'�fy�s��|P�����O��:�����I"{�\�o��'�frF�k���,1N���������14m67���
vBy5�L�dhNu���/�N�r�:��Ra�HU[O[���i_8�g�F�#u�@L�RR�������������c[�m�Nz�>"�Hx������/���d/]�����c�4]@s��eLe!��4h~Ebu�X���8F��R���R�:���s���5������t^�E�;��^W��*J�;�����"���klG��%�Q��|P�������)��|[�JH	�U�������J����-tw��By����R���P
���"���/�*��I �<�}�.����C�,_p���g	#!/DN��9���@}�t1�&Y��^zm�ME8����_Q
��#@8�U�����LT���L6������(�oL���=���x���mSb<rz����K�C���ZH���@#�|a����l�`��f:�z2n�jN��`:���A�'{S[�� ��F���H��$E����cX#�\��=u�/��1���n���`���1�P���M���nQ���<%�h��a��������!���N���l�-&/���o�q;9s��Zn2��g��WYN�� ���}|6@���c&!hrpo�����o
�:��<�<�oy�������o����u��,��3Ra=���p@�j���xb�Y4%y�q��,��;p���4�b��V��'��anw�!�?T���Z6�gC"9*q1���|�7G){9���\�1�`��k�~�����5��2�B~��O���>������sL�����Ya���m�F8H�y��==>����q���Vm\����g�����g������$�x���l�.5�P�J>��L����f����F��k����&e<��I���F������c>�hJ�W�`3��2s��)|�QNg����`qXB�	C���Y�W�9
�6�5�~�����-����X�FY��#�����*�E��Ox�����
�kJo��������^��C)�j�~8o��~z,�������Z��qS��i��9$E���eK���D(��$�VJ��4�z���PN��g?��]�Q�i�z�X�P����
\��=�E���~��Jx��t=8#�;�����Sd
)%R���~��G�x��=�e�W���
�u���k<E���>�f��n���F�%s� A��������������Cl�P���OP��,��D�������]cLO�%�G��������)���D�9y��P��L�����
��-/�����Im���V?���"�"txL\�������jv/�F��A�j�Lu
���>6�
r���v��{���G$��b����M�k[�����"N����)_P0��M���a�I��=j���9�yx�S%����]��VT,T��b)��/:����$=��l�`�
�t�I�.��vB��Y%���%	q�In�
�O�=�=���2(����h�qW��Au�L
��&3��WG�6��z�������wWmf�(w�9�C^ysv.=��_������=����-aM^��R�����Ln��(�P�;:l���R���	�>�Hm[�Bb+
�c`>��#�OPW�F$��YT��	�f^����6�$;��f���B�M�����A���	
�����-��O	����JP=�����H���n
��(�?,c������s�B����EX�%+r��^�Ut�+9A���Q�����1�E�M��w�������bF�X�
���j�m���~:�R����]���;��J��
d1�@Y���^�w�����Pq{�	f�5�P����"����������[=�l�
��'X��^�����n���j_E&�4��vl�|����o�������������]_�U#��������0��y��z�` k4Fo���5��_��up�ZTY�?l�����"�,��� �r�B���~����d�
��'A��<��BD��QR����#{��R)������9@�X����nf�p�l
2TR$���t��6u���9�3>C0�Q}	J�����}s�����r����i����gw��9�y��jup�j>F��4r`�T�kd��Nu)��������S;4��0
�J���h��8.�!#pZF�R�x'dR��ghW�<U���9�Jo��o�)e�y�a,�s��I��M���&s��T�})���U,�Hw-�vrw��m���g��+-FJR�I��� �JQODpe�=tg{�B./�c�V.fJ-����������Uj!��Q���%`���,��<���?�0b�U��V����������:U�9�B��y��X���;���f����Z0e���"�����a�x_8���;�\�� �-��������3�������0�vX07,
�+���>8/��p���_w�����x&4�"+�$q�������q�T����@���3��JB���$@%(�T0V��J8��a��n��
&�t�w��G
H��k�Am�J�n��x��C����[3�S��9��\������g�$�R>��f'��w�z����i��~��������N�+Qo~��rh����&K�a���{���DI�����F<�M|�����})k��'�"{����.G9�[S�#��Ih����������S����ni/W����f��1]��18��&hH
���Im�!�
�B�`�i����	?88rnl����2��l�LBc�>K/d��z�`���3F��
��Hr2���(��H��<���IC.hNb:��P��G��(���
$�>&���@?��iN��<KS�&u��i���]���������n����G3hh��fPH��r�>YS�(���;:r����mg>�m��<=�������1���[�L��Ce�Hp���g2���P���d����0�p���f���4����p�&U	z��hp�������z��F~o�w	���2���x���#��<L�k��}��8�7IvZ<�v��I������aw�������A&o��I}7���;�����]�z�?��C������;�x!=U�uS%3|�,��G�P.��FW?�������xJ��kf����6�+�)���G�>t��J��$������7s������5}��]�?z<�p:���!�0������nI�9=�,��|ww>Z3�"���}��F}-yD�(��7}�����O��>]G�,�$�_�
�(��c��BH6�� Py�"��'��K�S�P�EE|g���}
��>�tK3��;����s���������f9��&I\�%RuB������9*�N"���.�t�}t��@:���w��X]�������� K�/ZZ����u
��g� �S�H�L�M6�>�	_�C<K��|�6BC��){������<���"��@FMs��w��S�okF&����^��s�!���������y�pB����M�! @-)�4���+$���=0i�j�e[��k��}5��nGt�{��4"��B�U�4�����5{��jZ������b���d�K�7��wP��~��=i�Su�=�\����@
Do� #}$c@F�Y�1C�x�EO����J�����b��=��Am"�2����L��QA�]�e�h�2�y��+%9��eL:�wT.�<�����z����j��zM�������\�m�^�a���@�B�b�J���\�����nS��c�s8����t����e���������L�u�)�1��!o�N�f��W
���J�@� 26�H�7O��w6��iN�����p��5r��c��<�E.$!����}�������h-�qJ��I��)�A��"j�����[A�M�H`(B�1\��_�ay����f)�,'��j�G�f��'��z�yT�-����������(E����T��T;9��O+�E������N��7�&��hczB�O8��)^��}FzRV���L�|��l1����u����X��{�e����lp�6��i�����(ya8'�7i�����R\Q$�+�U)PD����)����
�cM!R���5'�{��/R ,6�o%�
`U�(J&�72E`&��(B��~��'w(�S*S �rd,�mM��zz����PQ)��5�����\c&	�^w��	��s.(7YG^J���V�BP	��C�*�D�����.�ZD�����N��LsM����O�f$|i����O�\��2i���CD��<-�j�}q��b��f�`O�����yv�wL�H���K8�mC�e�E�*����QU&�R}@m�P"�[>���V��0=�I��_/��O*"��>J���M�H	L
^��]|����8>���)���;�w���]�ut{6�a#i�2U�bR�����4��v&�0�#��|�A�Q�-,��L�Jw���b�0�$��:���X_r��'������`{��E���m�n�l��~�����'��N���7	�.�������c�(�V7�
&�B*)x<���4&�U���4U�<H?�Ft�����i6A�{b�����w?zM�QV8c-j��������FLG�'�r����n��"�4�C4�=�X#�(5���@�Y�:D���}��v�n�C��m51d�X���P����\
�X�V ����%�L4�[����:��H�Ch��F��"d��\����06�3,L�+>�N
b3q��u�M��	]����1����:��X����]�S}�)���)G��L��MF�('9��9�-^pl�2��!DP ;������
S!c�x+�H��ZM��a�:�M��HC��73A���,f������L�Ab�@�%�#���QFl���
o!q�l�}�KN�_:��8%bU�0���3��C>bJ��
Z���N�^���M��xX�S���&�`��(]H��5�Q�-N����}=M����\��]��SFf�w���W����	�	��*���WO,�0D�n��[f>-���%WTwQ�4��������%�
��Z���)����4I���_*�iH��+Yl�Of[�|�����b 1Zt"|�Z���!$K��f���"�:)��9n�V�2�k������]����9��M�����.����p���������n�$
�J���L5�g
��)qT]3�W
���sW�fNy��w��#��w�Xp���*@�.;�fTk�D��fQ��5�i�X���������7h�����&���Mv��F����hi��wb�7��p9!gkJ�pY��t��������Z;��n��`MH;o(!�x�!���eD��s7r
����20�=xx������F��2���!��w|1��D�C���*�����3���7��)m����w�R� ��Hx���Er��M�)�e-^��2����d�����z���;rPY�_�O�K��^��:n�T'�jn�j����A	��2l������b,������*��C�Q��r��_�?(�Z���**v��`uv:;����:��;����N���
2�{���ai���v�O1��'�����������Z���P�X�����7�<�	Q�P��|>�u@2������$>���C�v�*Mw��Gq����19�.��7���J�v�+	���E%��{�M��3)����&�Tw���A�b��������^���	��3��"��+|r���������w�����/�������z���s�����x��,t��.��������/����z]=��/�Qh���kK���=�g���co�� ��x��TH,hY�7tC�%'�:v�C.��s���|*^�q1$�|N_�0x��g��h�]D��r&�i����C*�����egK��A�(��
��r��+m{|�tet��@�6h�A{�|��9&D�g
�����\������������;0m~.2/{4���>v�_�0}���
������Z���Q���R�w�.�1�d:K�La^��$
MGZ��vp���Aq�E�����nOu�>�*����&4��'��2���h��\�A����qlc�������%n4s����X?����>��nEp9^w�z~p�)^d����h��yF��
�����[*6�7�#��\��Pl*]J�
��#t	u����� �)��fSuS�"l�RAu�h*���/�������u��A�T#QR'2 -g�3��t>:]�h�YA���~��_�)u+�|�S���J�ni���p�^��2�aI��p���g_��$,���u���;�����^���
G��j���k��^=��O�X�
+�)�o��D��%B����7a�^��-B�S���v���@���@��O ��S�����?s((�Kd��T������Pox\���6��6�}�5|\��Hc��_��w"3�
�z�������^Mk��
������9���t#!�[�-Q��E�*qEg_?���@n�	��aB���\%oU�������Xh���(��������h�f�/�<d�Gn�M��)���|�����Z��5���)m6����L�<���]���KnHV��W��,��";�/�����I�\��Ftw��������.U2F��j�m: ��b�������Z+���G���-���/6B������ny��f�!ZhJM��j{�����%1�%],.�����vQ�es?U�F`�CP���]��\���'���o��.?��SW2�f��A]A��m�Z}��o,sB�DC��#���LPu(���A�K�WR,������;�!���L�@����C�h�b�G��?S���7�N_"��L��+1����<��c�f�$V=[&�4�I�xi�����������SC�o��r�\��'o�.u�[Z�\��
7�$��Z�b�(%R�E��"p x��6=��+��=_�g������Q&l�������~���NH���������M-����+#e�Ej+p�����z�p>��*�sL)'�o�7�z!��MXy����7M�)Mv�I�Mc}������qK���)��c�:S>�S�D}r�������=����������o��OF�� �"�9�	��6�S�l~9�-�'�Y��^�����.f���N��r�����f[�G_6�J���|������;7�<+��&�Qd'�oM^����8Kq8kZ��1���������>����A(����O����PiI$ ����S�^o�������S�A���H��p���g�Y�/T��ZB9�i�5���\���`��D	��4�{��"P��-\�"�]$�n�	�?�g>g�BK���3���"8��eX��
��"=��\��D\�����2���p)h(EpH��8�B<5��%5�bd��UZ���V��W����L�n�N������x�����O]���y������t*4�$^�=aD������<�!��V��&��I0t�P6p����#���D1F�O<U��,_fY��*���0=b��C{qp�	�%�g�9�l�S�����dc����q�9f�qW�7�/�b�� Y��4������b�[�rmY�E m�9O�b@�Y#2��z��=���PU!e	z�\`%\�"i����Y.���G���/���/'��{v������a���Q��^W�z�R���y9��<�I�P��'�����UN���c}�^��%�U?�O���t�#M��P#���A
�$���;� �3�FZdw�q�Z
�G)��A �)� m���".�?|Xv1L�)�������Ah���h����!G��	(�(�y`L�%v����\����/����_r�o������z��~���E�1} �`���R��/�x�����5����H��������6����5��!^��������$:�,���I&n������9B1�_��X����
\�r�"Y����&�/g
��+#�28���k�������4u�;UEUK	J���_q��������_N��Ii�w#����;�v��1�2�d��wO8g�#�0��g���%8Q��S\d�Y����	�.��Oj/�0������y���Op��E��,��{�����Nd���y�]�F�C��d=�K��N�1���(��'�/��Z�p�~��pP�?N����/}�ZDA��F��0�����lk�)16��j�[}e�h*�����Q-u{�Y��;����$_��[����5v���K�^�����.�)���^��C����m�<��
;�h�[�B�r�{�|:�&a]C(����������9%�#��rr
��Ynu���+��^>�i��w����)>�SZNq��RK��P�h�n��K��K��N�.�������$	^��a��tIst��G���Tpi��t@[�CS������\!1wW@
�R���������g�?Z�3"'���L��n�po�C`��5��S=�e�nwEQ��J��(�����O�&����t�7��i�����U��p4�/D4����;��{j��y��c|�.�D�f���$j|��.�~���}	����x������������o�"��Dq����N�o$)Y��Ai�U)����^���V:�I$h!�Ro�-�|[~��u=RX��z����/���k�h�h'�&��&�zC�g{�R,(z��s�n��]���
��j���
�!HI��I�$��������;����>�zR���\
�u�s��X�Q��u��	��}(K���^�mL�Iy?��;��B�d���2�������-�Y@�nUfn+!d<�������cC�[�������2D&�-�;z+��&�&m��l�����(���0j�.)G���J�@��A��a��t�N�{�� �n�i;0$�sh�,M��8����ck�SU7B�9�4O�����\���u��E�y�������b�o	�%M&�I��vt'�w�m���k�3����
����%��������.�0�1�n���T�����xwVwd{���Pr�~o���(�����C^�P��6f�&7rDq���&�������������[S��j C���<�$]y�c����2<���q{���� ���g�\��&�0�'}�,AyUA��������%
 d{Q}�5�V�������s	C��1�l�f�I�l;��g����c��!�/������-�
E���I'�2'�alQ. ���N3�0@%0�4���'��p���[�+#��L� �Y����&�K��c�%���.�&�9bK��LJ����x�;����l<��a����F��v(���14cVPl���F#��[���
h�(^=Y0'�v�������a�r����#��J��&x�E���C�L�b����������y3������T�T]�+`3:(-��P�R�HDFSQ�I�A)���S0����-��N�hv�j�s%����e�N�`���
3���`B;X�^N8Qj`�o���3���"��n��S��&e�yq1n_�)*��CPr`��bB����������6J~Zcv���(Z��	0d
@��,-PUB�8p����c�}������zh����o��m����f!C���!�2����K�0�*
G����u�	3�M������U�4�R�+�b�
��tH`J8�����d
|�"�8��K37���C��)���00@I@�^���(��l��!7%������D���w}noa��V����e�Xx�G�{P�����e��M�t���'����$��89��������4�8e��h�)����!{�)}��6�D��YC�C;R-�\
����As���A�j�E������r���%+<�V0�����E�������B��� �>v(�O���RK���R������r9�S��E�P8�_;��\L���{+�.�xDJ-�������QA����g�@�@��,KN�A�~m��aTfc��O�y��@z?Y����\�����Mz�.�����:<\5{������Y,����9A��g�h����#X0�g�a�R��Q�e���m�IQ 5�sF@���lq�7�wU	lV�*SIK����eWvFJ��[�?�&��b�����f���G�����`����wi�W���cQ��x3g��,1��|�%�=]e���q��WpE(bz�}@�%���K'���^�,g�j��pj
�I�OXi���qph]����}�����F����>�;��*���
�(B���/��R�B	�ny7_��(I����'��D����1w���t������= b�m{�O
L7�d�$Ct��vTX�4����.��d��=k�4<������d��$�t��L+m0"4
&!\A���#uJ��V��sIu$�I��cR,����	�:Qm��Hkff,����X�\�]�Sf�H|��}�*���+����K�����N=i`u����>>g�]���e�*�m^TIJ-��[JIJz�
-�	_��!����/��Q���#%	�#��N_K�i^pjw�}�L���H����i{G�P@/�4A�
O�^
�!����1��!M����:�@cVR�����W��p��U��,`SJ7QP�r�����6'�������$d����(�|-���rIs	I'�,�,���	E�99��8�����:%��@y�x0o*q1D���s�,&�h��"g�o3�#��,�p�,j	D?��itnM-��I��g���knG���VMN$��'�U3*u�[$�+�����7��[�]�b'�%|�5�C���X6�j���p
e���9���L�5Y����Dd��F���q;^�n��:t�l�qoD3�+?��hX��U/jQ����N����c.2�B������w�� _�SN)c�	/��=|�d
�>'�X����E��$�����+����K%3�@I
�C��R�|MZ����w�x����9D����J��G�g�!������x��I>������`�q������Hi%
���|>Y��i�U����0�'�v�����9�c#����+�W}j��\5r��iH'��R5Mq��Pm1�L[~Z�
�h��8%�6fM�[�1%��mY�OAa���!O�c%��~a���	�NI��Wc)(U�CH	�m�8��c���\��R�N5��$M�%7K3��)�@/
#I�F/Kx���P2'f����2�����n��F�0K��S�������e�������
��HGB2~|�k�9�C�ZT�����+�mTN��������6���b�u+F��;XFOW��|��^��
WXt�Y������{���`�~�V��P�G�1�����$�$v�x8=��E��<)�@P�����e�G�q�sBc$z�����h�
���w�~	��#^��HF�s�+��H�+�>C�:rxt%�*%���HG���,���&E�d5�h�MHE�V��gt=<?�C%l���H�U�*= � �A:��|P*#o�����)v��4�N���1���L��+UO��1������]�|>�@cbF�E[V�cryE��hgY�0d������8�1�'	p�4��dVHP�B�7����[z{<p�9t�U#d�{�q�{"P|������!���9s&%���Nf��|h:kesr`�k��]IhKg���&��2�Y���m����(q�s��e���C���*7���=���/�.u��BI����@��|tNY����z��x���L	zt��[	���O��eL:%��S}��N��D5H�l���]���N��O����Q�y�������=��I��(�E�������!v���3"�x�&9ik{����I�j���8d���p@R���@������=�������l�/�U�l��{��Ax�aUS�m�����m�<�B�������*�-'C����o�A��.���6�q�2���D��������������	���@�VW�-F:S��$������%�S������G+�����f�u���<�	m�t��t6[���7fR��l0}������6�	
Ld
M��3�#��
��0X��c����=|93p������.;�
�5�B~������������._����0���g��/�����.�����������q����3�hI�s+(��j)��o}�B�M:9�u��Q���fN��6�
��\��o�������RS��c�[��v�B�(�!��f�E6d�&<r���$�����O<x�D�������&+����Ri�h�op�j!x�������������{�p��@��Ze�]��p�	����qH������d@�VT���5
jW<��1
9���<(�zt��G�z��]�v{�)<"�R.>9<h��!/�&��=�����&�g�-�i5ZWnW4�_>ztV�����wn���9�����c�4E�\V��Z�('t�����8:{
s�f�D`i[eR�Qq�0%�<r0��7�h�A[)a&���c�jU���>
3tx����G�F4��<��#k?1aJW�`]���}����>�������V��L�E����-���zP�g�i =�&l,%��me�x8�@)�Azu�~gm�2x$n���;3�`�(
5���,��%�g%���8�����i���&SQ�R�FL�H�4��|����%�aa

��(�eg��e>F�/�+���;�0�Zv0��Rp��k�*�����1�2�$�%����e
n]�xb!v��Cu���� �RBTK����6��XH�w*����C��<����5��l��k���3
����2�m��X&<�xq�D�qO��� �ob���Xm6_�~�~�Nj�k�R��m�^����������������!�����M��~^(�����6��R��T���+��S�9��$��
~�v
�'|+���{)V
�ri��[��K�T*���"
K���7Fs��_����r���o������N���
����~{����98���/��:e�].��Re4����(��B���%X�ul�P�0�����������~���?
_��:�fr�X���K�~��b�pP(�_�[��(����8z'
����	�J`��G�I�?�__�A���Pg�|�8��� ��dR�0 ���S+JjK��k��SXL/���Q����0���vd�%�G�+�4RZi7X~}��U�<������ ��{W.���>���0�'�?��x=+���,�+/~�@�hZE7e��6�	�z���6��H|3������D�9	N�F�!������`�������%dXlpU��W1��<��0�9�u?�[�GX(��R��VJ��-h�N��]����#`,1t���]F
(�H�g7`���t2�����bsj`)FH�T#X�-2HALO2�`t���HY��y�X$6��Y&��.����DMg�0s�B�r�Q��6�}$|���5'_��G�����c��l�6�_��w�	���%�WJ���Ne}=LJ�p�{��v4q���S���57C0X����V'�6���F�6�"U����iw}�e���������/���?gi�� ���/��/��LJ	e�������D��}���]K�F`g������lg�������5����mww�{�P����nigw��)�3oOk�3pO+�����9������k4#��}�a6����o��\���o�������������/x���������"�A^��q��s�?������=�~�U�cG�Lv�O�7@ ��7XT�������n��u�)�9%y�3��������=p���M����2g���{g�0
�#�� `�]���Ni�����b���X����)��
��2q��Y{��:������q#Z�
�0���`��W�p�(��6������oC7@��2OL��}�7p�&R�&���fq)��gh�yH�����
�r��7yjj��9���|����Mx���!lh=�Uw�R�-@�������^�R.yUk_�6C��,��g>���*�
)�5'B%.F}������5��^e���':A�~
�����o��M^5X4�J���������7������������3��7��JS3��D�,���X[�A �f�*��q�D����`������M����p�����N���S�\	8��k����+��D��74
����eq�-Z,�-K|����M�2����&������|S�6.�k���F�������F�8���H���2)72��:�VF��|����l��|���@qs�?^	���/���������0l�cyH,A!�j_gz����'�y	�;��B�XZ�H��F��8l�����v��������&G��o������)�^G[=�c��*z�IA	8�#y�E��
�33�O�[
'5$t�n��VNAK���	Z&�6bK���o=J�[�R��;(.q�~��oj�Z����oH��x��9H�y��z�G
�X�����,���0��"T�i����2]��f�ys�~J��~M�g��j'���g�zm�~*����M��'�=�.b�<��9+zt�	Md��	pM�����������������75�K�6��E�����A���q2$:i H�`.5j�c��2�yN��k��6�0'c4����}K�
�X>Z��{7Q`-�p%��D�.�����r�����$�a���DUHw����5�Nm���g��(��&d����~�Ni9�7�0hg%pV%\�1���� p�Pv�5�3p�^`��'���D��{zb�c�k
�L���-��g��7�U�������	��-�7�9z����7#!��'l���#�t���q�\�����?i���p0Af�Sd���fH��9�b.y-�����9�N��,1p��H���������hO$@3�A*}?|S���-bM
Yt��h��y�!��7�UYc�("9
�fQ�{�X����e�v��t�UGn�����Y!�L	��3��+���Z����M�!%w-A:���X�R��u2�7|l�x���iU�.�D�;���+
c�SSYR�����u�:�M��=���{{K���E��eC�0�1�>F���{
��������]A��B*;�?n+������g��}�<4MaO�KJ����r���R.�*�%�%[���o����-�,�<,��Yr�������q�t�rm��;����|5~����5����������27p�Z�L���(E�|�)mq����9m���B����p�\�D��)��=�����+<���������%Z���~��O����
��Q%B%����!L��n�j�g�__�m9i�57�8 �U0�S'���Z��1,������H�~e~�Y����hpR��K�`@���|��Z�`j���`�o��~���A��p�2�K!� a���^P��n�;�n@�C�D��A���1�,*�x6��5�a��;=���8���~bv#�f�n�	�$�����G�����j�����n�1�. OMw�����J�]��;��A��g��NM��&���,�0�����yjjj���������K��v��6k�����5,�K�<�6�bg'��bn���i����~��<�'?�m�A����B�J���|u�^�%�\5���;�x�a�e\xI�%4m"��x\?�5��7o�#'�O����@�	��i�3�g�	�W<N�"�E5V���,����g�qF�b "A��9�6���=����s B���%�	������,��O(o@M�Hw���#��Vz�h�$
xy&O�����������
��r�&���e����������v&(:�������1�Re�����q��c�0��~�7���b9H�H���2	v���3b�JF�����"���������
�T}��5z��"\�L��!TS������Ps���@z��2�2�Ro9����l���%��K��g��nMR����<&|���@�����0��0s2�����@�g����X��x���rm"�x5dVJ���H~K�b�X6\5�o�Y��3��HS�L���g����6���xM��P@!���k��s}Z����5A�y��p��h����8�rHK�
��!y���5"�z�j�gj=a�L��]�0
��Q�#��<�i�}���&�mR&����b�W�[}F8l�I��!		�K�b�!�h-uY�f�n$�t�+����W��D��6d������g�lJ���K�K� 'z���C��>�O��!"'���6�l�g�%�]��$<�:�����T�yI�@�T�#[�N�
5�l�TP ���O���Y������~A!i<L(���Y"2��I���
5�s���w8��xv���m���V����7���a���f�/�:r��H��#$%��5�����'�gn<��7{��5��q� N��e���	����iX�N����P�S�d<d�&.M�m�"PQ�����j�Gy�5
�;
���P���w��oLm����n&�c��nL�	�*��Z(�ZQ���';�6�'�����3LG���/�y�����Cqt���b��d� �rm�����
��~7yy�!��}���R�&���n����zV:@bY,�W�Z����gM6���������Q?;mV��^�k��HFy����>dq����#�B�"�'�"����	����&��^���x
���,s'��&�����r����I�#M�E�T�Q+ll,�w�\�����������'y"G���sW�J
�=�I!�C2E%��M��T�<��;{`R������+��N�S)�J��qD�t�1mm`�C\��.���������y��Q;n�}��W�2S%��s�/L�����S�R@qF��A� �l������H	�"�N�[|���;GE������Q�; z���?�$�G�T2|��g��U���
t��&�c���Nb������p�<��)O"�*��rxP����D;�EB��r�[d-
~����X��f�E����B���@��AK��|��"�iV~G�TD�#��$q�9@(vwr���v��}p��<
��$������:�y�O�^qz���q4Rg�Z5��U9y�ED����Eu`��������'C�����U�'�@I���U��&iT����*k_6��I6,��~�;��������a�PJ7�(�5��1�@5
���)�#�KnD(R�SH���T'�iAP���G2�w��B�����d���j����
I���H�y�z ����8���LY6���DX4��� ���.Y&�X����N�#�8$J�5\\�BG5��O���~��;�rEb�	�&��mK)z��}H�}������zt.��*M����
�!$`�@�����W�GEi�N��E
��O�Z�Gk�"�t3?�#�:a.��`�qQ;�5�(���j�x��P7�����C:�b0�
b�����(����c���c�qx����/�8;��dY�?�E���i�oU��Kx��`�hx��"�?m��,h������3Y:�1�4�~�Ld��������
N�*�''b#��|J�
!���e�H����mp&@�L��2�d�r
��Cl67����f�gVT/h�US����3�R8/IGD,�he����!�FN��0�����]�v�u��C���(y�^������1^���Z���(�m��B^��^g<�l���E�$s�4[��9�g�B���zx�O��{��_�
c2�������|�L�<Q}�6�������W�G�	3)H��;�CZ0,M�����3�)��*�fF|��f�n�%�����
�f���o	�	[B�NA9�J��=>���S�1��q,��2�f��uv��r��L8��2����>�;
.�U��|�����P�`i���X���9��E������PZ[���P�xV��:xPuS���ld2t��,+���lV��I���Yj��5����7!G��������Kz'��+���X3@���c�5t,2���/�-\/M�^��L�����8�K��	��)g���T�����x��C� ��Z�����A)�t�=���
�9�����}%�
�Y1D��v��u�I��O�����TU�\�����"$��?���G��CL�~�hEJ����o$'���f�8������>��s�<Z���m"7P���G�I��a��!��i��C�!��\�����������%��1�w��N��!�4�����'��`�uC	Y�M�TC��Rto%�H��������A�3����4�#�-��s�J!�&$z��%Y����Dx����<�;����~����F���&��9�J�6��J��"ef�����o��C �6;8�\����L�$�H�������AV�(#�0�Qu�'t��R�M4#�
�B`P�F����2&�'��5)uXC�V����86�J�+�4�:�!g��d�L~��J�p�������C��dL��
�%�c��A;��D+I
`� ��P/g�.a%��������=���*�'�4uF��RBUZ�p�yY�����B�F:|+S�2���&�Xs2/�A�K�7{A���h�a����%��C�8	`����wq��������%�&�y#����H�DwN^�����a|0V�c#�0g�[IQ$��r$*2��1���Qs�D����'v2r�(����9oCiF�J��i��oQS��}����[����%J%Tb�)S[;�vr�8�����wc#������~��H�[e��
��	=��(�`���USg���QPQ����;HH0��FA�1�Q��P�2���k�/�3��U�����l�f>��)�v'O�����a[�<����K��,�gB�C�0Ow��0��'��V^�H������$����
��gK�����o��� [N��+�
���f$�K5�!�%S��-�#XYq�����������H������(D[�5��������T1�t�
/���.��b{(h7��Wrh���O�����1�)���a��d�|A�y�f���34���*�r}Z;�{���	����&@��L#���C��XN1������YX+1�h\>�JN�����+r����K�6U��Hb-u��O�Dt�1�(���57�
��U9<Y�"0��a��{�	�c�M�^Aq�`T }�-U#����!���J����l"���1����y���x�O�s����r�T&�kq���	:�bK*�T�/��)KKxPFC�o�=��<b�M�S�"RPa%�����
)VY�:}�DG��C�fP\?q��
�|D>K5��HC?�]$`�����V��Z�Q'��(�����'������W9����������Vb)R�h6O�����P�d8V8FQ��z�<��iJ�P��Z"G��
�)��:��}7CK����2�;���X?=���
t��'_�=�J����1Fs,�����G<$1!�������"m{v���H@�5h���4��W
�J�:���T�9-�H����&f�nu��~�Q�#���7@�qn�|�c�+�tu\##/U�=B�{-�� �P�
����B� ��R��/x'��	r���7NA����d�g>���s���NPdNk�z���e�Q��$������u����LG�@V==���x=�H���Cy6�'��t���9W;���-�C��lN�Tdv�ue����
,;�$H��PC����0�Ud�.o�\���i�T�A�PZ�"m��i�a��|��(���d����$5a�`S�4\Y�4x����P-3�'����&���=�x�!�U����$�&���hJx�O�S��"��<\�o��!��.��jI�b�!���W��7q�YX�U�KQ[@b�2
�!UFsiR���}������������"�;x+�����_C�5
����A��;Y��c��_�V,�$)V~�N��z�����{m�	��;'���s~ZQ�
�a-�[L[P%������&J������xDx�����v����96�I����c������[�t��@���_���;����1#Uka�L���_������"D���v6��+��I:��2Uq�
��3?Z_#| ����V�"Q_6�cr��6�&
�Qos���M�J�����:�-)��S�o��^�O�6}r5��U��.WW.P$8Juy}���6�$������*P��z[�����R��/�2v���tw�q�.>h�5'�#��y��(p�b�?p�;�%���$�yK������&>V}�&9e�1F�DY��g�c *�S~��2�7����"�OJ��s|q��j9�Gu�<�@��#L��3��+��XX�V
�k6�oxa��:k���#0�H�ah_Z���P"B�'FN[*���&#g��"�� 	w�"`CW�a�mT���"���4u��h���&G�����J�3�����D����3mJ��`���}�5E��!c9�K�Xu��Gl��_�O�"����%����Y�|�����bw@�`�$#�T������t�FD�2�f�25��d���M�0��\�Q=[����@6���v\�`d���6�Y�����v~TkB�'��z�yTE�l����`��uu���E��v���<@m2��%�_{�)���������:TQ1��UE���~d�L
��0�)x�xU�az�Z�W�0�#V��?����2�������hL�>�T������`M�B��v�������>������h��	�$�����D@�x �!0.������Gv�.��U���)9=�D�~|V?F<������������K��*#�l�6����>�],g�m�IS
���,�O^�$[�(2�
yg����`=�$��A���<tT��������'H�����_���7����g'��MzU��fV�oa15��iFO6Bu#��u>���(�]S�M6�(�K_�:����AQ�
th�3�T����q�9���EY%��O>�=B�R�~�� L$<P���L5�J��\�����������<{3���������P9�R�3��n���[N��������x�l���"�"'�@����5a�{�\��#JY�^�������K�PJR�r�O�;h��N����3r�9���L�l������WT3��
&H��f���w�c��/�����������P_T���� �	����&h�<q:�i#��:��o�Gf�l�Y0�Ut�����%�Q/�����m���D��^Bu�C4�qR�h�7��F�E��Hq��&��fGi,W��&l�d�P���wU������]����v{�H�9��uP�,a\�1&����\J������-t������u�e�����������'x�sf�;��������<@��:#���EbNa��Bu�Mth��h����4w����n���~�OI��������$��Ln�Sx=����'�Q����6�O*��1�8��)��>������Up��B
.I��Y������R�W$�8�'�-s�4�f�����=q����y��C'�:e�������"�J�y8sn�P���?Mf+�b[����HAu�A�L</��X\��TM*A��C7�m���]�Ej� W�}g���R�h-���������U�4U\������
��(�:�"6{G�|er��s���������x=S2GIss,�I.��6��$�]2��R���������X,�)���,R2��J�-������beGy/c�����r���R���"8��\BR6c���3��_�����
��q�.�E CJ��td��XG��boR@�������K/�W�-��}�U)���J�;:�{Q��$w�����7��0��y�EgBl�E���+m���C��|�O���O�8j������"4����e�ca?��o��a��I����(���cw�n;�L5��=�W/C����?�@Y��k#��zcD\d�����&�0���x?�s�B�OW������mI,�@GFl�GA���:�����(�6�
��E������GM�G���e�}����_����N"�KH��H���M0$��	��
�%
H����@ �:k�X ]��*L�8�����������[���v(�!H���B�p��8"�I�8��x9��M)�MQ,K����d�!�RF�'������s�o�w_�&��T_#LOU0zc5��{+�2����N-��EO��qW��4������TqxQN�]-�Vl����C������>�zU�&��q�K�����Qq���S5�7��w�i>Q��L���g	H��?��D y�������
����<���\o�xC�{��-$��#�6�!�r�T�.}�����ff`��M��8���EN��37�3&w��<u}J���@>'D@�r�$-��~����'N��	�#��Xff�[�#j��/2���Z�.yq�8.u����EM8��	������r,�[��^"�	y���;���-XJ��"��0�
���cWC5p
O���N��r�E��D��V�y������\U��-�'�����O0��K>k`Dl*��,Aj���U���b^��{���Fr���OQ�<c#a��e��`	�����3�I�4;�fh��M���s�[w5 [�$��nFRWuu]O����
���gz��k�����������~������}�I^�:�D��C�\s���7��),L�6��
��>hv����a_���S��;��`V^�EME���@���{�$DY�S����2�xqj�=u'������;��&Yv2����������4��b�yt�Z�e2�\`�n�2�q�����b�<�9�0w������R���z���lb�2zw�����<7-H�mu����{��f&,L��^k��-X��J��C�Us����m��+cg���{��v��Q��N�Y����r��f��9@@�t��FIJO�5��l	Z����Z�}�#@�A��e��e8�B��w�����J��)��5O�\�-U�O� ��!A��nC���[��P�M���9��_x^��@e�����]�2OQ)X��
j��c!7��/Y�����k_4���{%����JG���?wyi�xz��~[
"�M�O	BOyMiO�) ��4����t�����S��o�:�t��+��������l�Y�"
�/�-����s_�����4o����3=��4o���Hm�	�5��7�0�4]���F��U��;pi����h�5Y�����|Sz����X�������s�D�D~�T�I*�6�J?q7Ie�q
�a���0-{����Xno�87��f��'1�Y��e���wH�OSa�2z�BY��Gl���!��9C���+S����V����Q�����o�����$����R����=���9������qoc��h��v����n�n�B�.�4����g��`�����{��t��������������vv�v��S;��	�cS�~w;�J���������CVu��>9:��G��~|E�������h7�?>���;���
��m���#�����_��j�c3O��0G�<�B�c���t%iZ�%������q�Tu� x����)D�W�'O�w�V���������������^`&�s����w��rv��]��]�5���
1�!b��D����zKI�3�&�2��F���:2�����~�����t��GT�b�)��u���)ta8~�����X������KU5��g��fhN�L2����#&��fTV/�/�����5JP�G
��C��F�%bd"���uG�����IR/g�����I�!Y��:%��?��^���lN��{�tH�7
eB
������i:G�E+�em�
s�<�%�m:�o�9�?�7�0�\���������/'�E*���Fd�g�>�
�$S� p3C��8N���*�#n0��w�����XN��zG9��NH#/�9t�h������9B��k��Lq'��-{����;���.�X`t{fW#(���iIY7���$��:|H�D��!��`���������
�b*�cR����
�����&}6>j�iC50,�f����l��#���&�>��^������`�����I���u�N{�/��/�SG=DL� �y�j����'�O�-�64u��B�yn���R��'�j��+x���{7}y���S�M �#P�zX�s����]�m*���8�Ut���%�[4���I�j�6qt^�E����ozam���*=��)��j,xk�������?O)��TW{��@�<{���UQ����}y��bj�����}PX[���3��lm�&���������HX�
��wplu�iik�����;dZ��\_'�d{�j8S���g�ry�u���=�����`�W���?���CU%���*�[�}��7�q��y�1``]�^�Nm8���7���P)��*���e���q��f
���#XT���L1�[s���t2�"��>��a��C���]�'\���jJ��F�������l8���j�Y����j���hR�p�a�x@p�Q�C2�%��+�L�C2��[�Db�~
��*��5k}�
4���C��qi�K�b���y�W�B��F���|��0�-���c���`OM���(����R'�*�v�rFtM~L�T2�xB�����W�]��[1�G��v�i��i���9��1���Og�Un����*<��{�rY
9��^����Nu7�T���;;�~�_|
C-��`������>���~5s���q�w�.A����4�����rjA��������u��k�;giq
��T��������(%����o/�0y����Eo/!>�z�bh%��8����_L�Lq�s7�����g�L&�������
VXn~���%_3�*�����N�.3�]h���n��=>�����.�mQy`/eX��$f{���&'�v��{��\e�n��c�0;�S:�
G�c�����8.@�@��s��#�zQ�:;,.�-�|n-G�����) ��bZ�oH��(�����,9��������x�>.��e2l��fz����4Ng-�Av�j��T����z>�'���_�������
�>-���7�s�$�)7�9�z�����>���0�Erw%�
c��csJv:8�}M:���9F)m�tc����M4�+���P�~�����]�����l��#K��8�@��R�X�7���2���h�yw����J��P���������5:a��G�^�x���?$c$zCZ�"|pB�9�,=+
W��m||N6~��p�'�\&�-�UF��M�
�Da�VL+�o���w�|��g�I��q��csi�v%�q�T^{:$��d���#����h�n� �1���;���������q�+������q*8���1�{e��s:O���d��
�:���H���U�T��}���yx�K�����{���4z���tF�������'�����Lx0������uJ�����9��@=��<qb���W�hl�3])�N{P��4O����Z�m��=ov����V�~j|D���&�*m�'m^JI�eTV���)z�H`�2�z���5&\�0�1�R�3/z���8�s��-v�X@�L4���[�yh�>�]�2�u�(t	%�%��5��Hs�KkH���'��b2�a����l��e��*�b����	)Q��J�XT�F7y��#�Y�qN*bn��U�����M��1'�7�FOS&�0��T��H�El9�v�3_O;��I��~)@�8��X�%^&�z��Q�[�;��*�!_<k���P?��T2�i����v<{������osa��7�]V����W]�
�(������@�~�n*�����Z�kq���'�x���7Y��M�2�P6����a<�N�7
%G�������=��q�y�M������@���(��q��Du��?��#KHuq��������9
�]�7�������k��'n<K�IE����G�&�,`���R����^&7�<s����C@�D�F����������_6wK}2���-[6���j{��~�Z
(�S��JaMq���3�G�������,.r��r��W��,��4����A��<Z�)>�"�@��?h�>�Z�����6�C���ahS<�^�|
��\�0�1fP�w�
��!%0����FW	4p}c�B���W�<5���d����#f}����J\&.���,T������N
�`�\\p�%��{_-�������*g���C���!=`q�<������
=`�����#]@�,�_�H�
.EI{���G����i������@$3��������>�wi�9�����,9���������GD��{�E��'��D�#D�/���Zz��������h���T[��p:�@�%�u��b�5�	s[���F���D|5�Md��K�i����7<D�V���z�����.q!����R�l��0�~���^���J��� ���0��np"L���#/E0cz;=����S.���[
�YN��P:��G ���]�<��,��O>�$��0$W!��d�@�*r�K��>M�jmL��Y���`
�B��,A���Qt�|0������qG�H|��S=i��p)�'�Z���������Q��J
�T��7!�J��1Ba���n-�
]��3;,��Xz.g����\���d�wY��L��\���De��`A���B�Hd��2��+�tOz[���4m���U$�'�Nt=�N��2=�����*���e'<=R��?����DgYUc3���G��-�C�9�����N�����"���i�������d���_�h�di��6�Q]���i�2Fr�����(%�`C8@�����y�=��Y-���3�~��g�I��gXT�3/�i�tZ�����6�x�W\t��lg�D�;t4C�<Mn��c�p��3�+��3�fU$\L��tp������#��C��|�~-G�L�=�����(�	���A2���M�|���)O��
D=�JK�|��Q�B!���M�'}6�,��ximh{��-
�c��dNif���\}�[6J� ���$�.�W���dSY�p�zr��_X�?5e�a�7:oDPC��DY.�;��/�S
�|)�5T�UK�S$�prC��|OnnS�p�I{	"�tJk�4?�&�	��(*�~�qS��Gv�;%��x��m�K��������\��O��'?wQolU�o`���6�N��A���1����N��~jb^J�����"$q�yML'���%��I��juB������~�g�}��Kr�SW�`�j�r�HJky�56�@�l��	J��3d�.Z�WGWEcC�����w6c;�l��=�����v�:z��/���_c�u�]�����}��X��^�O9����#4�j_�~�8��4K�jG����h�N�-����9!#��f����d}�'�eD-����5�4:��/_����9��(�F�������F5���(~���r�j��@����MB�{�����c�2�|�j��@��]V�������s��E]�;~���J �3������F��q���v������I����7&��}�5���A=��{���5�� �����U�o�|@����EA�P,|=n�����L

��^S2u�3f�n�e�K��
��J�����k�m�M�Ug�E���}~��~�]{sqv�s�Dc|����6��#��tD�u�]G���=K�X��_%�����8=��7���y������TQ�7�����@=�#�������O=��S4����]��m��U�H_���|r�KGS���T{������:x��6�����pe3��u~o���R��9T7�z.�g��]2�]'������8�\��f��-�E�~�Y~'��'G��<O�&>\kl.c�s�~����P���]��n�
��V��4�_���i���2��3[9
~W�r���'�6�Gh��s����{Jt�������d�JnG�����
��.���53�F�f�\C����45T�3;x�u
��������z=��;�`�^$�(���CTya���B��?K�;���[a�nf[��S8��g�i%���O	��|4jh�
�$�Q���c��V�T��y3��hp	����O`v�����������C�k��b��G��RL��+��
;~72��N�
n����/������F��
�����������@�Ih��s�zZtg�m�5uM�^Z��j��M�`f���k�����`����g��/O�G�>�]]M�����hP��>��-�)�m6���_��!��6�e�t�}p���x6���)�5�e��H?9���B��C�a��u$���������A�1�`���k�&��Us~��Z�r���&�!p'�����z�x������y���Y'��%�������A�����P�brXv���X�tU���N��A�`���RF���������[dlD��,����[�������O$���[+0�k�
��Q�+�P<�ujg����	l�0�
JR���'N���y��nl����-������������q�� ���pM�\Rhy�����[dm�b4	�oN��f�#��"]p�_9�t$�*Sk^�p�Z&���������(����������'��m|6��l7�(��gJ?kOWs{��by����i���A�@O*�	�9�������q�1m���9�������9���i�Pn#>bO#��K��a��
9��_6�J�]3G�A�sG��rAT���ULD=���r����[@���KZ�V/���T� ���_$j[i�i"Q��R@x+�FK&�W��a]y�k�%}��	�2�����-`D=���� �����)L&����A�x�g����
b����BS2c�(��+c?��WBG_;`��w�o����&u5,��3�x�7�a��_px�����f��P��`��� �����������Ev�-���	����s��D���B�^����?~N)��[�!#��UN����'o�����-<KO��g��N��C�^K��-
���,vY�7W�]4�����v����g�V1�1�?����7�1I��d��g��N���Bx��������N��P����P�|� �p�cV�v�� v���	��A�e;A8��8|���ZGD����%�}����P��rK��C�8:��e����
��)��N�g����2��*k&2���>(q��k�I�J$��kvh6�D��zJ�
���}�B���=��g��
�S[��*��.�����`Y>&��4�G���B��*e�C>�����zS;��������I�����K���|H�P1���x�N���`-�ua
������Q�Pc��������B�A�z�$�EwIc[�BEu�V+������{�]4P�����@��<<1&9EYN#F�i`P?
���T��	�Yz_i����m3��!B��(�{���7�v)����.�a��VG_s�4��k���2����{��uP,�/>��`�85�'{������|)�sG��)B� �MuFg�`>��:|K9E_n�qa���@�f�%��������t�
S��qFp��z���
.������S���7$a�;���JxD|
b���K��P
1�vJ����>GDT��F9_�</�0\N-Tkp�����`XJ%���XT-B�?�*����������4���kH���*Q�C�}2�ej��G�a}����	j�3��'Rd��;��M��r����Ws��O��������0��S��CD�_t�2u���%E�+H��������@�����1�|����@v���u�Wo����I��c�5�����<��8���W�>=�SOl# �Y�b��z�Z4J�A�� D��%I0��r7SQ9.�]��{L��liM�����M����y��\��4�]���(�A
���,:��c�^Vp[v�-�yn�gv������$���<���pY�|}q3m������e��"���B\_2�-�7w�c��;U��zx�w�����*�������Ag�s���n5F$<��|��}�-�^�Mq�Ep�+c~'W�
��		<�o�=�'�������b�� ���H�i4����%f���uk����l9�M��]�C9o��F��C	�t�Rv�*�DS,�&S�1�}o���������Q�,�\��!It@���
'��?��H�R�������	�S��t���V�������������:s�G�z]������KM���V���j/�!��5������{dy�{�i ��Y���V�����Y���U�TP��x)x��?�%�v��d5�6�8q~����<���������RI��8�83t��B7�8��T?<��=2wz#qC[+\���mO1�J���E=�������-G�r���dBaP7����1��$������z*��L}��������.f�������A���gZp��Jt}P�E�o �E�u�S����<���bF(�����J��bkXT�����w:+���(�������"��B��%���E3RNh���|�t�J��t��g��������X%�GQ���9>zI<�-I������1)���Ldr�����0��|���9HF�$�Q�I�4$�2��\_`I�%�,%Hv(���q��R�d������~�R?|9��g�q��2I������N[]6��W������c����6��.�����_��U_�:��"@�����'F-�Z'�b�����(��t��0�+�Qe�?����IC��������l�:�UC8���1��V��K���"1�)[pY��}�n�����
�{Lk�����OHP��Q8:QL`n���a�w�F����4$�VkD������:=��<����
^5�d��������iWY�����8�?d_��^	;/����Fx��
��f�kD�aLN�����$5NnCV��H��������z��`=�0�]'s�3-��;��e��S��}3���P�J���&Y�~���3�}|����ew��Y����z��34f�5c�f�	�f`�.�<\c�~Hma�s4��Bg�9��h�����B��S{���wP���q3��9���'B�v���	�y�G�
����*z��F7l���i�.��f2H!��t�q�`�[���n��
�,XI��*�e�)E�N�W���7��{������}�KSk�_=�����$[���~��_�u��.�����I�������5��=�g��T�\��j�_������<t�<��$v�qL�y`�I��(U�u�D�pw�%.���4�`��M=)��8/=��xz�����7�d����������L
~"��&k������}���uM���h����*F�<t�l�����-��S�V6���YC�y@��4A��L��������f�:�+��i�l/�a���v��k��=���@���i������"d�K�����y���m�s<��r(����g���WC���!���)��������x<e�}���D��.`\����2�[�9�^@Y��)�2�NQe�V�����{���1O_�c�Xy�pf�f/���`?���������~d6Z~q����	�Q�W�T0��]�%�8�y���l����p!�;��[�z�N��y��fi����r��;��6�a[���������d2��%����e�x3d�F�O�Nv�\��������4,s���-�9P�c9KmxX�������B��������3j|��%��~������5��
c5��]�n&}�y
��bc8�����~�B_v����F�|���.�a�*�}����Z��W����#�B��m�����k2��:�=�"�F2���p3�I����g(�~�9`"�����	f(�b��.�eJI����,=B�s��wQ�joCn���+����t3��o�dN�8�w6(W����[����el��SN8l�&��\����'���kk��.
�mQ����$�n���N�)9��HrO^����V�?���~�Fc�%*
��[>jR@�jk�+��m�!�K�r��3P_���(�@���o�.2�y�*4�P���R'�y&�_��� ���J`��������*��a�Y�|����xK����8�#z�C7�e���$�)7����m��Ay)���e�J>�1L}?M�:�0�!��mz�:I&����S��A�E���#y���i���T[�o����k�1�|��p$�(L���s��W>�kX*��'Z�M���iwZoO:�����r�`������Q�y�h������R
�p[�Ic����L��8P�	����j���&�,&��j��C1���{�'����nE���FY����fm��)g3rH�T����V��:i&6�BP�}$��Q�P�%�[.�:YH�$�2�!=��e�0Q�!
V |���&�X[s��s��6	3K!���LQ�����?e���3-���^=8��;6w�=�V�4���g~���L�+� �
4�\8&Y�i{��o��&�#��s^�
�z�b��y�>�y8<�y����<8�%��
������|DpG��0f����5V��P�d�u�K�l*��<-y�Q
!J����1 t����.������b!M4���]����ru���:������S��K�zS*Y������V�~�9��r������A!����h��*��z:K������y)�6�P�9�bb�[S���d�8�t1DZVQ�V�&y���]J�-6�pv{vTI)&/�&����
����`E��DB�|�6�8��kk��Xc1op�������+�T�j�0

X�������{�������WC�P������������=�U���z��f��D*�Z�\"b�@ 	tr,;`�<G:#�5u0#��JgF'.�#���n�BIvMyqTy�q���e�����?����%m��7:�-��'����4���C<6~6l��{	O^�3�7���z�T5_��w�Lrm��P?�5���p�"��-�^w
�}�I�>p��Y�~���w@��u�('����LY���?*W��+�c��`�;w�uz
H`1'�����$��8�6�����7�n��;[
d���a��A�������r��L��9��6K��8��Q��J��\�,�u�U����ec�=�f
�fff5�p;�{���Y�/Wx���<�_=,���J[M<����\AN%�`D�x�(e�q�(��v�u7���~��d���;��T�������km��?����/1g���&'�zR{>�M�8m��P�3�������:r�*��G.�}��������pi�z5��M���#3�������^��}&�7H�%��Q1���DU��9���R��E���yO��^&�;3�z���b-�������f��X���N�v����_��P��]����r2���M,.!2��)��2���a����h*�%����	���q�"t��<��ev��`�q��������%��y`�lc�zY���Nh�^QFG�E���Ll�s�e�'�.��y'���N�t�&�r��m5Z�\%fo����%�1�,�t,s(J����NO.��L6*�p=i�e"�� ��f>�
�,��;-ym�����Vf�q�)��z�e���s�5��]J$q^3�n]���?A]�*W�g=?�O�������$��Y�Iy��]4�k57;RUv�C��+D-�����T_V�M�	=Ds���!�B	�����C�v��V�wd���*X�|%���z�C4�9>�k&C I����>,�r�����o������}!����.����.��UR����O���������E>�����������ee&Nx��/X�����1�L��b;Q��s;K����G�����I���>��w�e:��t��;~�wQ�Lp>p���O�q�h_x.O�_��t_�Lj�3�>������wC�H�����/6#kP�Pa�"*`�+J�����8���u���N����_4yMS%���S�(������G��:��6M�C���4���c�z���z=���O�w��<C]B>��(���LcLcbd���Z������J�������]
�=�9`��/Y$����Y�����vM
�&%�����s�nF��R?A�P��,8��L�[�����u�f�%o��}�h�.���^	������V����!	��z�Yo�-t{�,��b�Q
A�d)�i��6>����"��R�a��
��m��b#A#+��3�����3g���3��_<���Q�n�U����E�������I�����B ���w�eP�z����}��<�cp<��}��H��[����1���������^Tk[wS���)hK�j�
3���{�8�Z�1���j
L�p3
�g~��C�C�������(M���J�8.7�����2�]�oQ���M$6���_I�d�O��!�@��`&Zu�,��3H��[��U�����k�Wm�*��'a���B��-�T������QY����|}��������%~�Y.�����[a���Z�{�6U����b�<s>�d������0"`J�o��*J�=��t�Lh@�E�7��V����h�Z�����/+��V{���4�j���g3��$c���l���ND�*s#�#���hE����K�������Wm�*B�l��zS�]���"62���8#M	{�������6/����7�N����w������Ic.���c}���~�(�a���p��0�mzf8���E�Fi���n�-�0T��]�2�h����!�u����~z
�=�o���])'[G���:&e@#M�@�M��'���X��N��E{��P�!d���O�/h4Z�
G	��h���R�!��
.!��`�/�{i���h��*l�`r���Q�~'��#���i8k���@8j�j�3\;���N.1c��n���	q���Mp��]���6fI'�~^�����Ts��8���Kb�p�-*Aw���:B�w��CZ��3�F�K.����I��r��R�a7;"$H��w8�=Sc��uTc����,�������"j`�S��7���
mg��T�p��_����D�M��6j�J R��"���T���g�?R�����^dOi�����z��|��{#���\�K����-��v�>�M a�Y���x��������������}=�����\?�,�M���m�0����J����6�����]�E:'�+����Om�5��������#+Y�/cWXS
���%.T�^�z�n�-�r��$B����
e����#Q���:uM��t���tk)����Z[�����7�i&��zA���&���0Yy�$���=:i�x�L�<��3�lT�N���@��<i�t����(��;�Ak�v"*�I�������]JP��.Z]r�d]I�G���4k�����z��C6���g��	E�����#�w��+�}��`^�
�������Mt�+�����MW�z2�4��vf���L#������w��x���l���Nt����e���8���Ae/mR/�
w�d�r�Bj�3��y��<2��0���;I��LN����o-�6{;%�z��!�pnX��i�,	��4R!����J���K�H��2�������y��
O*���u���[>�2�s�
Z���dE�=�9�
2r�t=���A�?w�����p��
w(���-������z������p���*��0�}��f���?�?�f�'7��Y�n�
��;���W��V�����n��]ou�Y$���A�u��
��k;r���R}�������^��Vk�k��XC`������e
tC����h�%����G��������6j/A��xw�c��}�~�E����_���V94��_@���Q���p&: }U��?<Ww�	;,|���wq�K��uv]t� ����|)g�.|���9�����������x�����U�����1R[�}0LjiD/,'����kY�.�����M�Zk�����>��D���b������&��QQw���D*�&����U��P�n����N�['w|��/���� ��������q��h��E��;�3�z�9��I�.4���������OqdDo��Yu���qC��;J���Ff��f�$�D����f�{[�����z��L�c�",�����R5�mM��Q��|��A��t(�&�������G��K.�)��SJ��&�|t��������+���\V=���/��i�_��qOn��GI�K�jo���S�o�}�T�J�S��'o[����9n��E���"l�W< ��<�'g�>"�����iY��Q�q��F�PXW{���$�OF���YR6a8C����vxn�=L����&i��#T)��L>7sKF�����CnA8x�F��(SKN:���\S����')A
7/���Le�r-�V��x�����a_Z�����J�PMCmq`�l�kl�d<'�UO�oV��A�rz��>9z)�g�;�����Mm����K����������\��9~H�� �?oG�#rU���h���2���[����h��T~G�����
�k��P3�E�7��9e�g.Kl�������:�q�qF@u|�X�O��s���vWPydY$�Q�L���I�D��������qTc>4�zn��+������9�a�eFLe��7�a�]�/�������e�"��v��o$��k�S�0�.%�������5O|��J�Q�]G������s������]�t��3Y�_|��Q[��b���gI:\hk���,������L_.^u;�����C���8���2a|������s���UEg����4��:�N�lM'�����:'�3�+��~nMv�6��w;�[�K��� &��3{m����w�,�V�'D��C���l�g�u��^��r��,h_�kE������^�f�
���JJu*@����m�Q���&���_����*
}!0?fj.��z��Z�m��]'VtnQ����!y�>-���bVo�cVJ���K7���w;�^���Ni��-�<�-$�����a)��$Hf���?-C{	�	*]�,6������R����r}�tx _�.p���$��T7�2J�����L.���0��O�����
�������	�����������1��q?
�����9�+�6p����/N��?s��F����D���U�3#So�������p6��/R�T%zg�$'�j�_�C�	D�M :�*�3s��G�r���!�5�m�<fVw���O�����`��a*��*���Z�%+@��n�H-���s�8�/��p2��}�x������\3��P�����S:C��i=5l8<f*����������M�dt�s��(�u7Q3����p/ �7R���r�w�6z�d��M�xL�y3��3���B����)�y/��v��o��!3����(Vf�����N�&+��f�c��Q�v�|������4��q����mc2�+J|�Sp�(�<v��]��{�Y�8{�~��;�D$#�~�����'"^���h3�t���|�L�r��S�����u�38` �|n��C;q��S`��H�z���5f���6�o�t�o�p����B��4%�,�)[8}��P}�����W1������'�����b�i�^����f����)q<�*���w�	�wO]�L�Y9���	r�%0�������u��@�=}�p25nc����.���&���5�Q�xt�<���1�b(L��
	�.[�����":�DZ�:��"g���51��y5��"�3�����^+��`j��rFY�/���6����j����c��O>���N��������?��:�z�������!p��4^�HQ�T:��`crl0"9�Y.�M/U(J�c�1����
���Z���R���x�}�f�l��aHgS��Df��\�;�8*uP���<G�l}�UI��K������E^���N�B1���$��k�0[�8������	0�p��o=_��(w�le���<!eI4�=a���iI��uLfe��N���(���k?R�0:�Ah]��4��C7v�[J`��[|�3%��
�:�U@j5���S5����,EWqiV�x3o�m�}?��vO�������M�l�[R�������B�=�[��;�P�)|2�"�j!v��
 �#����K
�w���e������z{�h������`:���� ��H���X��,�i��$tzmjh��)�����Ck�~�^�Z�z��������M�!'D���u��5O2A�@�����US'�N�(��T4�X%G�QU�NT�Q�����jNs.�a�Hi��po+�6$�:g���R�kV�$,_0�>�^��T_�Y����1M�����8�TI��g�m��S����zQ��9J�8/�x�U����<G������G�������<�5���f>��)�}c��*-E��c`$���������p���?
4��/(�3]l��\�����{M�Cr53Fc1FM��Q�]���\b����4o�o�m
��D�"����L����9�n8�����h8�6}�u�,6N�
�tP�����f�_�����H-�5��	)��Y�������m��0�J-E;Nx�P�";O�Q�'�
��Kp���i�6Tnuz||]F3@�
����:L�W$\�:��cL,������d(���e��C�$�!b5Q|���57&�o�y���I	����(�!��T�r>�+Q�Z�vV?��e����kc<��m6(Q,�6����}��~��6JX��13�Km��;���H6�avm����M(��\��k�@x@Pp�f?\gd��I��
�����q2�E|�y�p���}��������'-`k�0�z�2,!�g�[pU��G�f��(f��o�~�k���n��(�4���
vYx��1��d.6��.b,�=�s�,��?�r]���%�w�"����v�5��?!���32B��������"b��~�"��%(�H���`�0>����L	�g<���$���:i�['�.P���I��=��v�J;`0��!���T�N�&ES��t5M�v�$�hy;�vu���@M��z���~ �����&S���E������4�'����W���R�����3�E���'��s��T����
���&
���
R	��E�N3��\ky��;S�����"��{���5��$�<�:��s�.���_O��}��.�x���B����%}u�e����VZ����P��Hx������0
���@}��f��!����s��P��'�����n�5���Xm����k����l����}i&�5����+o�
��)7�c�3�h*�Y����~�h*����/�Hk>�]&����O$����A4�����(��Z)�U�E�6e=�7�F)C���e'�P���p�s��i�]������L�+�w�K��$5��K<���y=���J�������Vm��Qi��]mu�
(�8ti�V�N��8�d������G�-~������4G����N����H�h�#�Z���O�h�����L4�k�gV�Z���mU������6%�=a�2���[K�,�k9��K��;�|�&��N���5�@�(��2&��sJ�Q=�ABn�,�U��b'�C]M�I��p�XP����,�eK5�)����sfS"^#z.?&����/��g�"�!����F�]3O[���Y����#���]�3-c�4�L���)&&i�Fg�1)������U��.N{�����/(q�$�����{q��>����,��~af�-��|�)U���F���GO���������{�2W+���.�������T.�8�+�q��a�R��;�d8�?//�����5�RI�����J���H%9�Z|�?�<yl7���m��:�"|�&4�8����XW�/:<��0���4F�'�|��?wO�����N6���p^P�h���
!�u�kd�9�/e����-���<)<�b�S���?$�����C{�`JD�.��S=R�RH2n�Z��<#��N
�4s�wC����5���W��w��|�}/�������Lvw�����:%q�F��l�V�������[||r�������-���&��
��fH���Z�K���f_� 0���h!A�Tf��8�*���)>��]S������E���!c��w��v��,���5O��zGS,������x��3:c�:��3*�	
>�����G(�`����g?�(��e���_�?��p{a"�Uo����1X�Z\pa�����_6�����ZB������!�g���iE���^#���cU�%�����8Dx�9$L�u�l�~I�Pe�eq�1�\QF�5�@>�3V$+�"5iC�Y@������
�i�����@X�����zKO�l}V�����].������(�+2i����~��`�X+�L{<�"��l���;]�V����s]��/K�0�3�������LM^�����H���0�X�����j�jmU�X����P��}G�&G�C��h=������=��uks��B+�S^(IU��m�V	��,����Mu�
7�g���@2�p^����<����������q��b���&^��3�u����uc���������Y�\i~�pK��j�����������M5���0g�m�,���K������i������d�FV8*��U�������+5�M��������2?T�:�	��P�Y�����ara!cIH��q�/�����/���T��ii�}������/�U�������8�"|i�]p��������5V���mdl�9U����_)W��c�A���IW���������mw��8��@��4��CZ�������j�\Ho�p��G������4���o.a�6U��%TL��� W�����V�v��Qv.n���
��G������1�0u�*��2���zoo;~���d�J��I����w�KE��l���q����DH\��c,���C�L2�����C0�enQ��O�v������ !��N���
�pC=r6�� t�%K\s�����L�r�B�/��~d|�]D��i������rS�=z�[�����/���v�H��������U�h�i:��O��.��6�Lh�W'�d�*���>egW4,�����
e����6�����^u�wm�U��m�� '3!��B���C���,�#8+���&~�����jD�B�g���|8�i��e�DYWH��_���^-�d�"<^��#���r@���z1������Z�����</���)�W0+
�V�~G���a�*_z_�{It4�@e�ti#{���=*�nJ\V�ZVaAa�����>|�'���y������9f�Da�h��c����%�/�w}e�2�"O��X%,�\�u�
+@�\����|��+�E��^X6,	��{���������nj�b�;�4���}��_�����C�b#X5�����N(����X��	�3h�v����Fs�����1H9)ll(	!S$S��$��6J�Q���J����3�����f�wT��$J5�<�H�?7�`m�w��E����tuI�m%"9��N���6��TDa��������z���C�1�����M���
�z�Eq�.sP�v2�m#l�L���Y2�/F��M&:�^=�Sk8�[rC�������']||3�P�V��/�'����n�/��E��v�A��;t�(�����&�)/�$L�'��wA)�N1�><i���o�O�/��S�[��*�z�r�������FO4�����	Z^#�����k���5+����R���2tH���*T���H������r�wC�%����2c����������o�xVz�V-+���?YZ�_*x�0O�s]�d�"*�����l�����������6��3	���.L
4�8ot����h7����P<��\��O���Y�E;;��Y�v����d�0Nm��,�k�X�<���C���|���I�
G���u��U����0���@vf�H%o��]��K�7��^]"�����Q*��m^�������o��B��LXR���g^I��}��E�s�������:sK�PX�(F���zQ����04-Oa����6�a��B<�`��/��lE����E�������hCN��u���������0�J��Z4��Hp��/D�����X,�b��,��}|�j&\�a���ABa�����ID�5&!��7��9��q@�_A����(7;g�6b���~H�������*�?H[v_��l:��L�'\�%�{6��`>�?�x��p��k?~�������L�3���"	�r|�6����xE�,�����Gu���c��fAI� K��p}���i��D��)vz�p��2�l�5�GV6���8;L�j�"&�h����a�'W���\��h�`BJ��Y����Q0�D "O�>��@�47ICM��2Ca�� �f�o����}����9!�g��"�o%�����=�<��,��$U_
������r��c����3�\Y�!�v t'o� �.1�3����F���a�e���qT��z��`��?�ez�'7.e��Ha��ESD�%�i��-�`�sL�Aug�	T�
QZE���DZ�$��GK�?m�J����t�.��|�;���jX.*� ��?���|{�j8S��t�{�	�@�|,9j���p���Vz�ri�u)}r48��G�`�Ry2���'�'����s�������������V������v��j�{����u�{II�6��7�kW�7�����E�}�t����@�pE/SBv�qUbB[T
��<N�q�{���8b��W���������(@8�������[F�YWb0����.Q�RS��#N0���������E�o��m�uN�������9�.] ����n�����i�5���`0��V�_�A\i����N�����	��iN"��3�*�����7D�V84�$�y(����("�@���L��t>�$x���(3�,�b��,�)f�f��5�3�f
�CU���c���8������"�M�|���pG����~u'zR�T�;������q�����?��:��w��q��O<���x���K���d���y�s�}R;���/���������j 2[��v	�;�dt[���	������e+�����VY���0Z��nY�����Q4��{�� >>���^��-��P��z�h���h��'/0�Ya�#��<��esQ�#�`��2�����w����@!Ps����g�v�-�� z28<\�*��Z�]F������i�h8U�.�}�w��=5��(�I��<������c���wO^�^7�;��k,Xw]�E�yZu��JaY�55hO��p����D8�Ek���b�p���]�U*{G�����at�l1�v�V����Y��;������X�_O���,��������EE����e�~a�Ub�O�a�
skUPKV��P�`���n�h���W�bE�����&�C:��9Fz>�p�G����)�P���#T�D�p���AaO�p�l=a�^9O������CD�UP�u*�*�?�����d0^�/��D���8�b`��O�j�	i�dE���O��$^{[���@��R�*�`RD��JM���+;W���	�(�f�����=��b]_Z!��[d�;��D������0��e��G6���g|O���ey�B(�
.:��z2�S��gN-��
���n5;V�*�	���@3��d�vv�0)�����������T���w9���"_���`�JSt��W4E����l�!�>�H.
>�@��:���j�P�`T��X�&������@��.F7g����[�`HQ�<({���`��V	�]�$?��%��B�Q%�m�5��)������O���M�����KX��Bv�.��1����V��e)�D���Os`B}%���A���FG��Q�b�OBw��<�Z`uMuw����k�#��]���Z�Z��I��@'�mH�Aa�pbH^�K*y&�w�����Q��n�{�0����G�i�;�Z1d���:��H5��B��FZ�m�/m����4:Le������G��n2W�%�z�
7uu���y�8�p��b	��w���r���r�]4u��P�t����e��`;���|[�-B����7��8�$�=�����W����K8�_>���w�)���*���S����v����+��rjS/��R�������!�I�D��=����1������(����GO������RDQM����
��'��H_����V��G�^V�Y�-(�=�S�vJ�PhJ����;������������x�����n��n
��k���#�.;dQv:a�����q��u�;�_��jWpkn�.z��.���i�i�9�u��]��q�m���w����OFo��S��3!�%j�1PFL��~���j�����9D�;�~�2������ �WX���[�jS]=x�W���~�����0'9��P/��q�f8��>vo`b��t�������}��q��	��#~!�g�x��E���,���i��X�Rt���P��C�;�>��"�^���?0a�v���!Fl��)���M{��.ct��|�~ ����gxs������c}���m��F��(������������;�O_y��h�����1��_�V���6_��Lv����a��?���?�9z/8'���$P��	iq�Z����h%��
dR ?iFm���	p?7iFk��f��[�EO��2��^2y���A=��b�����l"0|�7'���(^�3$�x]37^
�����+����#R6����*��S����/�k��G�5�S�����\��QX�\�s�I���,�0�RJ?��/_�&�j����E�*�r
q|l��6�A�4��R��(�JK��$�x44���{~�*7�7����a�q04T��������j�`P�<|�����
-8|�+~X��5���+Voj�w�����+G��
n��n�*�[&pD�#H�k�-���9�H;�����H&��HG�����0jc`�(�Mn���������������j��S����-@��I3����3�X;��8�U���l�C �3@���n��f`E���~Y��i4����=]�xe�
���+�%_��s5.z�����.�r����6���v��=�{L�8�����6d�	J��d�x����{�����a�b�?����qfACy��DeG4���tJ��YAS���1���&p�5����l9i�".�q�0\��mq���zGK���?�'�<O�N#�sV��M�H��Bk�1NSz�>��s���7K���u�D)\v�J@���3�:i6�&�(�*�\%�2�����~g��*�
=������*�����K�"���(J3���y9"E��xv��i�����z���P�`�f]���S���6��\�.qs>t�
��;[8Q|��v�38~��8��'g>��U���;\��V�G������������o��������o��������o������?���]�Y�X
#179Andrew Dunstan
andrew@dunslane.net
In reply to: Yugo NAGATA (#178)
Re: Implementing Incremental View Maintenance

On 4/7/21 5:25 AM, Yugo NAGATA wrote:

Hi,

I rebased the patch because the cfbot failed.

Regards,
Yugo Nagata

This patch (v22c) just crashed for me with an assertion failure on
Fedora 31. Here's the stack trace:

[New LWP 333090]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `postgres: andrew regression [local]
INSERT����������������������������������� '.
Program terminated with signal SIGABRT, Aborted.
#0� 0x00007f8981caa625 in raise () from /lib64/libc.so.6
#0� 0x00007f8981caa625 in raise () from /lib64/libc.so.6
#1� 0x00007f8981c938d9 in abort () from /lib64/libc.so.6
#2� 0x000000000094a54a in ExceptionalCondition
(conditionName=conditionName@entry=0xa91dae "queryDesc->sourceText !=
NULL", errorType=errorType@entry=0x99b468 "FailedAssertion",
fileName=fileName@entry=0xa91468
"/home/andrew/pgl/pg_head/src/backend/executor/execMain.c",
lineNumber=lineNumber@entry=199) at
/home/andrew/pgl/pg_head/src/backend/utils/error/assert.c:69
#3� 0x00000000006c0e17 in standard_ExecutorStart (queryDesc=0x226af98,
eflags=0) at /home/andrew/pgl/pg_head/src/backend/executor/execMain.c:199
#4� 0x00000000006737b2 in refresh_matview_datafill (dest=0x21cf428,
query=<optimized out>, queryEnv=0x2245fd0,
resultTupleDesc=0x7ffd5e764888, queryString=0x0) at
/home/andrew/pgl/pg_head/src/backend/commands/matview.c:719
#5� 0x0000000000678042 in calc_delta (queryEnv=0x2245fd0,
tupdesc_new=0x7ffd5e764888, tupdesc_old=0x7ffd5e764880,
dest_new=0x21cf428, dest_old=0x0, query=0x2246108, rte_path=0x2228a60,
table=<optimized out>) at
/home/andrew/pgl/pg_head/src/backend/commands/matview.c:2907
#6� IVM_immediate_maintenance (fcinfo=<optimized out>) at
/home/andrew/pgl/pg_head/src/backend/commands/matview.c:1683
#7� 0x000000000069e483 in ExecCallTriggerFunc (trigdata=0x7ffd5e764bb0,
tgindx=2, finfo=0x22345f8, instr=0x0, per_tuple_context=0x2245eb0) at
/home/andrew/pgl/pg_head/src/backend/commands/trigger.c:2142
#8� 0x000000000069fc4c in AfterTriggerExecute (trigdesc=0x2233db8,
trigdesc=0x2233db8, trig_tuple_slot2=0x0, trig_tuple_slot1=0x0,
per_tuple_context=0x2245eb0, instr=0x0, finfo=0x2234598,
relInfo=0x2233ba0, event=0x222d380, estate=0x2233710) at
/home/andrew/pgl/pg_head/src/backend/commands/trigger.c:4041
#9� afterTriggerInvokeEvents (events=0x21cece8, firing_id=1,
estate=0x2233710, delete_ok=false) at
/home/andrew/pgl/pg_head/src/backend/commands/trigger.c:4255
#10 0x00000000006a4173 in AfterTriggerEndQuery
(estate=estate@entry=0x2233710) at
/home/andrew/pgl/pg_head/src/backend/commands/trigger.c:4632
#11 0x00000000006c04c8 in standard_ExecutorFinish (queryDesc=0x2237300)
at /home/andrew/pgl/pg_head/src/backend/executor/execMain.c:436
#12 0x00000000008415d8 in ProcessQuery (plan=<optimized out>,
sourceText=0x21490a0 "INSERT INTO mv_base_b VALUES(5,105);", params=0x0,
queryEnv=0x0, dest=0x2221010, qc=0x7ffd5e764f00) at
/home/andrew/pgl/pg_head/src/backend/tcop/pquery.c:190
#13 0x00000000008417f2 in PortalRunMulti (portal=portal@entry=0x21ac3c0,
isTopLevel=isTopLevel@entry=true,
setHoldSnapshot=setHoldSnapshot@entry=false, dest=dest@entry=0x2221010,
altdest=altdest@entry=0x2221010, qc=qc@entry=0x7ffd5e764f00) at
/home/andrew/pgl/pg_head/src/backend/tcop/pquery.c:1267
#14 0x0000000000842415 in PortalRun (portal=portal@entry=0x21ac3c0,
count=count@entry=9223372036854775807, isTopLevel=isTopLevel@entry=true,
run_once=run_once@entry=true, dest=dest@entry=0x2221010,
altdest=altdest@entry=0x2221010, qc=0x7ffd5e764f00) at
/home/andrew/pgl/pg_head/src/backend/tcop/pquery.c:779
#15 0x000000000083e3ca in exec_simple_query (query_string=0x21490a0
"INSERT INTO mv_base_b VALUES(5,105);") at
/home/andrew/pgl/pg_head/src/backend/tcop/postgres.c:1196
#16 0x0000000000840075 in PostgresMain (argc=argc@entry=1,
argv=argv@entry=0x7ffd5e765450, dbname=<optimized out>,
username=<optimized out>) at
/home/andrew/pgl/pg_head/src/backend/tcop/postgres.c:4458
#17 0x00000000007b8054 in BackendRun (port=<optimized out>,
port=<optimized out>) at
/home/andrew/pgl/pg_head/src/backend/postmaster/postmaster.c:4488
#18 BackendStartup (port=<optimized out>) at
/home/andrew/pgl/pg_head/src/backend/postmaster/postmaster.c:4210
#19 ServerLoop () at
/home/andrew/pgl/pg_head/src/backend/postmaster/postmaster.c:1742
#20 0x00000000007b8ebf in PostmasterMain (argc=argc@entry=8,
argv=argv@entry=0x21435c0) at
/home/andrew/pgl/pg_head/src/backend/postmaster/postmaster.c:1414
#21 0x000000000050e030 in main (argc=8, argv=0x21435c0) at
/home/andrew/pgl/pg_head/src/backend/main/main.c:209
$1 = {si_signo = 6, si_errno = 0, si_code = -6, _sifields = {_pad =
{333090, 500, 0 <repeats 26 times>}, _kill = {si_pid = 333090, si_uid =
500}, _timer = {si_tid = 333090, si_overrun = 500, si_sigval =
{sival_int = 0, sival_ptr = 0x0}}, _rt = {si_pid = 333090, si_uid = 500,
si_sigval = {sival_int = 0, sival_ptr = 0x0}}, _sigchld = {si_pid =
333090, si_uid = 500, si_status = 0, si_utime = 0, si_stime = 0},
_sigfault = {si_addr = 0x1f400051522, _addr_lsb = 0, _addr_bnd = {_lower
= 0x0, _upper = 0x0}}, _sigpoll = {si_band = 2147483981090, si_fd = 0}}}

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#180Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#179)
Re: Implementing Incremental View Maintenance

Andrew Dunstan <andrew@dunslane.net> writes:

This patch (v22c) just crashed for me with an assertion failure on
Fedora 31. Here's the stack trace:

#2  0x000000000094a54a in ExceptionalCondition
(conditionName=conditionName@entry=0xa91dae "queryDesc->sourceText !=
NULL", errorType=errorType@entry=0x99b468 "FailedAssertion",
fileName=fileName@entry=0xa91468
"/home/andrew/pgl/pg_head/src/backend/executor/execMain.c",
lineNumber=lineNumber@entry=199) at
/home/andrew/pgl/pg_head/src/backend/utils/error/assert.c:69

That assert just got added a few days ago, so that's why the patch
seemed OK before.

regards, tom lane

#181Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Tom Lane (#180)
Re: Implementing Incremental View Maintenance

On Mon, 19 Apr 2021 17:40:31 -0400
Tom Lane <tgl@sss.pgh.pa.us> wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

This patch (v22c) just crashed for me with an assertion failure on
Fedora 31. Here's the stack trace:

#2  0x000000000094a54a in ExceptionalCondition
(conditionName=conditionName@entry=0xa91dae "queryDesc->sourceText !=
NULL", errorType=errorType@entry=0x99b468 "FailedAssertion",
fileName=fileName@entry=0xa91468
"/home/andrew/pgl/pg_head/src/backend/executor/execMain.c",
lineNumber=lineNumber@entry=199) at
/home/andrew/pgl/pg_head/src/backend/utils/error/assert.c:69

That assert just got added a few days ago, so that's why the patch
seemed OK before.

Thank you for letting me know. I'll fix it.

--
Yugo NAGATA <nagata@sraoss.co.jp>

#182Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#181)
1 attachment(s)
Re: Implementing Incremental View Maintenance

On Tue, 20 Apr 2021 09:51:34 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Mon, 19 Apr 2021 17:40:31 -0400
Tom Lane <tgl@sss.pgh.pa.us> wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

This patch (v22c) just crashed for me with an assertion failure on
Fedora 31. Here's the stack trace:

#2  0x000000000094a54a in ExceptionalCondition
(conditionName=conditionName@entry=0xa91dae "queryDesc->sourceText !=
NULL", errorType=errorType@entry=0x99b468 "FailedAssertion",
fileName=fileName@entry=0xa91468
"/home/andrew/pgl/pg_head/src/backend/executor/execMain.c",
lineNumber=lineNumber@entry=199) at
/home/andrew/pgl/pg_head/src/backend/utils/error/assert.c:69

That assert just got added a few days ago, so that's why the patch
seemed OK before.

Thank you for letting me know. I'll fix it.

Attached is the fixed patch.

queryDesc->sourceText cannot be NULL after commit 1111b2668d8,
so now we pass an empty string "" for refresh_matview_datafill() instead NULL
when maintaining views incrementally.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v22d.tar.gzapplication/gzip; name=IVM_patches_v22d.tar.gzDownload
#183Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#182)
1 attachment(s)
Re: Implementing Incremental View Maintenance

On Mon, 26 Apr 2021 15:46:21 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Tue, 20 Apr 2021 09:51:34 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Mon, 19 Apr 2021 17:40:31 -0400
Tom Lane <tgl@sss.pgh.pa.us> wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

This patch (v22c) just crashed for me with an assertion failure on
Fedora 31. Here's the stack trace:

#2  0x000000000094a54a in ExceptionalCondition
(conditionName=conditionName@entry=0xa91dae "queryDesc->sourceText !=
NULL", errorType=errorType@entry=0x99b468 "FailedAssertion",
fileName=fileName@entry=0xa91468
"/home/andrew/pgl/pg_head/src/backend/executor/execMain.c",
lineNumber=lineNumber@entry=199) at
/home/andrew/pgl/pg_head/src/backend/utils/error/assert.c:69

That assert just got added a few days ago, so that's why the patch
seemed OK before.

Thank you for letting me know. I'll fix it.

Attached is the fixed patch.

queryDesc->sourceText cannot be NULL after commit 1111b2668d8,
so now we pass an empty string "" for refresh_matview_datafill() instead NULL
when maintaining views incrementally.

I am sorry, I forgot to include a fix for 8aba9322511.
Attached is the fixed version.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v22e.tar.gzapplication/gzip; name=IVM_patches_v22e.tar.gzDownload
#184Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#183)
1 attachment(s)
Re: Implementing Incremental View Maintenance

On Mon, 26 Apr 2021 16:03:48 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Mon, 26 Apr 2021 15:46:21 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Tue, 20 Apr 2021 09:51:34 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Mon, 19 Apr 2021 17:40:31 -0400
Tom Lane <tgl@sss.pgh.pa.us> wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

This patch (v22c) just crashed for me with an assertion failure on
Fedora 31. Here's the stack trace:

#2  0x000000000094a54a in ExceptionalCondition
(conditionName=conditionName@entry=0xa91dae "queryDesc->sourceText !=
NULL", errorType=errorType@entry=0x99b468 "FailedAssertion",
fileName=fileName@entry=0xa91468
"/home/andrew/pgl/pg_head/src/backend/executor/execMain.c",
lineNumber=lineNumber@entry=199) at
/home/andrew/pgl/pg_head/src/backend/utils/error/assert.c:69

That assert just got added a few days ago, so that's why the patch
seemed OK before.

Thank you for letting me know. I'll fix it.

Attached is the fixed patch.

queryDesc->sourceText cannot be NULL after commit 1111b2668d8,
so now we pass an empty string "" for refresh_matview_datafill() instead NULL
when maintaining views incrementally.

I am sorry, I forgot to include a fix for 8aba9322511.
Attached is the fixed version.

Attached is the rebased patch (for 6b8d29419d).

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v22f.tar.gzapplication/gzip; name=IVM_patches_v22f.tar.gzDownload
����`�<is����
����&�ic�o2��$����L���E	���H������n-������WoT��u������N�s3�.y8���i���
�v?�vS�~��3�P��j����LQu]����7�e�X��0��nq�������������a�\���E~m���]�\g�k���j���:��{����<�-�����9�����������2��V��?c�� ��s��	����t�4������+S�����O��fk��LL��
�N|�
���m�({��i��"��c����yaF&��G�?����a�������Gf�������s��[��v�
��{F�U������'nE{�_�F���b4T�����E>�R�P*J%��,�J&���T�Eq�����b�,p.�t��*�s�� oF�<�\\��E����8
8���7�K�y��;f�ytYgo���hsf}���DUx������xqu%��K����[�"����y���y��~�gE��d7���%f.f��.�������	 $�3��2���4�nO��f��G�
��Z�Vda`5&�u�kX�lfzv������uV���p�r6_�s	p��*E8M�S�WE��+&�J�2H����w�\,��t�j�'bf�t'�r���Mu�9�L,eZ��i�����
�S�eH�{g*�������XM74��b�T���9����Q��"v0�x0��A���0*2��������JdpBp����w ��J�����$8u�F�Gh	�/������6�h��
\���mT�g�xNJ0�������)@�h������� ��|	HD��Y���f>���q�n�"zD���:~����1�("���E�r��"���_s���\��`�j*��=����B�|6����0����`kW����j��,���G� �@1���`6h����4�1��!;7�n�����i9����5�z��������m�����=f��d�d�9Sfzw�R��`�y� f�!��� �	����F���0��l ���
CbR��
�h*3�;�K9d�����C���3M�pM�%8�����d����B�rAT��B��,���g������k������	��1����=�Q�����nR|�B
@_c[���`"vp��"$��K@][wp����V����X�>!pU����A��Q�u!�����v���J\�����k���(Z���A�^��������k�����R>!v�J��b�!�"h�(��d��t�d�h������X>hn8�=�z8��3u��`����P�t.ddi736��4O`�0����1��J���)��B(��
������rf3n;�K2����7l��0�,�&��NH��t_��`�Kg��V���R���b�w��|�g�����Y��'�5�@��lj�!=��q�]��k���yx��c��V)\�;C����P]��RV$�z����)��B��E�7�
�lg)���������)�$�;e�����<oF2��������h�@�J�Ue���+!�$� ������e�N� �bT �S�WB�_�����9�7$7�V���s|� �M��={=�H/����	'�+_�����������}������S:�����t�?b?��.���N5�X a>���"ry��M~IF��%f4u���B��7�PQ�`>�]f$����@�t(0��e��pRe��J��\�d5)�<"7B�����}z�����`�z��k�~�O#��v�,J��X)�d� ��jBT�uZ`���*y�@*G
]���H�B��J�Y�[t;��������J_2b6/�����T<A�7S����JP(AD�,]�dZ�#��x�f������@���|��x�h���x>>]9����)Ba3���2h% �0h�������M����V�w�����A�]N���!�X��E*wK�om���X��,�V�I*`;�j0��K��%�|�]�����F��Q��;84%��Y��Nf�.��6���D����P�#*�����Z�fKAhr�Y���f�mM'���VGo[m��]��l�Vj�Bq�����d
��C��\����bB>CCp^�L��M�@�F�~)��C�6�#�s�a�}�=�����L�]|�X�N�� ��%���E��a���F�����r��VW�u�X�OW��>r�������z�p1��ATs�����~���i�j�Gi����|���?z�w����hV�4����]�4,���&���[M�kw[O��9�^H��Y<�)nO��)�M���h��6�dR(��P�C���-�&;�%�}R"��K�����8��M���������
`�S��}���q���zy(��	U%����T�c�(N@ZB�	���
H�&�T%e��"L8���(�P�|$!�,4g�>�������"X�*������M�?����Ac��^!>����n�%�ai1qk[�BD ��G��3�
}��f833B:�`��b�}���-+��1�������7��< a0C���fg���*_�R�PhC�'!C�f�7'����z}��I[S�����R���Rf+��R(��/T���4v�gc�����^��t��XI�su����kE�({����� �$������UFp�b�j5�,Y3�j�I��"- b�A�S��Fbd
�k�@����
/E�H�Q�e-F^,J�����Qo��w0:���g���������wTN�Yg�Ei-�#�Hj��e��`%b�'a-5]C���N�,b��\��`~Y�.��O��-�}�"����a��*�NUk~3�bl��o���������;���R��i(�D���V�bf-�������B����g���]neu�A<{J��i�����k��& 2����d&��|K��sB���]��������'h���=;/V�Gy���y�����j����?O�*[�;�za�NI�*������������_b���PwU^������D�������PhOls��0�Q��qP�]�����
��V#�^}��������C���/axi'����P%��Z���xR	��������X�\ $Y�qF�d#"� �Pv�H��E|�z���;*TT�k|��a�t|�p|c������%#���h@>S~2J�!����jU52<�gfw�+~76����1�v����H���]+)_	/D;��l����>%������!�G�(�ad��PQ�QDK��7v��JdK���99y������$��1��,��6��g��G������Y���O���qAqAO�2��)�s��% IVWz�C�
�E!�RX^JRd�t�}�&&���{�8�Ew�1S��I�bs������t���(L�;����������LG�xr�(����W���4' �*M���ek�z�ku���Y�dU)�-IU:H�IK�j�K5	�M�D\��hJ�s������j"H_�����=�[�Q
�c?�/2g���������X�N�b�c��
K6�������l�~�hW�bg1< ����]���N=��u��#���6���O\�v�C�I�J�iL��82D�d�[f���=�����:����E��%��c�\�>�g�^��7�`|��&������������w�;�G�����f[��
5y�@i��NG55�cZ�:�v]�5�wn�M[���-�Qr;(�8��G3r,�9����=s3!Rq���x��I�V���}�B�1�5%�\K��f�Yw) 5	���������!�Z���B��{�n&��'�����c(����F#D^�<��������I$!I�H��#��G�;��	��x0:cQl����,Q�������?q����/���o�����A���1�,2,C�m��%?st{Eyr�����L:�d�������L���lnG��Z�H��#��"$�U]�J�����q��p�����S�T�����]�n?��p�>��.���!�Nt'��u�W�m��,u���Gi+���)�96��ya�r����S�g,������O9�_U��o�/���-?p�a
��Z?u����k'���G���l���^{�Oo�����;m������jM�4�5e��fh������Uk�d�����4}�U�
`UK��T2�J�J3R�P*Y*��(���z�!�m@t�������8�����fW���;E�'N�A,���q:d/ ���IK��K���>&TY���g7q�N��vS"���l���������O&��l��Rx�u�#���lYHN��������@p�^Y��8>9��������%w���u�[�n�i������.�JR��O�
�$4�w��;&�X71CGH>	ic�Z����6�_�;$�	$Zb[t�p9c��^���z����MVr0L��O����:�
F�::��pp|�+���}El��W���_��5�F��Ym*4w�I2����'�������%������*Ff��)|9�����	\�����;b��_q,`���H�S�T�bog'
���7`�u�?��6a������Sf��G���;:�����{{������Di��5���f."��2����;��;\���L�!Q�n:u �����l2�������d�� g�#X����Z��:�����V�!D��N8ds�pMz=S"�K�l�{3�
��0D����c����<s�P~"+{�j[�6����U4�
�T���7M�����t�S����?�b�d����-�����MK�1������#��1��,�E������&,��.S4�3�1����La���
 ���]{�uz��%e��%���U����)�\LV�g���������B�}��w,�]�2��AfY�Hms��ow�@/Y\�����;K��tqg��Cw���c|��>�����;<;>?9ec�EE��������f�gJ��	[)iv�hp���	�����L>u�&��$D�Dd1t'������9�|����N��+-Q����)L���V�~��+��g����L�]�P!Y��AD�:����6u^�7� }@��R*����C�y��=?x�c/ ��a b/���/X����B����p���J!M1y��}���k����h�fV�>�j������"/.\�S����$�H6�h74g�b���J(B��	����Ii�i��{��i��R�)��[���bh������g	��l�R&���:�d�Cq���Lr��d��	�R+�\�g�)��	V`�n�i����M�����o��e�������U���OG�.��q��-~���H���t-��4��dN���@�������:�K7L� ��q;��x.�U#����bs�Im���H��1�<���G�gP4/������CF�"���i��tz'#;N��y�H�qF��&��������VkB�u��Iw5�;�j�U��1��e��};6<�����	m�������i$��:�J�"G��X�i�2����Y�H��}���PHeEW�l�i�����"/�����EI�A��@U����eDdr��R��3/������|o��;�B�,����$������q�$J��8V��?���5�����}�fP�E�-�)�1�#2������W�;(��Oi�4(��q�Dx�a�xFJ9�����F�2E\������M�������~g�3y�l���'TZ�x`r����ln��r��c�P��9���
��X���X��M�6@=����9�5������]M0�fk<�!�m��|��;��9p�T!�<�����1c��xu�|+g��5��=��0d����Z�}�\i7���{k3����bfq�I.��b#%�J���h�z_�u�f��3h���a:�]}�}�r�Gn���D�e�4�����U����1�	cI1�
����#�[��b��8�R�������g�T�G�Y
���|�'%z�����(@�"��(�RP��J������H��F�%��8�\����n[x��O|_O��Pz��}T�G ���x]v�lZ
m�B���������������zF|����(�9��=���+�j3j�`���3N�)�(&�bwa�@���|Pc;�}8i�#��y���)��b#���R���(�q��	dn���Rv!L_nT�������a��1)~�=6�'%t����U�{nS���OZSM�oH'�A�{S����G�w$���b�;�d�YCr�����H��O�wxRpon��{��R��L��G~C�0%<��F&����~sD���	S%�e��R?}+�������%j�;v3)�AGy�b�7�'��W%��
W� ���[�o��7K�����BR@EE���y�z�T�F�p9H����n����*.������x!��B�b��p��ZPo��H�<${~��y�(�A�||�k7��Q�G#ti4�$r�3��A����������#�x)� ��v9���\5@TfJ�����/JJL_�R"Y����oT,/��DW3NLL	�XS�[��b�Xb�����@8W� tY9l����%����������~���)%������*4��" ���;�^U�k�D.�|�B�p$1��h��r�P)������d�9N��8 :��y��h=q8Do��D:3cx�T���$-<'��p�A�p���TD���$.��&r	p�{�W�m_���W�����&�/���&�N�$��2��\Pe�B:��b ����M��d31�ZW��G]��R�d��5>�(,��[��r���>8'�	��/����f3��Ea�CN|
�_�4��H��jm�/��ZB�r����i%LB;�@�
PR�������s��� �
�)@��H�F��SVb�",U�	����d�&/h�\����M�����@�i�v���w�-���L����H**<�����R�d��x��@��O��n�E�`+(�1�L��i��u�*]�^�gFRS���~��3�i�n�qk�T�������p7Q�]��Z�C`u�k�d���OAv-=� �� ����]	��b��KX����������0���)�����q?�]Z�N�}�0L�c��������{���l@��{��:�=oF-y�ZY��#��l�r������t�5eZ�r.~0�BL0�DQK3��Ns�+�����{T��Fw����cc'DI�G��e��=��<�����O��1������Z���$�;��S!&�����M���C�����sB�%��b���5�j�����i~a�K�HHD>r�E!l2	��`D�!c<���VW�����^�b�.�B#~0��>����"�rg�o�I�c_Z�-�B-�$�v��mtb�I��J���kB��nUq�0�>�9�fK��,������~���!��g$�[�uL��DJ|���o�kS:�����-Z�t�:�*/���0��2In�L$�@����`6$#����	[V8����q�p�F�@f��y��f�?�%��'0R,�����{�B��!4������-����(�~�b��>�B�R��^d����rB^�6���y����0Ga��$zFq}��U$r�TH�m�k��t����Z]
f�0��0St1�=fm�]���Z��_VNP�8-q���*R8���x�?�gn������!L�H� H�
�<�p�R.VVH��<�.��N���v_r�����|�x�5����k%��s�%�;X��0?�RON^���;��������k.��wi��������������>9�3���>?����x)R40��:)���I*n��$%*�����|d�F��%������������������G'�����W�7b2�`j���.�P���U/j����T3M��.
|��y���MQ;�"���o���������"�sEj��>4D��$���2�?[�<��TQ�*��
�m��1���c��I�0�
���Cp[��7���1����X���Lw����~dg����!�Ep
�������
�}�7� �^��.�y���
��N�)	Q�S�[:�3����!_'��	�E�>%�����G���O1����u��m��?��*�OT;"�{9�I��p�~�}�4��8�����|PL��� �<���HQ'�*E������w'�R �e/����R%�J���Nl�3�[���N��d�6*y��i%����
v��'H�L��Bw�)�=i@O����E�Xx(<EX�#���p'cr1�I��$���%����"SY�@�
L}RY ��m������w[��&�.��O��d{���83�J�O�����8�u7(���`��Cmh���B�CO��_**��)O�(V�:�?OnC+�����x����������3��n������3��_�����X�)��=_��[��?�!�G��Z�ef��#���;1���x�f����wo��d�]���$��KC��+��[C����w��8��cQ����A(��5�.p�'�%R���Zy7�� �������B���qQ�>m�O������B���g��%�o��������(Y�;���b�[-_���gwGO�G������Q]
�"����U���J�H��?��Ks$Z�����6�)���yL�+�������s����u���XB�C�~���G=zt�����+�hf�y��������%�N���(Kh�I�-��v�G���N��F�E���~b�����w+[���dch\����.�$����4k��f�mr����8P����wg�K��$�<u�<��	�O����L=3m�E�6����{���2�M�F�0a�;��M��=��/���$f�����M~AY�&�T���Vx<���e�2�����8e�u����3�������W6�E��m>�6���9�"����fi�ee�Y�aVv
�y\n�����gb��Ev��r�\V���#Y45�'g����]"�?�%����>��`Q�XY��p�qWC$\kuW�������b'\��-��8�'7����X�����v=j��e��3��M�������r���2�5���)(�'�q���~A���a�z���Q���@\Vh�w��a��#�f��;����GB��4ny���r>x�����U�����{�r�{he�L��R������$]+�����cx\���������~��t�����*{8������4��.�h0�'l[exz +��j� V����4������D�=N���G{O>no�T���T#�f�x����w>1+�6��C��?��aN	��q{�7�Ieq'O�.sG��Ya�������<��L�� �'8�U���N~����� ��h�:��V�D�=�d6���M�"-����?=��4�2M��caY���g���������e�$Y�%`i�<V������0�U��U����k�`�1��DO�W�wp��. �{�M�3�����W�#�bQ����<�
����+���"�NIV���`�X��T��/�T������S��9/�zr���c��B��'q|}�P\n��)Z
�=X���:�<�f]������}���aS���8�����t1#X�E��Z��P\^�b���#h�Mq�$hy���c���2���;���m�K�X�GW�Q�de���u�s
�?)(�������7F>��+C����~F��4��,3[V������i�Z���D��	����V��	��I�t�������k$w������j��C���x�+������,U�����d�Lw�e+�����'rz2�(������r�POb��mt������OOp�+���N~�����>��i��b�j��{|�����+����=Us�z���)fe�yb�������w`y������}�,��3{r�eeU{`[�����mY��zc#gVT_]��zH�}OaZSl��0���
�Z^��L�Juu�Y]�>�f@��.s_�-a�N^]>v7�M}���|�A�����3����tu���z2�����O1���}��'��G�i-�p1����4������<�}��������z+��*���"���=y�������;�&&��,P������:�G]���{V���fQX�S�aOy��6����6�����6�n�~|J��;�T���T���x��3a(������1b������lV�is�mV�6����<)���'��r��Y�����b��mv�e��4/w6�F�/��ZN�����=�$Y�OV�#�Fq�����^��wk�X*H�2\h�pQJA%��<U;�|.>dj�aMf�p�L��b�Y���,Y$��1!y��b3�-K���d0��f����*�p��h:S�L�F^�|>�L�K�3Iy�Z<�;Tf_s*O�����0G��3I��@}��5��9d�bNx��+�(cy��������)j.	�K�4B2����z����H�������F9���e��:�[0���f����2���b�g��<�#����vJO��=
�so��G=N���h�y<z����x"�v<��H�N�Y��bd�z�h_[�+�G�t'f�`k�����yL�;*�����mG���
��Z����~�V�9���TB��|	y����IG����keZZ��n��;����-��?�$�-M��&"%t��
�K4����$�+��"��$�x��9�i�Y���F�1�)�*���b8��1`�������g'�*�����a��;gp%�>P���u�+F�C{}����������>�D���\�5������2m�A�0\�>�%.�&���3���'������Hn`��@k.�u��Q���f�����L5��H������^/uL@�2������T�w5FR1���vU�{M��9�}���e~kH����F�)�r}+��w9���O���==_������Wo4k_��A��W�?���+���fk�=B�y�<�P;�6����:�~�W�>���GPl��m�3��`����	���g��QR�G'���C���T/O�
�6Q��	ek��6s-���+������e��J�[�<kk�����2h�Z��
���X�iBGS���J�����!y0i���2�7n7EYX0a��9��DN���An�n�]�����@N�$/7����irT^t������
7����W����[@�t�iK�P���Z$
�������k ���m��*�'�����L��%���$���E/��g��;�a8�>�>�
��'��!1
����6�lzm �m�Dw<h��r���d>���+ U��:m�O�f��p��sT����|*>�.�~)��	<,�����Ra\�A)��N5a���3��k�Z�������Y��m	(~W�B�>�>%![���H$��z�_}zjI�e��`Z����g�+A�zv$��=;Y[�`y��E�B�^��,�0s@�^8�P� ��	�,��.P�����g_�*����}����q?���T�=?������R�;T����~10�731y��}8^���L�Q�1q��~�_4.4����VI���@l�?/w;k�E !!�����?����7��
L��s�/
p���
��i�8�S|]���\C-��]t^(�+��[,l.q�}C�t�0+����S���I����.�2o�\�RQ<V��%h3e�@83A�ee%9�Q]Aj�
��
k0|�mg����N�=y��^�5�n�Bp�E��`70RD$�C�o�L���Z�FS�}W���7���OV�\�G!..����b���z�����M:�y��T��utv�8�Z���g��{���4�
99��5�K��==0#��mB��gF��(`/9qtvy��<�F)l�Y�g����5���2��<�!�P,�����.6�������,�2q��tz�0L��tQDZ[t�Pu��Q�����Xq��R�-���m�)�H�I���C�cvk����`5�����`��~&{xc3f��'�&��d�oaY�3��'�����z����7�F�B24����Y��7s����HN{��o��x��U������3:]cr��p�I��]T�8��k�������������F�}f'd�aHw��6��cd���&�TXr�=���h� �A���1�)`/G%Nzn�����4���	����1�,�~�`Jv�DP?��E�^�rT��(�(�����\���MKoKX��'�k+��8��<��|�y�m���d(r��vqt���e�J���D��SL��)}!��P�\&$<����8\~��	��gE��p�7��Z��5��;�r�vb�E��\d���~I9q�1_�����4����#��(0�Y��s3��(h���4.sKg���{�\����?�r�d��5�)b�V=9�������!�J?��zT��F�(��)��x�T�9�R��u&*������?L�gf���a���[i�d���SD����	S�I�����oN�bw������]�<~�2�\v'#z��1������c���$���f�'#��3Y��:}][#$	���r�����(!�&~��f�mw<5��+�1�P�P��@��:�l�'�����u�5�@�b�2����<�
fl����8�Ikq�~`��r||��������=���m
}�����"o
&�6�e�.�R�;�g�����>u@0�E��ic�x�1	�qf+)�i��XM<�i�X������k�hJ�)-���J	�W�3��.l�d}#Z�v�8�bC����K\����sq�����%��b�gwm�#����������|_������|���o�����[
ty�`��j	�����A(����6��
�qm������gg8�m��&F%7�o&�(W��4G}.{@��E���4��������T)p$�2k^^�o�Z���a��c�,R�@���W% ���7�e%�o0n.S�mv]w3����I�k�lk�ey�b��# j�fL����1�o�#��r�n��m�5���_e�u���m�8���?����~S�i�����(D9C����|�����V�+���&=��R�m9��m7G�jG��
sx�G�@��M�5�S�y~��8�}��xt	
�oP��y��Aj!�QUAw-� m��\g#UX���r`�	]�m?X��h;�w�����yj�U1W��)��#������5��HB��U`�a��wjz���Y�]�	�6����������\0��\��ZsaY�0�D.
���D'��+=���R�e�Y�+���'��h�^�5����&�Mx-�b,&L�5�P �&�X����5Y��$�2�Sc�43�l+�����C�k��o��4Ka$f��G����q($���CP����rKI�xl-�::����Q��$�#������eK����F�,?Tr��N (�2��}�,��b���[����MI���X�L:N�+���@����^����G�ACU�9H����^�o_��1Hr�
��v�
�r������W����{o�+���n�����`�4���5[���_b{'�'��_��E�s�V�u���rc���'���^�+��JC=P3~��6����IBx��|j���h^��g����m��w���3�v����EO����������^W��������(��������8?u�M���A�[�z��=�%Tc��W�9oL�)����5��5ae��H�ci7<lE���q�Q�x��� �O�h"�G�Hd���������~��V:;k
�G�JA�h1D�b� �+���@ug���C��p��6{V����r}�O�[�����W=��Y}��!��j:(�z�z\C��S��{�yBgyx�����#�������KS���A�nw�N�:�5y��o�c,N*�'t��P�`r���}X��H�Gj�@������
������=�b��<�n,��4fm��rV��)J��q�'u�h��A��&Y,h5�T�:d��%�����c����[����:oE;/�������X(�����h>�X4\�������	���I���MLf;����������A��v�AD2D���y�a�����i�II7��QS0������'��qr���ic�a'��V!X���`������r�MjZ6��6�,n�fs����k���5qtvr��4r������I9~���4�����Zw��lh��*��i����q�W������f���V����-Q���a�u,�
K�o���9|�45X��JJEIi��3��+6)��a�e��L�E3�
aqM?���
\R�����&w��3����qm��1g`s(~u�$&�l�B>���43�Ss�&�}���	MO���]d�i�){@�R��A��&T+ ������;�(B������U1Yj����x���
�z�3YN(���	�eN�T��0��U��5
-��Ni1���
��B����Qd���^���z)��g���.zu'()�F���U��]����^a`}J�i3��B;~
��MSBc��9G�~��n��������������G��"=��p<����q|T������o��g�������fk<��V�::���Bi�u)�.�V
>�:w�3��ov�t�a�����S�L���.����h}
/1v�I��)��>���V���EP���(��>��L���(��>K��O��3���C���/��(iH��v�U_ 	���d<�2�YV��#������^��	��qed���][�^-m�������c�<��H�G|J�#��{�sB��(/E�0�.���@C7�{kF��MN�J�#�1���������@��H�Y(�J���)W��5�E�=��}G
��J�7���YQ�/�H�<�u~s��M: ����l��)�����bu���3�2'4������d�	���R��d��5`d�Y���R/��������~|���f�o=@��jA�y�S�V�p'���$�����4��G������m"�zS���+'����#������^��(����R"��S�V�����}FD����\
������6����V�4\�������y�}��7���o1l�D��b0�70��#�*4D	SNe��:�'�I�f��$��.TJ.4+_���[��n�H��9%�c�C�������\hP~l���_���-�����B��&Q9����>��K_���2r*�����$#[��pj,���{�<������G|��m��,�>yRG��@�4��9���c>���K����R�Pjw��L4���nG�v����`A�w�L��i��u�*]Z��fT�wSu���"=�X��.����yf���&��(���?�N��]�%��JL����w�-��wZk����{)?H/�X/�R�Tb�'b1�wM+T�����������;A�D;��������K�&E�J�O�19�79��3���%�$V��#
�2��"��M&�2�df��YL���:3��	��R�2
Pj�V���(��a���"zWW+��V��"	��r�Ma��}_I�HE������a��raN�!G���"J{$���?� k���Q~j6���m�3������D,�H2@�l�N�3�iF8�n23@xff������H�E��r��!P����G��O�}��C�&�m��w�7������<�"3Q)G������	����]'���TH�&$72	�`���d���B�5�c�9A'���������H�4w�����&:����02�@�b�)����������a�w;��������7)N�sT/����|kh���]��/c�;���Sz!���0��!M��$��T�<|S�4|�)�f�G����H?	.��>`�\�1%b�?���T�A+�J�_H��Z;�D}E;�����}&1�����<���&_��y�����2�[k�)��2]��nO�d�)B:_���Hc��ccx�2|l28Qr����0h����aox���SXW��b����d��^O�$�#;�e�HA%�����������-� X�}�Hn�=��cJ���^��5��)����{����S{����� v�o�%L��N�o~�	�"+��)��T�1����_M��3t7���"������&�S�7�	mWLJZ��0m/e����^�w�fc�����`����)r�G���X���X���Xp�����ii��}�OG��aL�9�z�X����X����X����z������������eP+.���+.���+.���a.���������t��,�Zq��]q��]q���\������>	��E�i�bW,v�bW,v�bW,6�b�Dv*;{��E�i�bW,v�bW,v�bW,6�b�Dv*?{���j�eW\v�eW\v�eW\6�e�Dv*C{���j�eW\v�eW\v�eW\VrYRb�H6�>�3��j�eW\v�eW\v�eW\6�e�Tv:C{�c��j�eW\v�eW\v�eW\6�e�`v:G{�����j�hW�v�hW�v�hW�6�h�lv:K{�����j�hW�v�hW�v�hW�V2Zb��q:�WO�tv!�Zq��]q��]q���p�?�����tv!�Zq��]q��]q���p��?�����tv1�Z1��]1��]1��PFk�X�����Yc����&��;&�a������=���\lq��pil�������q�ep�ep��������K#����4r�X�	��@����T�����zqB��^f�4�EQ@��h���JD&utw2r�V���y�2����7!�?������}1�Z=[������wp���`��t�N"�/��;��s���L�K�����������Ae?�r������-����th0��V|)u.���������P#�p�btm����WX?�����S�V��F��4��D���,}���v�������E��,g��(s���]g`w��v������S*/����������3�\�5������2m�A�0e	����Fg�	/O��o/�o�a�����:Lw���|RK��G������u?Pj���I�����&^	l
W�~�����l���e#��]�������]�>'��h�nOO�������f��77��}���	�A��M�a�5����]�N�>�����s8���[���OB����s�vg���:��g_�[}y�V{�l�h�N�E�>�>���^�4�xm"������m����W��/s	/������j����kk�e����p1j�]�U�fdEM��V!��j7"�����,nG�f�J��&�����B�we�z�?��H�'n,��$�K��E�9���y���rt���S��ex3��-�B&�_�L.;_O�O����F�q�3A�m
n�H#HW�#���NN����g�Z���g���b���C8�^��a������_��v�vz|��D!+�Tbz�����g�>v����\+�0����z.�a����OI��`�>�Q�~~�)M�r����g���=�����=;2.�4�gQ�� ������6��O�Eoq�����> G
��DG������&)�q?���X�=?�����j+��~���b�?/�31������-|�k��/z���8E�$Z�O@l[��E���o������qVhb�M�P4��,�k�eV��&K�&����P?���n����fcp0�SD�+=o�as��<��X
T������K`�e1�`�F����sl���;��N|�����Eg�p@{�%&�;J�d(�T?<�w��Pj�!��H�4q}���|�/_���>BF��Z��9V��? V�0lGi5��9����_�����l��HH+����q�Pj���g�����f�-�� �]�62����`C����w��B>���w8��T��;����5�X[k���0�w���(:����4;��=k�q�����3�Q����������5b���#��8�+���7M_���C��=�P1�`4�
P?:$5�\T�8�,���s��Ba�x��E��>���_X�/F���5����h�/'������l�����F&�W$��D9=��q5l�c}�ow���'TM2���	��(<)�Q`^*��F��������Y�M�\��!�����=�P*�Vy���h�)>}!�_Z�%�	��3�n?�m[��eT����M��8h����F���^�ie�����,L����6�� K&���
��"nN�<E�?��'�n����c��zr2A��FE�b`��"��xO�P*������x���j�v8���� �DB����/J��|?��L�L���|��Zx~�����~K�;y����E��t@O�'
j�HC9bC�~��+F���1�7�8��9�0��5�l������HY����}�6�?��N��jrC&jk8�-O�
���9����+��lk����(�sBk0��4��V6)0Z����B~��d]�Y���?b�]�R6�k"M4�6o�������)�s^i���t�Q��H�vCa��-e�`:�P+�BJ���5?, #�\;�kq�i ����'|�=�{+�//@L��Q3F�Z��3x������f��sYE�������������T�0����^�o�Z���a���F���p�`�����Qo6YIb+�Zd�����n�6��6�9}e7[�/��?���o�$����f�"��,���F_�/��U�_����Vp�c�����I��75����9�[��3D�.���g(/�1��"9MzZ��h�rzA��L"������1�c��4�*���5�S�y~��8�}���rt	B�oP��y��AR�����6Z@A�,0
�$��W�
�#NW�������H*��k)��+��������	x
����kO�4���@Y�����B;L��k��a�$}%� Mt�X���+����7J�@�lOi��A��lB
��]���Mo ��HO><I��}T�8��^�^��D^F�b�a@�����Q�:�k���|q/_\_����i�������7oJ���B�X��v:��A�#���mt�/��-����-�����=������>
���[����N��������_��Be�X)���R(�����C`�*��_����r���o<�/���A�`[�Aw��s�=h�W���U�+t����~�*�;�=��{(�{�P8��D	�p�9����u����>���,���m7�i�z�T�C��srPO�m�(�b��T<�����A��~1n�������j���~^,�KV
+b�0�R���\�X���5@i�8�~g
:�o��mx���x4�D��P����^��~�r�����o{N����E������h0�m9l�Q�MloS�%�uz�/=���V
�%�@�
)����]��y~f;���q�]��}��f�<m���7���ce�|���v*;�|���[���;za�RAp����������.����v�}�	x�
�
�*��N������k���| H�: W����>\[���������/B/<{�s:dS�a�����jm
�&������F��3���������&�f��x��������L&�W��v�6#P�G�Q�:�y���^�'P
[�n;k������]� G������o�9]���(�� 8�_��S�&R^W��#4���nh�}_��Ri��0��g?�?>�����
?�1`pNl
�W#�������*%P>���C��fh
�hO��NG����B��0'~TO	h��m��	�����%�WB:�mn�G����px�l-��W��*��0t@K����j�
Qs����>aD�"!����l�
���=�����P�JR�Q���u��:�Q;P��;Mk������,�sa>��O�A�����eG[.����gb����Fn����;eW�z��G�3>���Hl����X���"�3������e�W ���_x�Pllnnd�q��e�}��m����R����N,j�}��)���+���;R��?���@+�3�qe�������B������U��t��F�����&����I���jm�6���+
���[Q$|��?�=���2|��1�/�����N%W,��)�-���j�m�N1��������d+�'�2o\w���m7'��<*�\��kk�:L��1�("���x��P{�����#�!L�k'��7A3'}<T� KkFe�Ha���b��P���O0��3�f7�/� ]���Z�Io����������������A�;A��� EJ��,�^�'IBx<��c��
�5G�@��#���:~���9���/�S$#��xsv^��=e�{#G�k��-��dA��?���n�D�q��6����H'I%=(�7�����h����S���5BD��m����m��5���G��xrL���_��Jj�������@�[WF���5q��N�1Ni��#<rh���W�A�>wy�!��
�lF���G[��t�g���/E��DSU��?.k�G8�
��l"�_�$���#���'go���7���m5���
;I��D�=2��B7�����IxG�^NF��<?T�u2���O���&28���E�����IS���~�
���l�p���]��|�q�(K�bXG��ww�����k�4WCR�<����S���4�RD�EBIc����OI-�vhIS'�y�k^e�����)9yupK3�K��@���]��8�8\��!av���5���������'8���+���Lk�V�m��yH�9���d�N�����^5;��p�	x��ww������V����#�o�SnW�r�]��v�J�t��������~e�����J�T��A�f�(N��^*@{���ai'��[����X�m����[h�k��M�Oi&Cp%��-�����E�/8��Xj���C��N��uQ����F�ba&�ql���w�r�n�w��%��:�V�Rh�{��)�FDk��G�
T�]������+��L?S����
h�]w,e�g�q��f��:��4
��1>l��A�|�9�T��l2&�
���,��V�'�p�?:W���=��-\�`?/gQ��Y����E`�6�� [D7��n=',�Z�F�.��X@������F_l7	z�vk������{%XI5�l�0�x��a��V`����5�P������V�#�c�E��
`lv7��&����5�L<PV����i�[�a]IA`�Z�Pl��8:�5D��;��[$kN��aCAO��[�����gt��m�-������'[q��+ m�1������?����g�C����|)��R)Q�������k"
� gJR�d@�e��G�tA�l�v�n������Xe��y�Kx�+a����lc�Y�r���p�P�������M
�����![|��O�P�C>��<�`�4c��2�)+C�+�'�����'��Or��%4H3�)�	�v��;c��'��*��L�4)���{�rX��h�������Yw���-����n�`��w����[���wOb�^i���X8w������_������]pwJ����+�����*�A�0�����?���[����[6�������*�V��������#�{TT����w�n�m��rF
�o����a�W`��^a��;��@
�v���z7��Gyk�Vu�:����M����F.��1��k��\��V&:NH��4��o���IqV�~�a��X�FU�s��l#Ud��-�.��>�}�Cm�� ���H�����1qZ�����&�!��q����0�z�"� ��"���JU�9������/;�b�<_���5].>��u�-�&L ����6��+����8���mM��d���������~�q[��mkT���X�]xh��4` ��c��~��C�}P����ljIUV�k5����t��P;��_4P�m��q�7�(���6f���N����������j\���|�0S�DO[=x#��l���	f�ki��)W
��ra��������=(��f3�^O1�^��Y~���'������#�)����P�K�n^�~�9�<�3��erJ��-[`�����DO�
��~��"�T�qp��6���gl�Q)�-�_��������&��������-��Q;�u�qm��:v��I6��`�����5���v:v`��^�����o2��&�k-"�f�X,w���v>�����,j�yZ�5��=�b�D��}�"�,E���G0K�R��N�b��������{c�U�FK5%���� O!�n3�D]b��S�dY'[w��:��^������=L��Zl�tN���7����+r�o��U������&�����&
�����C�����������p��[�V��o��?�X�~�8�p��1��Q���8�6�/���_�4
���U�����zMo�.�?F�U>�mrk=Xy��S��5/zl!z WW)c\�E��H�	�Q��c�j/	R��'$��������P<����.�[��d	~�;L9�/����ygu��t�o���mv�By�m�������n�S��@�.�v��Aq��_���B)�����[��EX�J�Jab�:�������*fp��~���`��z�V�}�M����({��t�n����8���@��'�W����P�+���y�{a�+X�� �>!��� w���S����?n�$r�n���=�O�@,4T)���/����pg��&��1���T������IA�U�Z�B��������J�6.h$�aH��Q��T��R	;s���3T��1�	�:��bRdP���s�S�<��|T�.���r�#'���������-��S���� ���J������R���7��������m
���F� �b�Z�#to�7�=���?��<����!(�6
���r�51)���D�C��qO*�������k��<)�����}�]����v�����U�k������1�s	�I�����7~�W�7�e�
Q;����nB�o�m�X����s�rJ��\�@��o���)�������������z���o���~Z�am��P�����b�U��e����_��(��&tJ	P����Z�
4jok��P��X��#��q�|<�;�v��/��W�8�d�S)��r�l��n9W�pD.F�0�@@�-����#�w���D�����.���N��*��G��j�Al$<rr���h����u2�[�x>��S�g���:n��Xr��?,��Zy���~Nj0<�y!��H���sU|��?v[�d$&G`(�H���79��)��q]F�ns����W���P���/v��s�g]����:\�/GLE��~�!�M�$$~(�0�����1�
�k���������e��B����CY���P�e��8u���1W�f��5t��9q7��d��WK��b!��S��XL�������7�/:�����Qt,|<��b
�B
��������r��&j���+��Xt���~M�����y��5�*�!��"@�����@��@x��h�s��<u�U����Q�>^7D�v)��j��@V���[�:\3�6!� ���o����������=7�p�\���=\S��5�x�U�>Rj�wP[`���y��s@������<�_:���-:�~�0d����7�����)�� ���k��72�C�������eyv�g�����)���+`�����s`��;��~e/�o�v�r��M�}��	���2�"��(�%���S����M�Cm�M�P1�
9<�"���R�����pQk���Tc&��Q^mY����������I^��E��S���������Q�5��\��~y�jw��v��R���*�^�N_�h+�%��`����wx?�m����/g���
g��#%]9A��X;V/s���y�yR��v�]�����*����<���3��-�n��
������ml%�a��V�'s�V�Z������j�.��OY��>�����T�m���I����U@�6ad���o��C�:���o����v���.<Z�������v�T*T�m�\����-��U*��E�������Ay��T���a��f�/i���J��
<���X�����.��H����<p����5K_�0�!� /���������%�����N>n���{~m[F0a$0R�8�Q8��x��|�{�y`A�(�\�����f �-`wxz����)����?�6��hin:�u�L�eaA��*����-�+���	'	��&���X����j�Y�������J;)A^r#���H�8�
?H��l>\!d�a���Kd����DxX�Y��Y�l��%��������c�hW�;o���q?�u�J��E����\�s��|u:���g
|����[�5&��w}���x���V�Hei2%���$B��3l������taDR�����*�%����/��j*��������=k���*����|~g�JA����0��J:���}E��"c��y��CtU?8�!�R�hU�o�F����mpJ9����e)��\-�8{�t��G �y�J]V�4�����p��G�QIUA���Z�n�W?�<�f)%�a���v��v����wvJ�J�u0��������$��%�gYE�"��a�g���9��g���@C�zm����h|�����S%�l�z��)�n���e���rn��*&��@Gv�����zd��$��6�[�����������(f�k'guP��������v�9�o�aV��|�z� W�1��	=�����E�����������M�A�Lv��Y���m���nH��nI'.4^����Tr��X� �&>�
�
	"��B*�@��o��%+�o�D�;���p�hl2�9g�����	�����P�&�j�t�,1�����mNe���S�����ja��X�@,��M� ��\��S^
SE��kB�S��V��Z���r:I)�y��������g���EZ�!�T��1�"� ;������*��|�P��;��+I��&47�%b���78j���������b�bi� w����PV���z���3���������ojM &��2WbTq|�E1�`�)@��a���7���F�+#b-)�e"���!�c����xP�P�+�s�<V�
�3�(d���Rb|!\@E������C�}�&�2��i�Q`�������C��CCC3�A=]hM~�r;���-���U8(Wv-�JrJo'�.�2�-��$D�_Ai �XST�O���f�P�I�HR9�E�`������Y5�yj�0�L�f
�GkNlw����&����Qi
��WL�����g�Q�fD�)�Je�r����T*-�����K��Y�N�{0
i��4t�������Md0n���g��t�����G�������CFm�����XI)E�����3��	_�l4�	m
�8q����;��.�����Z�P8�``���Dfw|u���7���-%q�X+�������������u�7g��W$L1���&��;���n��[*A��������S)X����~��v�J��e�tv���vh�~�������(��{���������V:,�B)�����[c����������m�g�P���#�7G�
L�O��=�,g��x�o�^]6�l������vm���P�������2���
����S���UN�y�������}�����O6f�\����y��\�\�'Zv�|yF�jRdh��a�G��-�b
FL���d
��\P>?��~^�s��7��S
���>�kB�Gw<ZW�aA���(�����6P����������h��n�H����LMtW%���|�p��%�:�]�|�t��u~��)�
2�q�%��b��5Q��
_-���['��?�:n[��	���PX|Zw|�@��wZ0�_(A�������b��5%�V`N�q��1��GaC�uyM����P6:}��m���G�p�
�/[7B����GF=pa�w�����"9�����wi��I��G	��U��s{Y�����dl�pY/5t��������7R�mr���5#�9u��+}K��k��[ k�	�:xk�~kK��n��w,��q��0�
&g#�Q�� �!�O^m��A�`��^�wM��4��$���Q�����O@�-��5Ph���r�t\%��69�i@����4%h|���%.05�Q0����l�^���P���Gz�Kd@��q���$G��������n�c���B�gosh��<�,����up�)���Rws�A�{6��|hO�g�4��`"��m�������es��E��$������+'�Of��Z@2z=98Z����w�0�"�S�~n�<��jK1�
����8���8h�Hc���Q�
#}^0%\W���g���f�f�4���\5|>�{�+=���k����"$�`�y�\@��?���-�9��X>��������VAZa��X��vuX�E`�r1:U��:�4���J�
e�n��v>_�����U.$��Oh)�*�+�����^�@�
��[c�������n����0�l�y;o�������'=�%���!��������B�n�.$�O���|/�2��)b
�]�%#����B���"�D��9�_��gh����w3ZM��l��W�]�9*��|eO��F?;�����gh�AQ���_���O�&���f�p����
!�mQ�a�E��4ql�-�7�]
^u|��v	���M�i0�z2do��lU*�I���DI#>�0(�,4�SF�R��u��|4�	���8{�����~S&�����)3b��j���48�	A�v�_wL���{��� ��u2
�7$��r@��HKT)h���pJG6��g�6�a���^;'����~.`�o���f��gE��x�����
k
J��Q=�k|�I}q�b�d|�#e���������L�(��O��g����	"I�v~~v���dl�kc`<>:;�5������g4/.?`P^��������������4������h���
	�=��FV���^��8�-�zu�c��	hC��I�IgJ��z���W�$�>�"�j\����GA9�t����WW����\u�.l�:y�,y@�G�6�8���g�F��d,[�#u����=��o)��c��a�~�����Zt\��b�g}p���H���>�����B�K�Hx�O������<������A�`a
����iL4��	�+z6}G�
�;u���daE1s,���P��J�������`eT�\P��.2����Wy#���*��n��3�l����'F=�
]UF�'�P^�	
������` <���O�����t�<������+��Da��t�����j�
B'WE��*v��]F����_�3����_l�4'�w2`��A��AJO�@���"���:���Y+&�l��G4����0&\3��L�7'6h�Y��������ur���I
d�'��{�w;�6@��������Q�	������H����b![|���,�FC��	
�T"���
Pt�y��>#�Po�<6Q����A��K�������wD�K�<�=���pcR��d��a����j���$&���nO
;�g0W�=%4�����p3`C&��ih�y1�7:bc,��n�lF���������l6�����0D�{�x-<$*��u�`l[[���	H��J�F1f!�&YK�7ka�j������j����]c�T����)�_�Q�\�d������P����X�G�GX�|d���f���io��c�:H'5��-xj.��v����J�����0��%@9�[r�����G������%#%&+Ki���%�|S>0U�������~�)E�
Pat�.����M,��'I����%0Oj��]�N?��Y,�v�����L:�]Ag��(`Ar��u���V��Q	s?����r!�5P3\��������d	_�
��)|�U���{�nT*dWIQQ�WR
���!��`�r�0�T�(��q(d��wy'��~n]�v�s�����l�37Ja�dKX�['��3%m	}�Y�C�]PM��=4U�����>p�U�T��9Q'a��&Z������ Z�o����62�X)x57Zi��k?�3���j^!�M-
��&�h��M��~���5���qB >��y k�g��K\��N���p�!g��"wv�M$���J��,�@�����q�K�)�Fi�P�,N��R��;��i�![$@c�J�`
�<�2#�������8
����5����7|@��5����6f�!��	4��M�������o������P�0�Z�������a�Y%�Y)����Y5^����|vZ�-�QJ�}~z��v^?
��	��W?��
]>�$<+E�=��g��&u�/G����Q�����T�����������J��R��Y�8�(>r�4��q�Q�>k��'>k�O�il%U��F�������V���~mD�%�>������y��x`j�F|�XW�6O.N�U��FV��k��������J��R�YtU�Q�����nb�
�b�*�E��WU����"�P��b������U��	�J}�����4�Z�+��k:LF�#��3�Z��!g3C��@�	�)b��iJt�_�\����r���?�_�f/�o��Z�eZ@����S���N/��N���e18����>�Fy!��P�x�1
�I�<�!+�"|���,Z#�Zct��o��Z������L�,+e��������zv/���}�K���}����%�_K�I���uc����8��������^����F����jZ��Kw�������(�5
�(	� ����
���u����xa�E���w����M�Z�eNp��?T1��� �0t��q��j���;��� &�;��)p�Jq0�T*��^����T�����*W��Jyw���	�LA!�v���2�G����
�y���Yw��������z����?���.���Z�2���x���?�TO�������C1����h������p������$r�w�V�x��g�V{j�W6P�!y;\|��!�.hMr�����_��,�GB�`A?H�8DP���[R���sjT��~-x���F�Qx�1 �Q�����_��o�,��9�&��w0"��������'�<{���L��G��'���!b����@�Z�������0'�k�08/���dA���#C[��+�&D�u�:�����$���	�1�>t ����t�������$����F���Q�Y<����X������q+M��Z�������J�m�i8��v�5�����U=&o�zq�3B�~�?J(���9����ETv36C�Nk��>iT�P����&6��Tp#(vvr-��:�g�������!_��"��M�M�P��{��"����*�j��sh���<1��"��w8p?�;��|����c���al�aw��d�b�8��8��(}�$���4�AMD�����
tG02�6�X(�Y�M���5�$���L������uBk����n�����{_Ur"�9`ZN�<���x���~��o�Op4�s>���DH5��)H�[9/\lI��y��E��6����aB	�f	���:1@�2����i�X#<r'E�Wz�0�q��
\P��N��I�YcC$)�<���8��5����1�Gi<���^�v��q���K������%���99Q�G������/���\c�r|��w�4Y�������U�=�i `b�he������x!���X���a��B%��9�n��&%�P�`�i��m���t�W�|AE�1/q*&I�=��!������-�`-��7����9�.��,���L�K�|�#�G�`����U<QN�At���u��Y2(��2/�4-6�[�����+�LDz��\�(r��;���I_�I�����ct=��}B��ff�����KbJ��:�G��&������	�l{�,	=d9K�z�;�D�<������,Th�)��DZ��N���i
�3c&1!hRGX�6d��i]3�v��;"=+bM�B+���.�����&ETf�&Y
X"���d�+�f����(�@<����bP��g�:���F�>�ae���A>H{>���3�2�8P	�X'g���Nh������Z	-���[��,��-LK���n�	�I��L��Z�H]f�j3������C`U$Y�G <�\�c
+YyAC�R!��NA��cq�}����A|x�������#���Y={����N��q�t�d��.�vyC���I���P���N'VL9V��O�z�����sNM���<h�<�
���@R#�B7 ���yT�L�L5�G(�P�(���TV	ipE�( J8Y�t��^�!3����0!�f��7BI	6��|��3Or��V�x�!R*bh�.�%�^�UZ����&2�_	�b� �vr�B��)Y�Y���P��&�;gF��m�#�������,��o^��
L���cY�Z��e�n�Mik��V<����Z��{�8��S~r�Xj����X����c����$���q������;�M<�.v�+�p���i�*Zl�h1JqA9rX��9�Gm��*��i�<����p��"�B���Q#���UX�*��n�OV6�L,�N(l	���$�HpY�}4��B^����X�K'O"�m	�t��]�[\*w�0�Q���	!����m�"�F��6x( ��}C�"���o�q��4�(�K�X.H��EF�N���Oe{e���Tb�(������u-�V>�[6�JU��aM�,�����E�0������B�����^Q�5���~l�{��_�V��3�:�B�ng/W�Y���t�Q��Oj�����������4*|?��<������J��,v��i\L��������"%��b�I�+��I�t|,���Do���Y3�<�L���e�
@8���;��O�h����-v"d)a�,�������0$![������������^E��X�
`J6O��v�L�	�����9�����-���5����V�5��)�����<:��0������
��${.��T%9V>�����g'��,����Ko�j;��@��K���b�o�g����[����O2U�j[j�uU�xA�s@"A�FkD��F����KMX'��F0�J���R�����%(�����!��
0AQF"��hX`�4�����H��I#!Un�H�se���$O�"w���V����
��y#3i�P��?�	���a�����IJk��2�I��0�]�)������H����9�������	E�Bg�!���1d
���	m#�r�����W2�Tl�A�g��w(~���N�7/U�w�n6�{tH[�F�B��h�`�fI��i�[$��IR�42��:k��]�H�����\Q I����P��<�3hu��3�~��=!z7���J�2��S*���9�!�.@~�!����G�.jR��`�C�:�vFD$E�EZ]��1g
3L���P���C/8���5�-���
nlo=�����d��+��#���J����\
���)����:1�����8Ro)�0���i�����6��,������J���k���.��!.��q2U,�K&~�y��v�D5a��f,��^H�6�i����h=H�
,�}�D��QG�v��ac����}@4���{�(�?�j$ g�����;O^ #K��E�r����6��L<��)������������)�^�2ktp�_]���>�#��E��xS��A(�"q�N�aa, ��w��5`�.4v����W�����r�Xc�Ss�����*�(�B�R,,kYB���&�3���ZN�����Jb�n�C��T��5�YA�G��7�p�	�@��I�#RnJJ��
�����k��C�����@'���G�����VV���Q�n'�Z�7y�c[��w&S<'��(��?�|�R��~�����(����x2]����x��;�T�����@Ox"��P�ec�@������~K` �, �����b��%���
�p#�a�'P������oFd��>��+W���\r�o���= �y�����
��N{�K�_p/6����� ��JYI�>@���W��i�Z�l�(�Y�c�=�2����:��QR�)K��i���3�������J��/h���k:������D�1;��n�f��t������VhRu��-���g�i�| &�k�
t�������.BW�sVq��v^m�����|)v�yvID�8���UY,�~`�*��x�y��YjxW<5ec�,j��H�Q�7�wEq!��g��s��Uf�$3f%�����+a���"�Fg�z��3L���v�!����c��� �������c�8xH�78P���I��G����k�����
�|'EVQ;��J{��su�k��������-����}q��oZ�g��G�����]�6��Y;����vz|w����A��$	";�3�e���|��
�<�kqR�^4P���K��7���J�=�U5Y�����5;
�b<g2e����g��M��RX-6@MH��7�A���bC`���X7�x�����O�(�4��)�^X�cP�>�U�=z�a��v���H^��/���7=�:�H
�7a)�h{�H���z�p�5w����4�C�x�$6���&l���M���N��/�<���$�O��3�
�{���H���`o�	�Km�Ey��x��T�$���s|������p��@����������L�b�c5���"��9!�OkR�������O�Im�|Sg
h��-t?�����/���A�����1�����+�S�~c$U(��J�L
��������/�yC���=1����4+KO�\;Z2���Uq����t3@��	4�12:%�bjM��)�$�����e@DZ�-)�R4����AMt�{���J� �X"���&)�Rb��?�j�B��%��D�q��`��F
Z�����������D�I<X�',`L��TA�������_�T5�{���R��)������@�����m�H�Y�3��a��W�M.�FJ
�F1f�e^���*�5���d��v����IGFSz��#+!��j�3\��B4���b�0{���N`���v�0�!��c�+r��o�f��T�"[5�_K�	u��*B���E�B����A�<�����!�k'�La�k�nx�C�8�s�cE^N
}��A�#g�P�M\�z�������ev�<S6�3&�Tm#���H�A4����G��2o8�����r�7�C��`����
h���S0J�����raw�9�����<&�9���n��oa�n�
F��j.67XT�,�x�	������3������U��^6�]w�.����F�K���@t�p��$��(�Gyk2WM�����I�d:�<��k��T2���sT&E����JA�L�Z"POa��A���0_��b���I��A�(�C�e�_�b!���Fe��!�fN)�G�G;S��W���"(b`R�V�1� �������l�������g�Hq>=����M*�������������i����kL�X��tu�2����S��L��i��������q�����(�����&��Lu�W.��R&L�\����Z�,>�:���%��������!����������������5��y����HT/DfcMZ�z8��1Bx��f�|�|O�6���I�h��gO�mdd�4���G&�����]K<�X]J��Vt��
4	�,��QK\���&|A	�{s~�Eyh�����z���I��d���z�������p�Ck"�@�'�A^N������x��#�d�d�$An;+F�@O`�A���_&�2��B�Iz� ��L#~��tz�P�t��`G)��� �!��������H�]��T(fC�-./p��R�>]9�
�%6��������-�V@���	0�"<v;��H�����5�1��7�A��d��S�
��%�P+������la�
}�sH�I��m���:�'��~�G�:�������|����������W�a�
zdF�l�F��h�=�{�qS�&>SF�lb�V�2�5�-�2�)_g�a�y"GN�"���yC����Nd��:IF�I����
2����I�2���+�;(�2��3/��o� �Z������(����s=����u1�����X���)J�W���c��P�R�L�������0(�.���R��$�{L]� &me��������D�Q�!:�m���(�I�
�
=C������.U
�����U�AJq�6m�(J�����\I�;,��T������y>��8��l�K����"��]:(����=�pm�k�lkA��r
�L�y���cc���`a�N}��D���nV�`�=3��P���Df���4N��2�a���;����g4�l#�o�H3!�������q�H����J��
0�����qyX����G	e��e�)5G&�&�$�\����t�����^sf9%l��?�����N^���P�>Er����������*A.N�-��U%e����b3����3�Ez����x��M��94�������0��������Z�g��Lc���?U!�}�m�C0�
<�^�"�����t_8��i��l�I�n<��UG	~o���Ws�f"@.��Q��$��E���Q(m��YCc�L���
u�8^��2o$��Hu�0��
���~,2/uCU�T>������,�%n���"�V3���+��w���D&���yZ��<'��I>���!���&����\{(��O�IQ4"��^u�8<z``Z�)U_^_rC8?�m|��'��d�#@3j�+bJ��5�upk���`�TE�	f���ti�qhE�0�����ms[��������_����:Wr��c���{,��S��=�a'���kYQ������t���?�H)�����A����RVsU��e��B%9B�0���B6\R�4�!b)yJ���Z��%4�P�_�tA9�@���=:��k��k��Q{�s)�z�Zm��mm��7f��T����W����F��P�8�C��=������}� "�:��h��a�:0�40"\!�X\"E��L�-�GNR��~?*l������ya7��X��2�.�h����y�7�(��T:F3��Vd�^\E�:�������z'F��|���B�������2���|����tx��I���[�%x&�dB���k����J�[�����D��tj8��-n������~G��S������#�2I�5x/q��	:�1�]��4���g�='�P�,?�,?�p���%.���??�K�i9x�R�mb�5P/'���	�t��J:ch�~z"i������`S�2�F�Y�2�~S`Zg�������)���?J���%�&�&H��s�)��~cZ�ddZ���#z9��,��LR�{(�B�LcL���&�?TW�����P���J5��Q�`[�������\�!s��^!D��K+x��t ���!�G!��B�e�;,����ePcc%��'�N����6D3����l�P�VS��i;S��J�����2�7c[s��-�]9���o8�	�5��^
��xx��,�lB�~|V?����z*=��76~9�K��DA��
n���&Y��?�i���
��8������)k��!QZ��7[�w��W\4�v�
����g��l��m�XL3�~���l����1'kMm��pk�R�k�R�D���Z�3!�IrZ�������O!j��G�y�w%�b��i�2`T��,��c����s��[xOs��5)�H��~�������#���b�������&�pR=m����'G��������x��n*K�P�I�W��P%��E�[��2O�X���+�5�2j�sM�P\9^f4r�mi���4���
�/q�2R�9v^�72�9�(TD�&.�l��.��w�e�����Ve(1�t!�,��;����v����P#�'4�����.-��B@�@6���������Y�T��U�h�W�
��s"����*C_�`�.�
��f�I�"�>�y%2?����Zc�=l�U���

���(e���O6��
�������u�dlZ|��m����sjl�&MC���d�V�m��#�YCP*���A|�������0���\!9�d���7�c@M:��p(\�GV]"$b��C�����s�)��c���f�f�������S?F�?���v�d cDW��6�3�2�A'��@c�c� �l�.��uQ�����51T�{��$��xy�"�C]� ��%qH���������6��6�n�V��c�U
IA_~����x8Iw�,O��HV=�I�,g��t��!��J�6M�Ko��
��c���#�d�n(y1E>Sf|����S�,GA�����!�G�������6�Xj�qkmA�����)�U�XuQk4?VO.k3JVS��d�J(��Y���FYu�?]���d&�����-,c�_+!I�x�C%�����1�v�)D$y�e6B��w._ 0��~�h��6����v������L7��zUD'r�.%#�<,c!Sb�>��`;����
#������$��{?K�
]YZ.���et�����E�G�{�<��.�����' ��z����@v3?���(���y��O��`�=.�Ro
z�a<lJ1]���j7M�:�@A�4��6q������&���L��O���?.k���L.��S3�$�M����������Fb�tE�t���<1a�
����=s��<;;�<�B%wp0qyf�E��1�e�3�Dc��6a�zJ�M}�\�x�3�=��|����I|��M��u3���a�����U6����~5W���6i��d&�e��8�L������<X7l������q.�\����
�#wv j���O��� ����?f#��F5<����#��3�|�#H��z����	�C3����H�h���/�!��_�c����F����g�4K>l�������Fd�������!�5B�����\Dz6I�P�48R>Iw���T�o�0�Iq[{d�Gb����Q2pQ��/��X	��e�~vz(c�9������7�G_\ot}+��$T�9�C^n���\�����b9��&��JVF���Z�Ij���O��P�u�&��y��0	�����k�`�_@�����s��J�u�~���t=�&�4�[O3�w��(�P�|w!�,�����O�t��3�*��8`��dS�J`�A��������+L��h�� "�
i���V�DO�$.�}9�\
9�i��|/�(<��!BJ����ys1z�������p���q����[�����"�2�]D��!l��Q��'�����o	
a��F ��	�}To�z����@p�`�����$�v�jX��>
���a�����"�m�cw�H�od �|�'Wm���>)�bhu:���[�.�;W����XJw+��)�E#��=X8���
��~�U���z�<����0[U[@z�-d���z)_������h�j�,�j\*u�
��6l�m�T���p&����v�l�&��k���Gv[�5�LR�daw����������R���b�P�)V�;���J��;����C`�"��_����r���oP�9�iv�+�r�*�v[���J����vZ���%��-�wZ�$�{(�{�P8��D	Vs�9
���o�w��X����w����U�rz����~��P������@HJ�RAw��bEl
���1�>�~�6���B�y��/"�|# a�`���M<��)?���������1��41�j�$��-;G���
�[�^}
�4�v��^�)KD��)$}
���������LWUW,J{Z��}N�n��]*W����xB����.|���e�\6�<��z�S�������(��-.���q�N*x
�P�,������5�B;g%���g�@�/�$�����:��mo_�Xn=�:���"�|�**��A��v-;�/��{�^w�h�"�Jg?Co�[[[���**�#�P��"��0�cYh�g��|�_�s����/��Q������q�����������a�;����4k�6j�(���W���Fk-28�����P�O��(����x�����b+���P��T��X_�{L-�.��)sI�A������y���N��y�0��w@kj����^�.��������:�;���9��3'��+�����/]=��
����/�uY��L�qQ;�����\���0|kG����|��w��:=n��&=����2����������i�@��I����7e	�:��������^��d�og�P,F��R������O�;��_�����c��������*���b���V:�=M��
`$N�+<�{9����=��}�����J�k���(��(�Y�+���n������+��J��P�Z?r=��#f�{������P�Z��9S������
���'hD�d�9�{����������A1gSHf?�F3��G���'���:p����\���]��N���p8��s�orFN������X�Z(�	���3	�E�9�q���_8Y�����K��#�Um=m�2F�}��n�5!��1�K-H�#�"03��8RO�m���:a���X��"����C>j�8\�6��t��_��	�tE���1�����9����b1h�:���Jm�_J���K��~���<�O�yq���_z]ij��(�����F�H��v����?�,G%��A)*��KZ���H�	�mM+!%�b�U���r�������A�4Q�Oh)A�O(���~Ec���
����$��~�>�	t�y��D�/8�f������?'����]�>t�i�,Xd/����"������(��D�� �*s�h�C&*��X{&��s����a�7&
M����hn<�x��)19=gtk�%�!��D-�?$AQe��V����pn�G0Ir3X=7�n5'�P0�^� �����
\T�e[�^ZC��`�"P|X�1��B.P���:��A�@rn�mOo0�k�L�J������a���M_���K4B�0�]{�@���W�w���A��������7����9�P-7I����+��,'�Q���>> O���1�49�7h�gg���hyFQ����[tTC����7�P����al�Q���ZaN8�V5Lar<1�,����8WZx��t�M�W��Da�\��I�0�;����~�l-�	��!������ad>�������T.���j�5�O���f�
���o{!?N�'��hz�HF���9&Eo��\�,�0����g#�S�<�����������|S�6.�k���F�������F�XgAP��_e6d��z�D��g��L�M�Y�lq#��5���H�2����$`�e���Im�|�1�b
4%�+x��^e��B���(��X�\j�8,!���o\���,��
�D��N�u����I_F�I�}#��T��WUv�r��H�'��xc���5�7F���C`DC����H�_?�7�^?=�Q|�F��m���8��w�4�x��"sj����N�@"�f�l+%�p�g=����('���s
�.��(�4Z=y,X�\A~��L���z��"���
Y?�o%<my������C����2��)��P?��#g<c��2�+O�k�:�H�5�"R�q�3�R�GtA����F� _j�	@�S���^�tV�!�h�����'�{y���C���rd��T��1�����#����t��d����w�D�
��<wc�fS�W\dt���Gw����a��q����~��?���ve:<&�^��S�d�5���#����`5�z&�:�^�
����9��D;�����xh�#�����r�OlU�����Qr��Y'T����/�
�^���������c��D����<���}Yi�.�@+**�\��r��N�	U���~6b�o
S:��t�l;!��������8��$����A���Rl_pn�W�`��+���:�&�R��h�+�#
v�P^=�pV��^������63@������!��9;����/�U�k����bKV���&/�n��I��x�7�ZP(�6Tz_�����r�F���y!���10�������+M#�[�,*w���3�^]�K�a��hY��gs���XE�w���d������������bY%��{���I�Q���b7Ss���1_	C�K���Y!���R�",���z�i��*����� b��(s�P[����&��;g�F��\�k1�����q�sW5�6���pw?�)Ab��.ZL����\�����
�w����Y/��xk�ha��=����G�qd�z�e�b���LBY���I6x|��,~r/������D���F���"
�|�Jl;�?A>^{v���~���MjvW�O�D���������R��Bzk������j=�R0�5#��dt�M��f�e��:8u-�,�6b�ZW}jJabR9_��YR?ay�K�t������`�k
^!"h^�()�V���=Iu�v{�k�g� w,���r73P�}6*)�j�}�F�c��P������!����%���]���������fe	9��L�4�eMr���@����|F5�:�w5�	��r90�|��52�Gv���u�iy�i��D|�G%�Sj4�H����	8-#S�X�2���34�+d
�*d
���P��_�7����<�0�9�$���x�l���JD*���@��*�J���J;��;HK�����������#%)����yGa��'"�2���=e!����k+3�x�`���z�����*�������bZ�0{��M��rB�MX��A1�*�u�\�����V��}t�*�P!_�<^G�I����az	Q3�Q��O-���BP���be���S�/���	��J�TD�r���ap��QMQ��iVd;,����]e��A8U���;vW�r�E<H�h�8�_��N��8I*i�fj ��g�]s%��O���m*��cm%�s�0L�Q�V�n:�;TM��$�l��5���r�Y�G@<���!��PE���)c��L.�T[���3uk)��E�Y��;X��Q����tR?���L�K�wm'���7?AL94��pu�%���K�H���vQ"�$U��AX#���&��mi�������[�=QE�U����)��r�$���W��^]�U�)T�Hk���+V��P�y��.Q���n4���Z��6��OT!O���K{M��97���d�
yc6^��1L����F=�Ac��q��m^$9�]�q�r�$GH��y���!4'
1�VN(l���`��_�j�k_��B
�4�{i��)N�������Z���[)������By���?��f������t� }���Q
N�owt�"�a�A��|x�|syzD�/���c�'H����������x1��dn���#j�|���a�����$�3i�['�+��*M���S���[;Qg��+�������;%<e��)@�lG��y���]��3qRo���xJ��W������3�%��*�������L�n���n`�wD)����&�=���'�?�w��Bz����Jf��Y�����\����~�1_��_�2�$JA��t%e1.m�W"tSV����}�0C��	I�R���Ln�d���]	k�<P����x0�tPY�CXa�)�W7���sz�Y*a���|�f��D�{5-�&����Z���QF�o�<i-0�a�}���Y�I�� �Q
f,��M��l�= �A���E��Ot}�"�R������f�k��}���f�Fww���)��(q�s���r�M���K�.���=Q%�/rT��DX�!\`�p����t$O��$�%���1%1%DgA�"�_����O�PJ�*�A:�+�.0������l�}l���x�NS�m��S��9/�M;x !EJ5������A�0���&����L�����d��zC*�
�-y#�i;�8.��4q�Y� C@�:ZR�i�MWH��/z`���7�����E5�j�U���F�XUiD�����ji���
k�����~A��-a��as����o�w��L���{�"���{�=.�
;B3�����A>F�H�����c�z�����k����q�]��%2Z{����D�e�I����i�����7�v��e���IWJr���t���\�y��!V���)i��1j��d7f���y�L���j���}�J����
;�re���z���p
���
�t���+2���)�%p���� �rS�c'zC��Z��������JAdlL��o����l^����e�a��h�k�6�%�T�y��6\HB~�1<���Os�7=��Z������S6�Z�E�r�	%r����;��P�2c����2������RYN����j���N�G�F���Zni�u��q�Q�%�3�(�(vr 9/�V��bY�e�j�1n�M������p;9R���������������b�S�-��~k��=����uE���Fm6I�ME-�Q��pNDo���%���H�W��R�����SB1��l &��>B�(��kN����_�@(X*l��J�0���Q�L�od��LFQ�*��-O�Pv�T�@4��X���M�������R�Ik�K�#���L���0B�;�\Pn2����*S��V��D��2U���%6VY�]�����?���$2%���n��	���H���w-�����9e�
������yZh����F��z���������{������y58�p,�������U*�q3��L�����4R�D#�|d��<���az�����^%��TD�}}�(�9�|���8���X�Wq|�
S>��#wX��]�����
�l��F�"e�N�, �Vm��;h'�L8�a�G�C���z�6�[X����������pa"I{u�?����
�p$Nf�����4��Q�.��~��������]/�O�w����!n6]�ks���,Q@�n0L<�TR�x8��iL��8��i��y�~���n����l�&�,�\u��~0��H��p�Z�����Gg�����"O,2�����'��
E�iJ	�h$�{��F�Qj����|�u��/4w��I�����l�jb���d���H������@�@b��Kb�hP�T]5�u�1�^�� ��@w-D�>!�-1>s#` l0g X��W|���f�����= �0�1���{c&�7�u:�}�&-���P��Sg!S��3�r���QNr�?ds�[���*�e]C��@v�1�s�9:�B���V��@����7��u��`��0�of��
"�Y�X����",�&���FK6G�+W���|�	2�B�z�<������tNAqJ���a&=g@q�|6��Z}��3 ��4x�&Xi[�4w+���8�;�M��>@�Q��47k���[���7q�z�BE)���������.�6��z���0
\1��;<OU(0w+��X�a���$�1��| Z��YK�����i�k�{#V9�K|6l��13S���ii�%41)�T����V��6���D�8��Q��5�@b�(�D�h�9}CH�@��N��E�uR2os���de�������{�*�EBs<�[�t;9_%]�%/W��#|�
:�M�8���I>R��y�j����S�:��f���)��&�����\�G:���S�U><��]v8������i��:�Uk �\�Z�+���1#o�����L&c������g�����]-��o&��rB
���*)fg�[l����|���iuv�����JH:/D���.�e�f����B��5���k���u�F���w�����L4=�C�7-��_L�5�����Jdq���0���d��
-jJ��=c���T0��7�`��i��~���dJY������t%'��}���^����TV������������&������Z�x�mP�m��=���?�B�������������}g�d�K�;{�bg�S����v�����Re��9��Z�������?����^<�vX�9,���S��	c� �4����w����997�#V�_�{��
?��D��B�*�&;��s�Ld*��)�6���v����J���Q��uzuL��K��F�������R���JBr�uQ�&���+D�|�L��=l�	/��>%�S�����|���.����B��FX��P�\�{�>9U���y������vig���R�FQ��O����9�J�?]�^�|n:�TY�p~�|hy����IP���z���(��O��%��u����_m��7�A��l�~�M�$4����!����w��!�C����|n>/����_>��z<p�3�K4�.�rL9��4�����!�E����2���`��Zp��G�����NA���=�J��:����F����P>y�"�3@���S.c��~���[n�u���6?��=m�u���Y�>�VM�HA�ku_-�Q��(��R)�;x��a2�%^&�0/E�
��#�It;�v�����8���	��XB��:G�]�x^�M�Q��gA�P�ni4VW�����y���8�1����T���7���[IK��QF|�t��}�"�
���q=?��/�d��x4��<��~�������-�����w.�b(6�.%��x������WWp���zb���)��o����x4�pw��k�g�lI�:T��q��(������A:�.J��� �mg�]�/����|��)wwK�J�4��No8�O��|��$��?�����/�q�����������qnx/�O����Z�QC��v^�����v,P���7�K"M��!TeN���0B�o���)|}_;mTO���z D�'�_��d`����9�%��_���E��m�7<.��W�s}���>��N�1��/��;����?r=�����Qtl���@W�k�KNI����E���-��(Q��W���3����| 7�����0��Ja���*vi�H�CC,4�A�����EQ�]4N���o��#������S�]>�D��W�I�gI��6�AC�b&J�b\�.[P�%7$+��C�+{�s��@���]���r��j.el#�;�icQ��I�Q�*#Tm��6�k
1ur�n��t��a��B���	r���K\��K\7���U���-4��yi�=wp��������������j;�(�����Z
���!��^�.��.n��\�`���g�����+t����� h�6�l�>��7�9!t��`����S&��:�a�������+)}q|[���HlG�L�[���!g4n1���H��)d
��	t�/�U�����
J�zR��X�`��-Y��a
���Z���M^gNW������b9J�k������I���-�J�E���B��@-�i1���)�"�g8�@i�����KD������H�K��(��F�����MB�I]�z'$��EZ�R�Z��T�����2�"�8���o_=�
8�pp�9�����Q���&���Nj��&���;��������g�S��%Z����j�1[�)��)R�>����`d���W��@�L�ylv��Q�'��Wt���`��)H6�������,^�d/��z�n��[�K'�^���Dtj����/�v�e�Z�~��}����Z���ke�(����&����z�%�8�5-u	�w�s�S��s�T�h�[� �������L�^��$��N�)�q����h_�Q�[�����ba�aA�GK�3�,�*�I-������[r.vbD0�^~�xK���pv([���	.R��.
s7����3��w�%Z�P�Kf�
`�2�M�`s�ea.RS".qU��CY�Y�4�"8�gz�a!�H���m12[�*�oqI+�	��V�sv�I�n�LY���^�h<M�Xt���.t���xJ���iKB:`/��0�xGxX�|����[+�Cw�$:Q(��������p���'���`^�/�,tl�c�_�1V���8��������Q6p�����vq�1Mra�8���������b1B~�,�y�I\�iKR���X��,�"�����g1 ����Jp�^��S������=v.��o
���?�XS�,x�E�M��k�b��B������=;~z������x���H��+L	=H)�����[���$](��I��	��*'�����f	/�?�����o��T:�����V��l�d� �B�U@��\�A#-�;�8J-������ �W��_��l�>,�&���L�RMXF� ��No��Hw����`�N�<0��;����w����Ns������/9��L��I�}�As?{�����>�y0�]iB)r�u������c�tJ$M^��f^�G�Q}a��T���/�t������V�Y�C��$7G����������v{��������t9e��o�fs�O��3�z���t����IDgao�B���������BDu����_���ba��/'_��4���{���l;���c�B2I��'�3�Y���3��C��(`��).���}����g����'���q�xH�FB�<gg�'8��"�R����BG�KFC'2��k����<��M#���Q�w��Rq'�����Bw�������a�R8`��e8(�'����>�l-"��Nv��A��u�YU�5t���B_��-���s4�KiK�����=�,���b��A�/��-[�S�;rz���	��ko�k���fb/U�!y����F�Y��M��-b�f9�=�]>}��������nqE�M������D9����,������Je/������;������)-�8uN�%CE(T�H�D��O�%Gg�G���@�O~�l�/B�0v����9���#���*�4�}:���!��w���}O����+ t���D�Egk
��������Bbk��C�w�7��!0G�Y����2N����A[%�e�y�wlx��gG
R�@:��K�4�����*Uc8��"���R���=���<K\�1�d�D�{3�{`5>A�A@?F_����f��?L<^dDO����p����n�ZM�8APXt'�7���J�����y`o�V�[.�O� AI�"x+oI��z�s�������������|qv~\;G��@;�6��6�H�:=����`�@��/��v{T�����U8�W4�m�
1@Jz�N�%�~� u�$�� �����PV%����b�P6�Rx��������
��;�LHP��CYR��^��zmc�O��,��|Z$C(�$�	�m?��m���zu�2�p[	� �����&Wd B�B��Me�!2nQ��sX!�6�6i��gs%�h�E9����Q� wI	8"TM��P2��n#f��u���.�t�N��!��C#fi��� ���'[�����Q�y:_W�|�R~��/.��s-�<g}�x+H�.i�0QN�l��;��#o{'?\��Q/'gl8��\,9g(�����v)����	u�w�:��
����#��%������xK
��F��-4��z����1;4��#���65����������v\F��"u�P���q> Y���3,��. ���Q����#��^Q>�=���=0���<��g	���"RO��$-i!�����@�P���5u]e�K���g�7cN�f��]>��\?q|)v��vxma�T(zE?M:	��9ac�r�8�Hu����(�)�y�}=9���l���p�X��g������7�4X�m/����pi4!�[�-eR"F�'>��C�a���'f;��/�s���5�G�C��������b#��7��EX�zW?m@CG����9�p�K,'�d�s��.��AL<Pr��0���(��%�jd�3��Ml���^����� �D�����]����@i9���RF"*0��M�J!7t������nlq�t:�@����V�+�$4�/�w*�8���W�e$��z�r���R�}��f�n!<t�����L4)3���q�ZOQ�pn
��Sg"��D�,=��Q���K��E���O�!SZHdi������KDE3��l4����C��NtP}{dm��]H}4����P�I�dfH_*�QTQ8��O��L��or��/�~�B�a��]��WP��CS��E&S�SA��T^�����(roL��x>��J�����CA�e���(	�f��t$D=0��s{d��-��_-���<���JD��W-{��n����`.�8���@N%��������T>$��Y�)wFkLag|o
�cL����a'R%�:��jQ�R��,L���fG�
�T{.�oO^����,1X�9���xE�P�x�(�d]�
�G����C�|��Zb��ZL5].������4/2��!��)W�b:gL��[t9�#RZ�h!$��w���H�
�W���?�&XzB�dYr��r�k#M�2v�x����@6������}�v���Dm���t���,�����;�6������b9_�������=SE#�=���!?K
�2����(�,��h#L��9�3��~f���	��J`c��T�JZ��%F�(c��3R��Z��6���&��6���/8^w�,����K��������z7��9C�g�y��x��,���(K������@���+B#�sX��,a�\:���Je9�T$p�SSNJ�x��J]���C�r@l5.��s��P��4Bv|~���q�WaN�T�F���|a��"Jw����ReGI��<<I�$R�6��!��]�
��������l���~j`�a�'#$������Z��>wQ%+$���X{�����muh�$�'���sVdZi��Q0	�
R_��S�����K�#QN�H��bI�8�MX�����j��GZ33�`�$\�}�����j�2�G2����U!e,�XAG�]&� ��v�I+���_���9s�j�-�Tl��JRrh�L�RJR�{`nh�L������
A'%}9����/�)I�!�t�Z2N��S���Hgr�fF���p�M;�;R�z��	2UxR�j��uV��Q�i2���y����|� �`��������d�R����B����7&��9��]�F'!��$$G	�k	�.��@�K�KH:1f!�`q�nM(�0��-��U���y(1�@����yS���!�P�pP��3f11E5��8���!A�fa��(gQK �q5�N��p�xhjy��H20�<�4�^s;��<�jr"�H�=����Q�3�� )_	�<��l�B�;Y/�;�I�w����T���k(S��������Xe�������F$"s��0��������p���+>`�{#�1_�1�G@�B�����zQ������p��Og�s��	�J��e���{�(�z��BpJ�Lxa����c$k�9!'����u/j� �PG��_�o�D^@(���HRR����k��^>��s�;�7��!���UJ�>�<�
��o����|L�!'�6��#h��<�%EJ+ih������DN���&���i=����(�|$��YIw��]y���Ss�����/LC:�l��i�S��j��d�������P0G+��)���1kr�2�)Q�%h�"}

�����x�+I��c��0@M�tJ���KA���BJ�l���9Lc��*4��t��.^ i�v(�Y�IO9zQI�6ZxY��6��91k4���	�E�u{�5���Y�l���w��,����.��l`?�@:��{�[];�q*���������X�n�r�~�6-?.,�����0�X1zN��2z��|���J�l�������5�\h����$��>������?���1�g� ��'������.j��Ia��2��$�\((�<����#�o���DSo���k�K���bLE2����^��E�]I�������c(qW)�,F:
d�d���4�4)�$��D���hB*2��?����i*a{���E���V���
�!f��Ry����(M�����u���x���eb�]�zz��$]��\�������#3R.������(
�D;���!�$M]���I��>I������ �B�"�Z����W����!�����!���������VoD>QN���3)���u2���C�Y+��+\�$�JB[:��5i%�a��'`�Om;�?�E�s���-����%T��,7�.}}qw�#�Jz��O�/ ���p���X�}�;e��gJ��3���J(��}��,c�)��
�#F�p��'2�Arf�%����~v��~���_���k��>������H��8D9x.��?����
��Cp@G�y}�k4�I[����N2T���!f����L��7�h�a���E�d{~��ds���/���0X$��P�h�5��,@�l����L��<��tT�\h9����}c��w1N����[0��n'r$������������L��
���Bm�0���4&��}���/y����l���=ZyF����7����+�D�	Nh��4�������1�^d��c����M�e���0�HP`"kh*��1���l�W���|;O������#Ff5H��t��m����;�����U>���v���l����w=�
|��D�viM\��5�o��<P-L�1�Dc H��[A��5UK9��p}�sBn�������j~5s�%��8o�.�z-}#�T����Z�V�H�
���Dy�F4�,(�!�7��;Pg�� ��$o
��s%B'�<��0Y������J{F�|��T��Fg��� h
lw����K�#����m�*K��:�KL8����C:��t@���&J�����.��PP���Q��%��Aa����>��;�����#N����p���A�G�x�4y���Q��6A48�l�O���r��y�����;��~m���sc����������	(b�r�����F�8��?�U����{P�5�$K�*[�j���v�)����X�!E�xD�����H	3��C�P��n$p�Q���C��N�X827�Y���]�Y���	S���"���c��7�YN�|�d��(B�|�Fh��e����>N�y4ac)�^o+��!Jq��S�;�k��9�#q[�-�A��+F@Q�	���`��/)�=+)��	�'.@L5�6������6bF�d���5����.SP����F!-;��/�1�}�_�m��I������q�����^CU�_�^��}��'9-!m��(kp�������-%�����Z:�,���>�BJ�S��$�t������m��&d���X�T��h(LF�	ok]�2��~����%"�{2U�p~3��~�j����{���vR3_�joo��R���/���e��=��?�o1��7oJ���B���]�t����BP��:�
_�|����&�����gqo�`~���n����b�P�)V�;���J��N�/���Yc4�������=H/7��w��)��Nk�h�tvv���^��*;�n���i��;������������'
�C�O�`
���C��H�ZWj�r@���{�������4|�~t����bYT�T/��!�W.���A��~1n�������j���(�?/��%+E��:9&u��~}X!�:�B�I�%�,�g���]H���,��N�(	8�-u^d3�qbO`1��'.�F�S�� ���]������ �Hi�}�`��uvT���<_F���,r8�]�����8���0����\�����^V��������ia�8��g��&������|�"��h�+�^o���$8=��c��������^��a��U�F\���������l�R0���nYa��3K��[)-~��:=�ct	x|\[�`����%V�w5�l0"Y���1Wot����o[*^������!R�`]�� 1=�������"e1���b��|�f����>_�5�����
�}G���d���!��� |]��������mc�-@�<~���&$��x^)���;���p0)	�i�
����Iw�O
�����`�&&^Z���|wAT����T��+����i���^�3����T�����mX���������2)%����{\x��S]��K�9Jt-������3�cz��]g��6���2�����n�Z����
����n�3oOk�3pO+�����9������k4#��}�a6����o��\���o�������������/x���������"�A^��q��s�?������=�~�U�cG�Lv�O�7@ ��7XT�������n��u�)�9%y�3��������=p���M����2g���{g�0
�#�� `�]���Ni�����b���X����)��
��2q��Y{��:������q#Z�
�0���`��W�p�(��6������oC7@��2OL��}�7p�&R�&���fq)��gh�yH�����
�r��7yjj��9���|����Mx���!lh=�Uw�R�-@�������^�R.yUk_�6C��,��g>���*�
)�5'B%.F}������5��^e���':A�~
�����o��M^5X4�J���������7������������3��7��JS3��D�,���X[�A �f�*��q�D����`������M����p�����N���S�\	8��k����+��D��74
����eq�-Z,�-K|����M�2����&������|S�6.�k���F�������F�8���H���2)72��:�VF��|����l��|���@qs�?^	���/���������0l�cyH,A!�j_gz����'�y	�;��B�XZ�H��F��8l�����v��������&G��o������)�^G[=�c��*z�IA	8�#y�E��
�33�O�[
'5$t�n��VNAK���	Z&�6bK���o=J�[�R��;(.q�~��oj�Z����oH��x��9H�y��z�G
�X�����,���0��"T�i����2]��f�ys�~J��~M�g��j'���g�zm�~*����M��'�=�.b�<��9+zt�	Md��	pM�����������������75�K�6��E�����A���q2$:i H�`.5j�c��2�yN��k��6�0'c4����}K�
�X>Z��{7Q`-�p%��D�.�����r�����$�a���DUHw����5�Nm���g��(��&d����~�Ni9�7�0hg%pV%\�1���� p�Pv�5�3p�^`��'���D��{zb�c�k
�L���-��g��7�U�������	��-�7�9z����7#!��'l���#�t���q�\�����?i���p0Af�Sd���fH��9�b.y-�����9�N��,1p��H���������hO$@3�A*}?|S���-bM
Yt��h��y�!��7�UYc�("9
�fQ�{�X����e�v��t�UGn�����Y!�L	��3��+���Z����M�!%w-A:���X�R��u2�7|l�x���iU�.�D�;���+
c�SSYR�����u�:�M��=���{{K���E��eC�0�1�>F���{
��������]A��B*;�?n+������g��}�<4MaO�KJ����r���R.�*�%�%[���o����-�,�<,��Yr�������q�t�rm��;����|5~����5����������27p�Z�L���(E�|�)mq����9m���B����p�\�D��)��=�����+<���������%Z���~��O����
��Q%B%����!L��n�j�g�__�m9i�57�8 �U0�S'���Z��1,������H�~e~�Y����hpR��K�`@���|��Z�`j���`�o��~���A��p�2�K!� a���^P��n�;�n@�C�D��A���1�,*�x6��5�a��;=���8���~bv#�f�n�	�$�����G�����j�����n�1�. OM+��~w�`?�/�����n�t0��i���C�������Q90OMm@�u����T��~i�N�f������&��~i�'�FP���8Z��ut1M�������G���"9�9~��_V���!x�����������r�0B ���/����MD�����F���
|����i���3��� a�1�`�r&|�1a����� �@����J9x������1�ZD$�0�!���V����r]B~D�Z��D2��5������	�
����7�y�Z�J/���D/��	���x��B��w�a?P���S�����Y�#�}:�W��Eg��^T1X�5FU���r���4�p�z�&6�o����[�A#I	���@&�{Y{FlU����R[�Z9���������O��F/�c@����I{1�jJ��t�w��jnS��c�H��Yf[FS�-�>����->���w|	��,��Ij�4`���$��`7�(�����<f�A&<���s����Kt@�|P�M����J�t�o�\�����9�M��#K�xF�i�������_�p��U���a�
� ��qvmp�x��Ok��|b�&�3oy��M�:Pni	X�9$�;�F$[OX
�C�B�'����C��F�! �8�~���?�P��U����MB���zS�*q����6	U8$!ar���W�6$��.��,#������N}E]XT��x�=��B���Y�6s����M�a���y�r���D�{�r�������� D�D�����u�
��$�ks���T������*{!/����|d���X�����

�����)��<����S}�/($���	%P�2KDf�2�����Fx���`�G=��8�����J{�����^T l4}����T'C�W�I�s�b����?��]��6�D����G@��f�]�f�?��>��R�L�\;!�}x6�K��	�3��`j�������i��V*j��z�P
��#���!|�!s��1�����3���m�}�W�
��ua>�
�I>aPEzVE�B+���dg������?�t������%0��0��v(��QW�xt����\������������&/�;$����A��T���Bt���qQZ�JH,���
SKTyt����2�6�\�5�g�����tmb;�(/��q���,N��v�x�]��C���\D�b;8z4�$;�+�����~=�%c�����������Z��`9�1�s���h�*6*cc��������\�a�>�Y���P�$O�s][c��PI@��g1)��rH����)0�J�g:pg�@�<�r��se���v*�\�0?����N4��
������y��[��v�8�5j�����*6@f`��V}�����_�^a
X
(�H�<���m��|��?i#!BD���p������v�h����s"�6JrDo@�x���D�h�J�O�L1�j�R�S����},T��IB��\����GC��� �IBTQ�9����hg�H��[.�a��E�O�����(��UC�Y{H�4h	�����C�;�*������p�w�$�;��N�T���1�NX�G!`����\R��\G?/�	�+N��3�F��[�����#'��h����,��X��x�d����T�*�$()X1����$�Js��R�b����5����q'>����v�1CJ��%��@_2F�F�2�5�zdx��E�
�?���0-jZ�H���[H�0r�����R���`�P!�b��x��;R�`���A����)������`���QD9CS���#�+�<�i�oq$�DI����@���3���v�/r���^�H��#aX�DsQ�m)E���	�oAc�_1T�Q����X�i�\��A3�L���������(�������s��IU��h��B�n�'�Q'��E��7.j'�����qR�/6���u}xHB�Q��!C3�������AV`����q�5�:�eg���,T�����C?m��j �|	��P
/�WD@��MQ@��������bV�t�!K�p` &��B��#��,���� ��q��i]E��DldQ�O)�!$���l	�V��-�����U��lV�����&}��������-�j*4�59F\
��%"���E���\<����xF~���K�����T���~�o"C%/�k����3>�k��_KRc�%���P���������
;���d��f��<��LQ��Qo��:v�#���k�aLf����P:~��i�'���f}pt�Cz#�J�(?a&�rtg�yH���:_sx�;�U����O����M��t:���a������!a7aKH�)(GvB������#v
;F�!�E�yB�l4���W�<�	��A�Qf����rG�%�����~r=�c�,���|�7]���]��B��J�b`k��J�_�n���Q��L�N��ee�����}�>)T�4K�R�������&�H�]|�|yI����t����kZ{���E&��%����I�K��i�����y��w8��5�,5��
���9��q�D8�C��3#��B1(������Z�5G���U��D�!2+�������n7���r;�a��������Z]�d:�����v�i��H�<��������]�L�o�_�q�8���z��B�}��A��56��5I:?L"A8D62M�z�� �Y�+���v�����3V�d�4��n���{7w����}��d��n(!���j��X����iTy����z�2�x���|�ft$��<w.^	�#d��D��$K��34���������xG5">q��<?v�����38�Q��F:4@	~\������-��x��f���K��#z�)�$i�~#}�6��e`�F2����n�aZ��	�fD��X����zT4C���$�&�k���
�z�_	s%����Wg�<������I��[)��\Y�qhU����C�@! B��|��] hr�h%�L������%�d����~�����CUW��������XJ�J��9/+��8SC��H�oe�U��z�DkN�e�4Hz��f/��m�<,���P���v�'��<��.�����TS��DC���2�!od��	�����2����2��*wl$F�r+)��S�DCE�_2��>jnA����p���N�B���0_6"��m(�(TiT2-���� j���/�rq~k�{>�D��J�<ejk��NNB2Br<_�nld"��X��Q���a�Lq�`U!�U3�g�%z��9�j��Q�?
*j�{~g		F����"��!�A;
��P��~����b��^�����q�B�������s"E�������2,ckS�'�C8�VuI|���Lhas���N���������PIrZ��X�D�_�R�\�l)��X����d��	vA��b�CS!�c�����~�&�#��d�5�%y+�!.r����;��Y�I�_�A����(`��F{�:��TV��*���._�E�b��3Sl-����J������Z�?�;��0�����/�s o�����u�f=X%Z��Ck'��7�S��h|�i�T|(��)� `R�xR#w�c0k%F���#P�)���T%B��r�r����I���p�����.;f���������*�'�]�~�� ,bt=�sL����+#"��
����j���:d�\I�@���M��9�}�:�?���CO��{Nt�_cR�����o-N��0AgSlI��*�E�<ei���h��
�g|�G,�)v*^D�*�D���UV!��*� cX�O����zr����'��TA}���g��a�i�'��,�Y���
�XK6���{�����x����*�P��p1����J,E*���=�_�J@����(J]/�'�7�C)���YK��zZa5�PC����fh����@�y�t�������X����`����^��V�1��a��`S��P���$&�����{�]�m�n�W	����z������a]�]G���J�8�����������@���<�t��t�H<�
��{�xE�����cd����Gs�e#�� ���@7xA��R�YJ���$@PR#A�^����)H���,�,�'�0�=��I���i�Y�x���;*���b��2�N��#wp��������6�2�0 �t(���$��.7�V4��a'���EC�bH9��)���N��,w����ey�	j�Uz����,����4���2��JkU�m��0m6���3�^�����\��&l*��+k��z=�e��4�X���W�"�o;��
��������MI���	c�|�W$���+�M�?�/R�%�Q-�ZL2�P��9�&n4��Jr)jH�T�� �J��`.MJ������R5z�Q[X$"�`oe�����aH��FA�2r0Hu�`'�p4`�+����$������i72Co����p��9�Ss���Qp���C+*�a=l���ij��2��Q���Di���������vs�r���]5��?������7�1�t��`0#������\C}�0��=&b�j-��i6���+2����S�H�z!�������pr%Y<Ig4^�*��R!�u��G�k��>���V$��FB�cLn����D�!�mn����\��2Pg�%��j��������O��cPBZ�*�����G�.��q������T]��C|�]�_o�V=t����@
v��q�A��;����:���-�"����u�<�3.9B��|��d����<o�z�Q��������O�$�;����(�[�xD�p�Ot]�����Y���I��s�/N�T-'��N��hw�y�i��tF�p�wpK�����`�F�
O#��Cg��|@)�"�K+Q�JD�����)`K�Y��d�,1|@d2�ab�$�.�Zl��>��
�J��Q����n�M����<a�Vix�c�s�8����|~�MI�b�q��O��t:d,gti�.��������	�Q��;z��tBP1 +`�o@�:�PQ�Hb�d��Z�z��n��(\��T���������If��k6��ok�|��&U���k�l��F6+S�����jMh��~To4����m���l������z��_���M&��$�k�!���U��:�Q�**&"��h\2Y����I�_F<o���#L/��CK��&9c�������\;�A&�����U�Ir���J�a�}V����V�S@�n��={�G_|�X��1��DR��>�1��z��{�����e���:=%���h������'9��U���|���|i�X�bda��m�R��`���G���l�M:iJA>������k�d�EF�!o�l<����$�2�_�����0�����1�S�����{��kC�����zy�����I�
:��J�-,�\;����F�nX��#�xAE�k*����yr�k�_'��S2(�V���w���r���Q�4�0G>c�H"�ds����Gh^��O���j[8�����S���K�pqx���4��go�{���x��Q���*�A�|��M�u�	�V��1��O�s��c���WDR�d�!R��&~`�k�~D�"�����}qv��p�JIjVn��w����u�\F�2'|x�i@���]����jF�_�i��L����NRcL�EC�c��s5`��v{�����1�@0$#0A��4��
�'N@�0mdPXG2�
����M3����~=��7��%�]5]�������K�.z���"N�������(�H).T���(����<�����juS���[��q;������nO�9���
y�%��;�$�A�KI�sZ���=T��r����[��X|���P���}��y��8�{Y��>BVg���?�H��#���]�����
��=vT���0�� ���P���I"�B9z�`�������y
�'7Y����"��b����I�Z6&�89�u�����"�3@�
N�_H�E�#�Q:���U:V*y��g���eN�f4��@�O|��'n��6��v�d[�,�������Q��V��r�#g�mJ9����lE]lb+�RR)���"h���E�����I%h�v����r��+�H-�*�����W��e��SY����*��
���w����S�[�B[�[��f�����Lq����U�Wy=�gJ�(in��0��S��BY���KBf�P����8|�%4�O~#3�EJfUP����Wv12�\��� �e,>���PN4\Z�Y�S's"��@H�&�`al:p&��+�48��au9���dH�������U��_�M
�:����c�%�*�E��O��*���\iwG�}/*���.�^4�|�f�F��3����L�M�(6�{�-��u�w�����6��	�G������=S�&�6=�q,�����Xb2l���"iY�zE@��}���m������#��e����(�zm$�8Uo���l�r����f���v.�@���j�S�����!�E���m�(Hz\��V���U������������ic#��<3�l�O�>6�k��Qr�I$w��V)!��	��:�!�TA@�D�������Sg��K�o�A�	g���S�V�6�R:c+X4��7���U���. �G$?)��/G3�)%�)�e)6�9�W#�V��H��tt"p�[��M��"�����k���
Fo���cS`oE[�vA�}���ecX�����i�8"����:u�1�*/�	�����9T�th2=��g�A����#V>N|I<��u?*.�u��#��t���n:�'*X�I>w��#	�G��$�Q�_S43��]����9t��-o�{o����<��f2�Z.��������@�������)��{����	R|�u��.?����O�"�W�h���HRN��E�"�O��x#��i�<A�@x�Q����r�p�C���E�B��\K�%/N����7��	�#�=�|W6�^��C�%xK�K4!/��_`�{9��K��Rdt&TA�V�c�j�N���x����[.��H���p��8�~=�}��J��E��9�:��	fpx�g
�(�M��B��%H-"_���*!�U�+�"S������o�[��H���~E
Yc#0�j�����7I���$K��Z��Pk�6'���n�����L�c�������k_�m�����������~������}�I^�:�D��C�\s���z��|L���>hv����a_��S��;��`V^�EME��j_���{�$DY�S����2�xqj�=u�������3���Yv2����������4��b�yt�Z�E2�\`�n�2�q�����b�<�9�0w������R���z���lb�2zw�����<7-H��-u�h��{��f&,L��^k4�-X��J��C�Us����m��+cg���{k��V��V��v�Y����r:�f��9@@�t��ZIJO����l	Z����Z�}�#@�A��e��e8�B��w*����[X����q����*��L��� ���!�[��Nu���oR��U�/</X^��E|O��.U���*X��5j��c!���������x�Z�u���nI/�|o���ai��^Z>��5��V�n��S��S^S���u
+.��.�*]��3j��Ts���N`k�
up��4p�~2[fV�H��s|�f�t)��+����/��7���Lp9��\fs(R�lLj�����1�'M�a8��Qci6�\�|&�.�oM��i9.-��^���,V�p�w�cA�\6�0��$�o�����R�O�MRY���k��e8L�^":�0�k���;��b�IhV4p:z�R���TA�����PV9���#g���r�P;��
��<|67���V�p�����o�����8�v��R����ooo�oV{����;�1[f4�|�k��O7E��E�.�4�}p��?+��������������U����+{����m����S����0��R���]&���z���C���:x�qxqtT�z��h��"�l���G���������~w7Ro�Fn�������jVs�y�~�9Rg�%r���?��(I��n�����U`����=�sgoW!j��<y�����Dmlmo��f���T��3y�������S���+�J��J�q_]��q;�6'���j��JR�q�0)�)�d8���U��IlNt-P�[������k�8?��_c�H���sd��La���M�3�V�#C��*U�8�`�����9I2�(bNk��P:�QY���j4k���(A�5t���&�a��d�W�vK���M�z9��M�DL�
���W)�
�	���f�gs�Ho�V�!%�4�	)���S��E|[T����@7�E������4������:�\swDk�[CR:c����Z]��5"�<���!U�'�b�����Fq2KeUIq�A7����W^�r�u�;�ywBy���F�E@ddo����^3ne��;&�n����o�cE�p����3�Aqn]MK��)T��'�(�V�C��#��5�p'{���^<�Voh����6&e`h=��@�Z�Mo�g��F�6T���6*�*��a��2~�m��c�U�y���	&��x�l�����I��E��v�������iD��?�A��*���S�j��
M]#��c��(��T�������
�#g��uO^���m~E���Tv��d�m�C����:�~�a`����zdR��M�W}^�d6���[f��LO(�z�?:���x;e&E������ ����.P4��^�x|�A�zdrm_�������qek����|���([�g��I�z�w�ve�'V}����YjZ�X/��#���6�WWI<���LU�x���X\g�#{��*{{�G;���V��0zr�=�>P�,���(.��U��K}�W)�'�V��e������hv�u�*E�]��R�LQz;�b�F���a�]{�*�]�)Fqk�z��XD����4L�y������K�WM	����]�RQ�,����]�:�5~�z��}�KN9H�n<�|H&?�y��iH�WpK�H��OA�T�6�f��Ar�F����c�0.L~	X\;U"�"v�J���h�����/��>&���v38`,{9���I���}7&���Y��T��N#Tn�����	�J�Oh������
����}3��?TO1mv2��b2��<&}�Z��4���u}4�_�'1t�\,�!�����<�����)���w���t�Oa������S���#�S��d��[:��.�%�>��Z�=~RNC-(_2��W���`t|�4-����
�����i��0Vp���&/��{����XoT-E�Bg64����)t�z�v���L��x:���X��
��o������kfB���W��i�e&���Q������p��4����+�������l/3��F��8��\s������M��{��f'{Jg��0}��������}h(qv��p[7�^e�����������h,��u���*$��QL�1�
)�!%6<���%'t���?n�9�Q&��n�G���O�t��d��V�Ku��I�f�^�aTZ��l;In����\�����%�H�8��	���+4��^�x��-��+	l�����P������k����JiK��M�L��1^�����;��F/�$�`v�O�gc?Y"�����R�:�i���i<EC���M�EeGr��*��}��d��:��1��?��B��_�|����
h���1=x�T��T�4\��������~g�Ls��7�W9��97��[1��\�o����~O�����>4������H��p���tH�i��]�G����^�A��b<��wO+�-�qU���W���m���Tp*t�c.�e��s:O���d��
�:���H���UkT��}^_�yx�C�����{���4�ASN:��������3xrJ�&���v��	C
��:%�yu�g�j��K^�81���+`
46����O&]��O�'���Z���Y��5������F�];1>"�c}�^����6/�$�2*+t��=F$�P�]��}�
����|)����kW_��8��;i��l&������<�r��.{�L��Y����������A�9��5����c�C1�������M��{O����a
�T�x��O�����iS�V,*g����������Z?#1�t���W���u��1'�7�FOS&�0��T��H�El9�v�3_O;��I��~)@�8��X�%^&�z��Q�{�;�
�!_<k��&�P?��T2�i����V<}�������osa��7�]V����W]�
�(������@�^�n�y�nC���,����O���Ko�����e��l�7��-�xb�oJ�L���){�=�4=2
����b?�&	..�	�$MkQr�N���2�4��G<���8I��E�[�s,���o�%u��1���O�x�8����!*�	0�8MdY��1z���S��LnPy���>43�����8��c)��&��l���d��%�[�l&�;@]���9�b��S��������)g��<RB1�uY\�������<!���i�A#��nx�2xS|�E��v7_h�>�Z�����6C���ahS<�^�|
��\�0�1fP�w�
��!%0������	4pum�B����W�<5���d����#f}����J\&.���,T������N
�`�\\p�%��{_-�������*g���C>��!=`q�<������
=`�����#]@�,�_�H�
.DI{���G����i������@$3��������>�wi�9����.�,9���������KD��{�E��'��D�#D�/���Xx��������h���T���p2�@��u��|�5�	s[���F���D|5�Md��K�i����7<D�f���Z�����&q!����R�l,�0�~�����T�J��� ���0��np"L���#/E0cz;=����S.���[�YN��P:��G ���]�<��,��O>�$�J���sE�` �
��%c@�&w�6&���,��e�_��u��B��0�D���z�������#P$����7kp���T�ep
v���j�����_!�*|��`�u���0��p���	����m���J�,���GA�Y���w�	���IA&Tz���@�2�w� ^�N��	$2�N���\�'����4����U$�'�Nt=�N��2=�����*l��e'<=R��?����DgYUc3���G��-�C�9���HB'\U~_g��C�4P�����y2�O�/b�X�4Tk��.ov��s#9\SO�E�O�! �|�������������_������W�3,*����4C:��]oV�kt<�+�;R���F�
�:��b�&7��{��~��F�����G��B�*.��B:�f��Y���C�!�C�h��#D&��WPV#�?����s�{s`S?��$|��%�Q����=�"GnT�P1�s��I�u8�+:^Zk���uK��,=�S��� W����,H��5I��U�!<�D�"\��\-�V�OM�f������:Q���$�K��B5_�u
�k���l
����69������&�|�^���8���1����d�:�����E�����]�Nc?uo���"i5�n�$+�������O�[��[�,q�M7�d�a��cL8fg�cr�������?-w7�I�(F^��9�o��u�����Z������2*,�_�Yj�q������=X����2��JAt��)�!�q�������V���U��X���a#�����!zr�k������n���c��sV�kA���������2�����9��	g���`��R���K'�����^�(���?��I��^��5'd7�����S��/�����W_����z�q���V��5�{��h�}����������Q/�@�_m�6��IHs��!���V��W&����w�Hb`�����8=}wN�p����it��Cy�icL	�p&��yzwVo�=���8����~<�������d���A���_2��xwo����c$��9b���M��U���(H�����7�f����ac��cJ&nr�����q�0Qa�R)�4��w��-�I4�����(������`����o�O�{n�a��`��Sr��f3t��C������������gIG������tE��Z���^�����jo�m��5'�zg>Uk�������(zx��/hW%�����Z����p������4�&t=�9�4IY����
�H�m�!�i1���J5T�.������k����S��z.�g��]2	Z'}�����8\��f��-���~�Y~'��'G��f<O���=\kl.c�s~����P���]��n�-��V����_���i���2|�3[9�}G�p���g���Gh��s�X���{Jt�������d�JnG,������>���U2�F�J�\Ax���25F�3;x�u
��������z=��;�`�^&�0���C�ua|��B��?K��:���[a�nf[��S��g�i)���Os	��l8��i�
�$�Q�c�c���,U��y3��hp	�����O`v����������C�k��b��G��PE�+��~72��N
n����/��ojgF��
�����������@�Ih��s:zZtg�m�5uM�^Z��r��M�`f���k����>g��f����O�G�>�^^N������}P��>��-�)�m6���_N�#t����e�m�}p���x����)���d��H?9.��B���ap�u�������A���(����+B�&}��sb��Z�qz��f�!T'��M��zYx�����	���Yg��9�������������brXv���Q�tU���N�>�`���R*��������Zd�C���,����?��������$��[�/�j�z�Q�+�N<���������k�0��KRa��7N����u��n����-���y������B���_��z{��`.)49sq��Y�-23K1���7���1��a��
8�/�W:�a��5/I�t-�|R{U}w�F��������ED��s��60�p@6��G���3������=�������^�A�4�
�Q'�������������}����G��Xh-���{gST�4n(����y���0q�\����/�@%���#���I�B[� �~u�j�B�k0}9�M"Q�-��C�8$-R��{��U*_�9��/��4�4���d) �u�%�+G�����5���>��LT�w���[�0���
V@tP�\Y�&cr�S� \<�1>�s����O�)���T�v������+���=����;�7�~�z�:H=�������N�/8<�C���w�mN(�|���~���`U�}�����";|���^��Ui�q9^M�n���fo�h\�?�\g��-������2����������[p�����'p�3�Y����\����_������i�����p
�1;�D#���$p��E�U�lL$Q�q�?�b�N��"Yi��Y�]?�����&X�d������C������T4�&/�E�������%3P��`&r�y�N���:�F]:>���Q>��dx�A���u5������P�����LJh����S'�3g��b�e�5eu��x>���$�$2�$�5;��v��xo�%���k���E���=��g��
�S[��*������`Y>��4�����A��*e�B>�����|[=��������I������KF��|H��eZk��B��e-�Z4���C�������DsA;H�16~�[
�us�F�����m���Z��/;�:�U��@�Pfw����p��0��d9-�I���A� �:�S}R��f�}��6VV���h2�I>��:��#�������[O3��U\�Xv����������V���%��A�h�����B���P��>f '�����'����6�������w��&�r
� �4������M�J��)�1'���p�dq�T����.�.\js�)��Q�0�oHw'v|9;�����o�{2A�b����7�o����f��rNFy^:(a�� Z�V�Duh�yQ&�0�!Jp���(8�Z�F�e��s�3K-��q
�[�����yU���t�dJ�*��3'����8	Y(�g�O>2����w�/��b>�O���3%�cu���I�a���M�?�e(��m�K��%���p�C�A���)0'��K����w=u������\����1�\<G@krsk�ry�gq�.6�/'}z����*�>J�\��c���h$�<9�OA��KK�`���n4��r\��*���J���$�rv�����%1���-��$�di�����Q(x���Y��!h������lk[���~�����muA�k�y��!�.�9��fZG�IS
(>�g=D,2�!���d�[�k���(w���n��T��v������J��w�_��mj��[�a	�� �]��t_�_�����xS�v���`��%�~v&��Qr�&w�`���� �D-�"9!�d����r����@��N��C)��~
[�j���P���a��P7�����3��*�����a�`�9/E�;�{�z�F7�1Wli@��?dF������-��ll87>d��T3;�����u��g���+/�{�PgN��Uo���5�Wu��ue�*��Y�%5���������w�,�pO:
��3��u��8^}rq2v��
�
��/�]�'�d������&��&'����U�G�s]��=S*�o ������W��%�������#s�77�����������T��J���l�(�;��pD�-���@f!�u���c�H!a.��l���k�T�k�r�w��';[[{����Eo����9���D���D��_�R�:�l����]���!f��*�����P*��y0�,)�m������w���}�M,J,�Y�xh#P4%���6Z2�G ;G��(;GA��|
�h
o�w^��e�{��X��������dj��^*CHQ����nYD�������"�*���]�|d�O>�����?�/s����MN�KO�dw��+��)7I6������g�����=}��(��oN��O���aDLu���=y>����N�������j�M��?����cz�*���&J�H{b��ot-�N���p�r�H���r�TQF�9 �4t������������Q5������l�P�4Y�#3�R�E�������/|�y���������{��i�T��{@�d���&�L���=f�Y�JC�h�Ft�H	�0��3��S�!��o��U��M��YI�Zj�o�U*Za������C�E���@��o~
m�g�=���wl���F�������nHR��6���)_��Q@���Q����g����,v��Sc�3Il��a*�o�6�v
S�6��$+����r�����{7�����W1}��~_�:z��,�f�����"�u����%��+L��-L�`��P�bRFx���6�X����/������X�7#�q�y"D���K����~@��v����1�>0kTq�Vk��d����j&u(M	6^�5Z�y������a�H��
�P��Rt)�T�xE8��ts��+I�tZ���4�����ky����%��`�{�a0��J��� A����y�Lkm]�\��`��yF�N��e��V����f0IF������u$����cR�[��Kzh\F9
���%��3*q>f����l�I���y���������M���%�L>,W<�<4fj��\(6K[eo��w���l'��h�I?D�`�(��P1R��C7`K�@xV��oig��d���u�F]�N����Ndb}��4���4��1f�X�5M�d{�
SoG��dV��(�9��b�ObN���<!�Xj�F���h�G�h��������C�v�e}8C����P���_8_Oqo�������	���&�?�hv� �2��4��|���9>���sL��9v��(S��T�����]��x�Bs��s�3�7{Y���u�~�7��#�����m_NJ
��������,9�y�m�g�=���qh.��P��w��;_6K�$��9��
������
�d�<�;x���������,A���-����� 52|bv��:<�fD�g�a��,oaN~"�Yj��2��Gd�|�T��L��Q���.�D��`z�=����u���5��u3�+�S����=�sw�#��;�p/�6���?-wiCT���Qh'e\����G���QJNGh#^��L_���0���6A7��?�������$��h0EAXp�3�I�j���15y3�wH/S
H�H��f�
���Cl�T�x�r+N�q`w���r��v;O�3�����A�B������uE�-c���r�a;�5���U/�X>�>^Y�|vQ@n�RT%�%3�u�<k�kO�q

@�t�
N&e��=�1���s7[/CQ 	��Q�BW[_�Focy\"�+DX��H��-/D���w|Gw����T�i�8hm)u<�ua��e3��������I<K��"+�o�&�w�����d���1���3>��94q
_F�.�AR�rS��A���i���0��_V��C
�$������	��Aj������d|[)����0�9��\T��+�?���Y�z�M�%�&[L=�v#����
������~?�~5������{�[W�k�V�����?���)��[�_>f;u�7A�������/�R������H����d�����Nh-��W��h�����������R��I���#V�j�eX��b�6��b��QL�!iS�f4k�������t��	A�	��'�����o�����-����G���+����4X�������am��k���Bl���,��&�2=D}�_8]��������d�b{e�3#o����0Zq���
��%&}2���*�ls��,�'1��k��@����$B�	a�8��c��C�����!�Ce�~��Q,a��)��v�D��#t;����������r��Z&E�;_��Q���i��@��RQ:C�������6�vA4=]U�i��\���L���+{�<�9���Og���"��R�b�7���5�����O���z�
�
�%���F#tT���iL�������H7)����y3"�2�
6$���!���-u>I`�a��|������b���gG��b"���h�����������HC�����grse%[�`,�5��T�AU���s���T�F!�k�R�{�u��k�~k��r�`P�^��{/�A���[E���g+��J�����e ��@'�����s�S1[S3"L�t�at��="�n!��v+�]���G���O���Y����3Y�[��?~���"\�wR<H�pJ���^�0�c�g�f����E:ux����UOT��|��$�&�
��\��9�f)"����u�Q���`�����������|�Px����,rr�i����������ri�����:��&�s']�����s�jK�1nK���@}��n�7K@�tp����w��@��	�H���^��..���t��cj�T�=�Cj0v�O�$�����]�[U�!���.3��5kp(�03S����a�������@�x�W���3{������������n����T�Fd����r����o�PX7pC]��K�,/�Oe��^<n�,�����s|K��s�K�+�1i2q"��'�������h
��=c���Q��H��#Y��y�����}�����
���W�P��	�>21����)	��5+�g�	�Zb)#�LT���V����.r�L��{r��"���a����k	�
�x��6K��Z�^�k����'��V�B����_������-Olb�p	���M����`�K<04,�`�@Sa.���DH�����8F��.�.��+��c���%������.q���Cg�����A���tB���2:/�L��`bc��,�5�uY��;���uz�C4��fo����*�0{[��8E�(���f�G�c�CQz��wzr�ef�Qq��I�-Dy����t@f	��i�k���8�4k0���vY���������8�P���Is��G��	��VP��?��!}�����d�'q�����n���!'X����jKa�1�h�D�B�IkHE��e��x��C4�
I/���x_�-9`o��J}G������E��2���?D����,1�f2�t�@���2�*���y�09��f�8�Z�b��AO�"������Z%���{��������Y���	�-A����j�]\Vf���������:�n�8��<?y��D:�y��ML����C�~'^�cZL����7z����i����W�������%>O����83����.
�z7�H����3I���a3��QF(���d6��#�MX�	���/�E��4UBn�;��"��*���Q4N��ii�$<��O3�K<����_��C�����yG��3�$�����8��$�4&FV<
�%(j�{�����m���������}���E�t��5_�|����cjp4))�}G�Kv3���	Z�J�7`���gr��L�X��7�@xK���D�tam��Jp��vw1�x�R�/WvH�^�s�*x�n����e�
;�Rb&=yN3�6��^���JSlP�]��YI��Y���M�9C���QF��A���u��jT7��.j��������M�_�F
��/�F�[/�j�;�lI����i���/��GZ�}�X�}��Qd��w��>���Z���jM�LA�X�U�Hl��������������V`"��Q�?��*J����h
�NAi��$T��q�D[��!3�U���!�D�`3)�����NF���O���	f�U�����;�41�����_w^�;;&���|���B�x��)$����u��(���!�K��w����!��/�$����BHK�.���o��giS�
�:�/6�3�CM�<{�l�#�$��J����8K����^4x��l�qP}�����xh��<���:o�v:I���<kx&1�Z�A2�]I�F��DT��27�?"L��V�-{S{�6=�4�����
UA�
�[O`�������W�F�:?�`��)a�>q�����%r�x�����l1�������il���:���X_#�����0�y`8�h��6/3�f��V�|���D7�W*ba��VI4J�al���:a���?>��6���������U�U�2��&|���q�#���I���M�]�����(��WQ���4.�����H4���X�������N�����0���-��f�T�<2���p9���N�G�#��p�8�3�pT�W���v�#!�\`����y��rt���0���t�3z'l��NZ����ut}����q#'.��j�8�[T���S/u���2����yg`� L�\n�e����T��L�nvD4H���<p�{���wj��.�tCY2���e�U�I���3��eo�#���7�	�>�*��<W-�l/���6j�J R��"���T���g�?R���c��^dOi�����z��l��{#���\�K����-��v�>�M a�Y���x��������������}=�����\?�,�M���m�0����J����6�������E:'�+����Om�5��������#+Y�/cWXS
���%.T�^�z�n�,�r�� B����
e����#Q��&:uM��t���tc!����ZY�����7�i���zN���&���0Yy_'��c�=:ivy�L�<��3�lT�N���@��<i�t����(:�;�Ak�v,*�q�������JP���r�d]I�
G���4k�����z��6���g�����_�����]-�J��@��-��W��.w :gq%@���@.�q��e�Ug���� M����Y��0��&r$��l�]�.��w%>d����1sp���S�z��BP�K���}��0�������0D|��=��>���vRj'c�96���1F���vI��ej+��2w�.�D�:<�T���*h�����3���o�����e�9z����s�c]�<�����z���|)Y�hbw��/]����|���]�9��g#\���
3�f/3}������p�{,3�l4�
w(�p�}��&��/�����u��nV�k����v���u�	��Y;����S?k��mI�"zx]�i�k���\s��T[�9����w�'������9�X+��ld�wkY�����#���	0.�A�Ga��43x{��M����+�f3�����9z���9l�xr�J�#��`��!�����=
LE���B��sUpW����w�+~�@*\�`��@����h��rF���N��#X~Y���r������0kA_YV��
,#������F��r�[�,��E�����h�������8�cnK�h�*fm��z�lRu�v�H��kr=��p^������L�_���pr�g\p��)�N��Hp�Q��L�}`�w��v�X�(���8c�'����B�;������9�GF��,��Q������`������e��o&MITHyj&��u~/\|��am�d6�)�Rn_�R������7�9���#H����Dt0����`��q~�5��aTcJ	��d���.�����y�zb�����������S:����^+���]z�(	@p	 Sm��zub������![��V��]����8�-�8�5�R����da�0�g����GdT�s0�!�R4���#n2��H
�j�By��$5���c6K�&g(�r����������$m�v�*ER����fn�Y_��s�-���H�ej�I����k����$%�@���r���lS�E�����b/S;#���9�K��65�Y	�i�-��m�{�m�����L����2�6(PNo]�'G/���x'�"P�Q����T�?�t��T�?8��|�K~>���g��-�h"yD���<����@f�<rks����r����[�[�{�< j������^#����`��>�b�?��_g1��� ����K�I7�z.7U��j#�,�D6L�1��B61��HV��;�wq47�j��&S��Wv���P��9��������0������2�Z~��J�L_k����ZB:F<���R��1���8Y�����Xe�u��X|<1�Y <��Z������>�@�8�e�����D�,��}��������J���9�������u���I��:����c�)��@K�\�;W��ZUt��y�Oc���3����VtRi�q�}�&3�b����d�o`�Z�������m�b��=���1�}{�z��neBd;�����zf^�����L-W!.������P�(�Jp��E0i�`l�t�t�T'$���������k2?|�(�~����c��(���E|�&@y�ubI��L�n@|�Q���k+��:fu�D_��t�
|���E+�q���������Br����r�N�df�-��2��p���%�acQ�f��5j�)%�/�-��O���e���JJ"�Ku�-��l=x?�����	��Z_\j� ��j{�����[�a��8��#����/������B�a�,�L@������S��l�!�+��KT�y\e=32�>��M�ug���"�
AU�w6Hr�����e6��@����R�;3W:zdN.�/MQb\C���c�auq���d�^���.�bo��Bk"��\������4���Q97�������
'#��W�w������5s�
�^���:�3$;���v���c��.:�0`~�X�LQ��OF@:g��2[w53�]k
�r�|C!u<
(g`|wl��I������$�7��8�pN��
4�	��2��R�j�,���2sO�
�beV�����$o�2n� 8�l�[����/���r�&�3�p��mLfrE��|
�������v�kS~O=g����p��d$��;����D�����}ACm������o��\�0@ujc��$���`�$��
{qh'�p}
��i]�t���6�#����������-xC����d���9e��4C��q����"Fyy;u�d0��L�"����Z��h��u9%��C�_���n�7�����K��0+��\2A��F����7�^��/�N;(��ON��m,�3�;�e�YY��T��<
������9fQ�i���!��e�{Bc�6ZD����@���C�VxN�����&����sSd"}&S=��CP�k�SL�6Y�(+������f<B�[���~la�������[]��xl�#N���p�Zxj�Y�Q�I�����L��	6&�#�3��2���E){�:��Sr���U^�:^�X���O����_@1�t�����L"��G�J=�����n�*i5}��7rw���8P�iQ�!���`�dUz�f�g~���qO���
�H���E�=�r��V�A~��R�DC��P���D��[�dVf����k�R����#�
��'��U�Mr?tc�����
��:SB�^���@��[�}��kT"0���2.M�>^F�jS�_���>E������1�v{]5�������~��g'�t��o���K�����Z�]7rH|�������*nl�"<p�����g�zz����k�i(3;���0�"'�p}`	��G���|�&%����5�?t���V�sR{U}w������
"�`K���v���'��k��LQ������a'q�l"I������E'*��hTYP	o5�9�0g��fG����t��dx~)�5��/e�/w�H���,a_���&C��zF�x�$M�3����)W�Lv�(���N��e����G��B���m���E�%\]�W\LI��ImG3\�����Gm��"`�10�]L_���	�{8Y�����������.6�{��������&������&
�����rrq.	1r�Ie�7����6�K�u��w�}�sx���E���D��T4]�>�:S���d:����{t3����uJ_�F�c���t���F�W��`��g�T���'<X�O����0��������%8T��$z*�:=>�.� ���|�Q&�+.L���1&n[��?LU2��U����!X���(�b`V����7�<����I	�V7�(�!��T�b��+Q�j�zZ;��e����kc4��m�/Q,�6����}��~��6JX��13�Km��;���H6�avm����M(�\��k�@x@Pp�f?\gd��q��
����_9�<���<u8���>F�r
X�M�������D�F������-�,�X�#R3wb3G��������Z��K�\�����,�|��Yy��c^�1���9]����s�.�������w�q��h��
����A���
���EP�I�V	1��k�G�����x�����o�e�TJ�_&���3�����szY7j��Z��i����WQ
�Y��m��./)���I��",]N���]1	7Z���^^��?P�!�'�;����;6���8��D��~#�.�*8��1%,1��q9�������#����F�hh�Iq��5-�jp=��?����`'D�T��r���S��n%�Z^}����������;�o�b��:8O�`�(��&��1���2l{F��)^-����������L1t�J]q�t
�	�y���F��{�/c\��?E�t|n2��c"������-���5����q8}b�s����R�z�/��d��2��|������
j&rd<�M�6�|�D�OM$����h�Gb���4�>��i�R;��C3~�Kx�P�� ]�@�WT�R��~��Y���XXv�
U����7#��v�5���� �*����k��$��L2�*~�S����z{,�0\x���0��!���u6~��P�.�Z�B�n�m���CV�Y��Xq���WM(�{�Mst����m�����=��uX���v��ak��G�v�fU��[�T9���
nS�/�V+������$����A=�T����whr�]�D��-Q��a/�bN�@�pN)0�T�G����?�H�S��{��I43n*��0��li�F%�[�a���`J�kCaD���Y�����,_��c�6��(�����i�>2+9�4�p����"yfe��p����:4��#����;&�uz>�p�����h��y���%%mG��3v��"��g�pz����/�,�%��+>��|7��u�����1��U�S_�wO�j�s�#~��X���`V����|eT!��5,V��v�����e����p����c0*	���l�� 	v�T���EW�����ts����?��.�vi@s���m�uU���C��rx�aO#4yR�|��s��qvV;ngs?�5����:Z��Z��F&���RF.�b�Z����*�9D�;�B2�^�8�0�R��	�lQ���2���8�q�����)�^��(��������U���l��<#����{���=�f��f����;���F�)���0Z]g�5k�����V������lv���Fm���5a�V8�5C�>I>�B^P<�/{���Y �E�J�2#��YV���L��������5O-��?e���S�E�+>eY���y���;b�^������3����������9P=P�����~�?B�s��<���@��.�X�u��!~8����,{/�c��������5-�}��~Vo�	_����m^
�>#��D+r�K��<��*.X��5����St�!a��Se��K�)�-���Y�-eTY3�#0cA��1�Q�6 ��dj��//���lb�Ns���M�z_x��-������g���;l�����+�j����"���:�Ck���������C+B��6�H!��1muX�*9��~��d�>c����+����d�s ����D����5yY���~����R����(%�j�s@kn4=�~��K�7����Ge�nmF�Ph�s��%�����,���������|]]�@�:����!H&k��P����gk�9���)�o��/6�m+���<�Y�
L_����
��\<���@��7�������Uk��y����\WS,�s*�V�B�N�}��m=��1���LvY`d��b[�M�z����V���/�a����I(�Q��C�����qu���Y�M �0���o�m����
__��\^O��!���g�%�6}�-s/��e�����K������ow7eu������m#c[���d�(�J����*���N:���.�����-�#��a6:U�I���O�n�}�P��Bz[?�`�
6��T�$&���hv}3����(�b��%���&n,�z�����sq+e�n�?2�������������������������V���+�p'���c�"���#�
L�?��������z?7�H��y7���d���� ����0����v}�5B
!��k)�X��z�l �?��K���<��;�Q�"6����_Y��:������>�z�@������r]�<z�[�����/���v�H��������e�h�i:���O-�.����Lh�W'�d�*���>egW4,�����
e���7������^u�we�Yk�k�� '3���B���C���,�#8+���~�����jD�B�g���|8�i��E�DYWH��_*��^-�d�"<^��#���r@���z1�����n�|A�J����k����������Q��k�����^��-P�#]Z�v�q�������U��UXPX/&�$���{�D�>o�~��5��1����"��'~����$�Y���,�A��A�)�}���Z�.Qa����4����vE�������$a@�cb/���������M�S�����tl���8�	<K6�U��P
����"�LO����=��h����M4��G���A�I!c��K�"��P�<%q.P�V����W�.���1&o4��M$Q���a�F���n+s�sG-�����K��k+Y�Qwbl4��^�E�"
�(,���g��c�Kb|q4NMmj�,W���,�sw�����1na�`g���0�O��1Tn2�au��1��X�����?J�_�^�m6���������j�t^;���uj�4��F���WO��KDI7��"����'�l@�0��Pn��x2�<J���qR��;;�����NL"l����u��wh+��[=����~&`y��N�<�3�u�G���n��K]�^��!=���PIv�"Y�V�r�%�
��|f^|.���o���@���:�:�N�i�<�[�����di~��#�9����CQ!
�/d�e�`?}/�@8�������I���va`j���Y��!�xE��?�?��Pv_�r����/O/[�y�N���i������qj��d��@X�����~Ej^��%gM:-82�v���������9��`23�E*9���U\z�����V.�RI�n��$\�W��]h��%d��
^�O=�J����s~-B��O4�<��[����D�0��]��t����iq��������s�b�k�|�P}d+�����������Wu<r
�������57�v���0bf������}!�V�=��b�c���
��kV3�Z���6
SM��M ��	i�������q8�@���)�
bv8p5�D��9+��s�E�C�}�^��wU�|!m�}EpZ���{=��p����Y/+��������~���Y��O�oFt�2��x�BR�$��]�AZ��>s��8Q��)8��
])Gz����A�����i��h	�&�S�����d"���k@��l�!+xqv����D������(�6;N2�������2���4������`��@D�}����f������e���A�"$�:���3�.-}cL�y�4]E��L>��5�ykvq:�,.�Y�I���g5��)��1�����c�e����Et��@�4N�7�bA�]b0�d?53��P5c�J�v=-��x��6;�^e���@On&\�����h��&�2�M&��[<����R���p���������&I
�����!~������;�b]�%�w(�;���0XT�AvZ%
����`�������
���X�������u/�����UV9�t�T������q��������������_[���X�������.�
������V��%�Z3o�����u�.�_��/*�a��5�nJ�[(z���k�����R�^g1p�X�����;���'��N.^*�����.;�L�e��U%�����!�)5���>���!�H��/��}�	^D�V���zY��]b;��<I�����"J<����)
��1\���	�~lyq�U
��f�s�hwZ����h��$r�=�����XoMdk�CSN��������#������]Ng�q�w(\�2�Bp+V��@[�b���bnvQ]�1�k�� �>T%`�> ?�M���#0
���-���w�\���N��[������v�G�����|;������|g�`�9����(��wO����qJ�a��?uZ���j���^;=)	���`�!�5�h�P�FFW`������:����Y@��j�.j��K
�y��V����*���QT���{=��9��-��P��z�h�+�������,����f�_���=�������
���Js�?���r�dvA�������^Hf�y;��>����z��p�w����w�E��i�h8U�.��}Bw��=5��(�I��<������C���u���^�����k�Yw]�e�qZu��Ja�X�45hO��p����D(�yk���b��<���lmE�����a,ZL����t��rV��*����������\��f�zR�RT:o����u�_�L�/,�J���?L�an�
j�����+��}X(X��N������[�bE�����&�:�9Fz6q������)�P��7*j�e�g�?���'�G���'��+G����cx�@�r�(���

�NeZE�gp�Y����������h��PL;��_
6!M�����:�I2��koJ]�h]Y�Q��L�(8���?�vd��|`8>aE��tz6���G�T��K+�?y�y;�k���!���o����fyUr�ww���������$v(D���>���Gy2�9�$v"�����X���'�[x���s�e��!���K�*��c���joS��b>�M���J�8@|!���&��;�ES�*`�o����!��
�y�����qP'��Y�
C�J�k�[����{!U����L]���aK	 ���eOy�,U�*��K��'z���Y�!WH�"������f����?y����h������{��Ow	��U�.��E��6&9\ ��J���,�����iL��d�|=����h�2�Q��I���A���`/wq����R�n�e���w���juc�Z�%���	�0�!%!����e x
/������I�G���#��a��/����u�Dk��Q4:_�v�"��wsQ�im��A���GBR�A�� �5��K��+��|\e����+�v.�Zh��q���[HhO��&����F��K���c���K>,U,s����Y'����n�����}���:��4���_y2�.����K�Y������zFAc�N�]t�"��dc��M�,K���`F�8'E�)b�0���G�Jwk��I�F���9�����RDQM��Q]��?���#}qs6��q:zYIg0����pO��	B�)-���`��z#�>N
/D l���'w�:��������1���,���E����6[_�-j���t����]����-������s*���a�4�������I����a�
�*��?�a&N�1���p<����@1�7�
�Z��!��d�8����lmV����E�I@J����[�jS]9�T�{�U�/�R�#gs���b2u����c�&v�Ig iNn;)l���'�I�V�)?��S,�6n�q��,M������K�_��R�Z����������������'1b#�N�o$n���t����C��F��?����7@�>��M~���i��?��Gp^���ON{�x>}�!�_�q����9'�XH~~Q�u����Z����������������l>	(S��8'�ZtNH�S�Z��(G+��m ��I3jV�����N3Z]4���2/z�%��)�v��c���H�[`|m��a���c���8�\�@�B����j���jp>�t�\)p��q�x�����<'T��*'��x�_��=J�I���fE\����%������[�O�U�dq�I�R�W���~��4!W3��/�TA�k����c������a���j���eZ��$9�����F����%�P��������h���������E�[����Uz�������)cNCs����V�h
*%����j�}��C�����9s�����@������	Q���b�qj�:�Nm�Sa���1�?��26y�C ���&Jr����)��K7b�c��79���Z�����z�G��gs��L�9���L"�6r"�xU��)���F�P����y�XRj��/��>���k��K��M�A��|M��s�r��y�������%]���Y������G~�	��������Ly!A����W�.��^?�9����'�~w�����]LP
�	J��m>���tJ��YAS���1���&p�����l9i�".�q�0\��[���S��2������Oy����F�����������"c����}����_)n����2�R�������g�#t�l�7LQ"U(�J>e2W9 1�Y	�"U$z
Q���EUy+���$E��%Q�b����rD�����*��h%]=V���_����4M;@/�����6��\�.qs.t�
��;[8Q|�v�38~��8��'g>��U���;\��lU�*��������o��������o��������o���������������X
#185Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#184)
1 attachment(s)
Re: Implementing Incremental View Maintenance

On Fri, 7 May 2021 14:14:16 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Mon, 26 Apr 2021 16:03:48 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Mon, 26 Apr 2021 15:46:21 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Tue, 20 Apr 2021 09:51:34 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Mon, 19 Apr 2021 17:40:31 -0400
Tom Lane <tgl@sss.pgh.pa.us> wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

This patch (v22c) just crashed for me with an assertion failure on
Fedora 31. Here's the stack trace:

#2  0x000000000094a54a in ExceptionalCondition
(conditionName=conditionName@entry=0xa91dae "queryDesc->sourceText !=
NULL", errorType=errorType@entry=0x99b468 "FailedAssertion",
fileName=fileName@entry=0xa91468
"/home/andrew/pgl/pg_head/src/backend/executor/execMain.c",
lineNumber=lineNumber@entry=199) at
/home/andrew/pgl/pg_head/src/backend/utils/error/assert.c:69

That assert just got added a few days ago, so that's why the patch
seemed OK before.

Thank you for letting me know. I'll fix it.

Attached is the fixed patch.

queryDesc->sourceText cannot be NULL after commit 1111b2668d8,
so now we pass an empty string "" for refresh_matview_datafill() instead NULL
when maintaining views incrementally.

I am sorry, I forgot to include a fix for 8aba9322511.
Attached is the fixed version.

Attached is the rebased patch (for 6b8d29419d).

I attached a rebased patch.

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v22g.tar.gzapplication/gzip; name=IVM_patches_v22g.tar.gzDownload
����`�<ks�H��~E��&���;�d��I�gg2wkK%��V,$"	?v&���s��&'��U"@j�>}�����N����<��5�����
�v?�vSIF�jCi*���V��Z�`8k~\��E>c?�-.���y�}���G����F��q��J�U���x�E�����
��M+�o����[	������
�*����j56�_���������L����W�7c�v�aN�F�9�Z��mYS����i5xSiu�m�Wk�S�e#>gj�)�3��4`a�<c�����0B����������jz���c#���+�.�s�����v�
���MVR���-&�>c�z{8>z��FM����T��cR*J%C�d��%R��T2�J'/n&�����������sWf�bCC�������7�'���?
2��p���>��z>��y�>.��,>/��\�>�b������o�X�����q��P�p�X�4��X<���/�,�8���s�D����������q61��f_\�����{�����Ar�l�p�VA5_�T�,����0��t5����
jrp�d���J��������p.�X)���xJ���va��S�T�R;\���y��NY�ra��������@��2>�7�:U�j�O�l���r��
���3�a�������R���-V�OUap-��s�OY�3d����c�P�����<c�(���8p��d@;`>w@���s2���Xv`�E�Selq�F��q ���
h@��k`�����,Z�1\7o�*>�/�sR���'rk!����K�p�D:��� i\Zj��"�yV�:F�
�I��Ti�F����G?uRQDA�����E��%j��`)~��L0�*TT@����y� �l�a[;-���Rn�����g��@��b���l�!?���i:�c��#v$n��<�) �[=
�rX�U�k�3��)�y����0�/=4�{��7���2���p�D)Gs���$A�	B��)	@<7�~�Uq`"�Y@�g�+�!H1�/�JL���7�p.A������]@"�4��5���LG�(��-�sr9�Q�r9b���B
\���|v�J�aE����'��uT}��\�Q�����R|�B
@_�"[���` v�8\P�b��%���;�x�O�d+�LJ�@,��*Kp}�� i�8���]�I���k{kh�%.��o�����pPZ�����+�2�p�H���^{W�V�	�/U"�>��K5aA����$Ko��&�Dc�����0��@s���Z���97��
���B��s!#�@���������AHd����T���OL1
B	��H�^��<\�=�q�F^B�����a�6�a34���k
7����B�����[�]8�%���hf��B��,�K�~d+>����H��� V���z?gS�	�!��#�8h��5���K�$�L�������tU�����"T����L�%�r`�.
���lXf{�HQ$?\��EA'�+
?��'Y3����F\�-�G+"Up�2�T^�@$��9<g�M$/2gj�A���������'O��!�����O����h�n"����GzN�f�9_��O�<��t��{&E����FE��������1��TuYM����`F	��LyV��kn�K2�h4������gk!�{�
�(E0���.3h�E�5��	L�V��"�T�}J���"-��YEE�-���`��r cB����=�X/�S`����i�]~�����x�E���+��LqZM��U�NL��]%�+H�H��u��S)6+~�ng��������cUA��G�`���A"�)�O�d}3�_H+ �u�Dt����H�y9�
`��I�,�
|r;��1�s\��!�"���t��Ot����������� ���;��������BR�\�����7�.;��;2s#��r��T����uP��Cu[X��[�&)�1���`$��3�]��;k+���0^����;84%��i��'���TX��|m��yY�U����:ep�V��R��3@Vj���l�����U�����v��]��l�Tj�Bq�����h�]�~u�F�Z���|.�������$��������B��tm9F���4��[N����D��D�����A2�;��e��v��a�5�����r��VU�U5��oW���Y��n���*G�^%X���Vl�7wx����
e���4���?_����Uo7����J�P��e��Vc���D���D�d���?��i�33��5�=S������OW�,��P2)�h�@(��uY�V�m��Y(��%XRf\\���&�����qp4�����%Dp��=��w�^��bBUI��|�%���!���P}�c�Rk�B��!U	E{��N$D�'�,T�J���x��.a�#�$p`b���	��!�����������3�����["���6��wLDBxT�FcC������N	����j_(zn�
�d��V�H���Dr�0�!���fg���*��RJQhC�'!C�f�7'f�e���D�����Ss��R
���Rj+��R(��/T0�����3C@���B�1�+����T8�7��fx��R���s�I��Nj�_Ew �V�0��5c����+"�"���i$F�����
����R�k����[�b��\�@L�����W����������������w\L�Yg�Ea-�-�Hb�����`%"�'a-5C���^�(b��\�`~]��O��-�}�"����a��*�NYk~1�bl�����������W�9���P��q(�D���V�bj-������������g���]niu�~4{L��i���)��k��& 2����}d&��|K��sB���}��������h���=�+D�\�Q������O�e��I���_�s�-��P=�U��R#�}��@wDN_bjD�O�qQ���*����kIXl"^[�hXQx$�'2���D�F����6�r��3���F���D=,�]7=D��/�_���^��5��J>5���v�n�+S�3!�h�@H�JzJ�d#"����(�[$P�$>E=M��J��m|��a�t|�p|a������%#���h@>S|4J�!����j�52<���=3�;����F�^�coW�UD>@������q�Jx!��A�d�p�p���)���]%�&�=JG	#�'���� Z�����W"[�l-����K���g�]%i�H��-d.����=/$�=�UF���g�|J������`�B��7NY�@�/(����Bn�(
����R�"k�{v�9�41�=�c�=��,��;��j�M2��D����%����A��Qt6�w��$��}`:�[ ��;6DQ���C^�d���x����z�>i�,�[�v�n������!�JmI��ABMZu�&u�&>�������F�4|1����Tf�TA�������?c}��8��"s��nMLm��h?��qa�|(�=���� a��(����d����CE��;��>q�����u��5��1b8�����|�2��L"�WZLb����"��$�_2�������h���-|.:V�a��I�rQ�|�Ty1��}���M��o��/���>�������p��7>�l��T�&;��I�tTCS:n#�v��jiX��d��\�Q[��vP^Kpn��Fh��s$�-�5sR!Rq���x��I�f���|�B�����0����Y�D�}�HE!dR� m���i��V�h$��^���n��@d{z��b�d���\�h������GPBO?��L,	q�G���?���`D�g�w���b��k����t�u]���F�/=�
����%��|ad�aRn���(�����+����H�ou�d�iL��j���Z�a������lX���q���I�CUW����7�P��������|~*�j�(���k���|��'�����w3����D�Q����9�-���Nt�� m�]q48��>.G�����a����-��}�������}~�cV�o���
*-U����']9�w�>���fS�+��������5�������i�aM���������ij�2��\�|�����3lVe6�U-n'R�H*)+MI%C�d�T���rV���|��!@�V-��Q�>���n�����G�8�(n�v�t�Z@$�:����-�>:��u�����<��v0��n{���0���D7����	����}��
���d4R?�?i���:������uk�v���k����I9�����RV��Iz~�9_���F���x����&F`	F m_���w��o{|d6��Ilo.
!��������]����n����V�}������7��`|������7��+�{��N��ZY������k����6�M�fN>�'��6�dur5�]U�xzU����J#5k_N{���	;����I�{��]��N��h��I���W����4E�zC�?g�A
�	;��v����a�3v|8>��!����1�������f�k,d{F{�X��C^L���7@�w����C�"�tjC��K/������Kr����A�������3�����"�.[q�P��:������1�5K��y@/#�a���7z��YN�6���;�yf���@f��1��
mF�S��hH:��S�o�����:|���u���=.�g#_*4����VX�������^�^��9h��4����X�����e6�e��|J?F�1���)��7�!$q�p���`���c@�������]�*b���0���I��������/������O�p��������,��v2�Db���x�+z������}�[�������W���O��������=vtvr~:`:�����3P������LhA].a+%��N���7��w����'N��D�����NDC�b\��_����{v6X���D�b��Kd �0�_��Z�y�IWf��LK-�G�z�d	�B��o|��sp�m��$���A<��8>��P-�#�]�^������?�����R����zzN!�g�j8_JZ��&�<B����Z��9kw==�8�����Z���7���K����:D�;6�5��"������;�)����kB�A0	M?l3{#q<�7)U���,���> �����mO=��dc�01�w0�� #�k�f�SO
<5�Th%�K��v�`�=�����(��o��1���\V�����i�X�(�|0w���w��[��E�����h)��)?�s��
g*�0�����-.��_�a����m 3����Qh,fp�������E�=���9�\>�<��y<Dh_UV��0?�e�����q��q��� ����w/���Y�����J`Mh��|�tWc�����J����(����aw>d3b'�%N���Y�3/vf����������ob��]��3�Y.��x��o���{��F�,����)r��n��0h�2����Y�H��}���PHeES [w��������%3+kc�@�{��6P�kddl��A	������W:����["����J���%�`�4E��xD���z���j��J���S���
�z~�3�|X6��R�B%�:��Q����?�ws��x��Y5���,v&�"���}�8���JL04��M���PnR��_0w#�XaQ�t��;v�������K����������f�h�'2��
X��1pG�1.�� ������� f�p����o��,���2�G[�l��Sk�o�+��w�{Ocmf�qU^�,�?���Sl�dY�}�MR�+�.��s����5L���O�/X���������,�F��P��
����>�=a,�"�P��z)����*F
��8!YL�,�H�>��=��b��D���8)�#�,�n�E�TD(#�*FU���NEF�l0"�,q��Ls�������u������������j�"@d�M��
v�lZ
m�B���������������zF|����(�9��=���+�j3j�`���3N�)�(.&�bwa�@�M�|Pc;�}8i�#��y���)��b#���b���(�q��	dn���bv!L_nd�orbT��0e����b�M�I.�z�A����x���T���o���%�w$���Ijt�G��N)��F��\{#��,����������g�v|UL�����|��oH�d�'8����A�X�o���73a���,}Zj�o{S����D�bu��6c`&:��#�UlwG���#�*�v������xK������`�@"�]�@�C
�����q�����WHuh4	��t��;�����L���A��q�/��@�����X-?\+�����%��!�=?D���(C������n���v�F��h�I�*g�9�����a��6W�Dr0L!A]��K�u�����2SL&%�)FI���]L$+~2Y����%��j���)�k*u�a�%V?P��NQ0��{�B�E��&��]K�����r���{��Y���b�==�U��0^��zG����q����OR��D#f6�M6S�*&�U��h��9.�N9l$(ZO��2����Cw��b�&-�n�;.����F����7VAI\ i!L��p������5������&�/��'�N�$��2��\Pe�B:������d���F��z��+l���B�L�_��
�P6����}�N�h������a��B�R��0
�!'>��/R�Fd�[h��6���H-�C9RK����&��x �(���`A�i���AxFO�� �P$l#O]�)+1c��S����IE2{��i.�5-d�����8�sZ�](���bKi�"S�%�6��
�-t�/5��"��)�d1����x�[x+�
�v�E?�/+�0�.C���+��Hj��������<3�����6Me�O�	Oi
w�����9VW�FM�rz�d������L}`��`�(�����(}�QN
�z������L.�J����e�d���#��9&>�&��4��_X���=T�{���X���e�jy����������
�WF�����i�J���Tll1�<E-�Tb:�-�h.Ck�Qu��}cO�z���%�I�����X����>y�F���N~jSNH���\F�N���f���4�?�PW��	m�$��G�����������.�"a ��-���$<B
�	�����+�ZU\�J�C�
�:x��Qo��
����K�d�n�3"�l�	��
'�}i����`�I�v��m�c�I��r���kB��nUq�0�>�9�fK����O+���o#��8^���z~b��i��H������}-J�C��V�I�NZ�Q�����f�Y&�m��d���l��dd���;a�
G�\82#��(���]3#OV��������fA�����|�XCh�5������������TlW��B�Z�����"��bN�K�f�b�<���w��*,��D�(��s��D)�-w�����<#V���LPf��C"����M���=Vk�����	���E����P�C�i��G��^�&�j�9�d��������3�,�b�a��	K�����*�����,t�������)-_����;/q����0��Q�zr�^l>��I
�^��\sq�����]z��w�wy� �������1^���)�l��K�����I!��ORq3�O%)�(P��D~���#[6�O,���L���d�t�%l�~��<:��W���
�����P��zt��Z����rQ��>��B�i�v�������on��	��<}��O��������c(R�����.*''�������)���hT�THo����<�LJ�����0h�����
����{�1�����5�Md:�nW�$�#;{��8r9PG��]|�����'��`�7@}��"h���ax���@�����D��%�=��8s|�YC����?�?�����5>�V�(9���bOH_W��f��j���D�#B����d�����O]��d0�0]>���b:\������;#D���
�>�{j���J�<�I�����J��*OT�;�1��nq�;���y����-g��X�f��J�O�d����S&{����k�'��Ux(<EX�#�Zk
��1�J|�����kI X�(2��
����'�2����i�C��b���n+����Q�	P�l/�g�P1�I_�:'�����!���{�
�/SHp���aC�KE���8�����S�����mhe#���z�^��U>Xrp}�-�0p]r�]�����u�7e�������0��1����qV�����wD�vp'��O��X���-�w����������vi(�pE�wk�����n`[g�~,����=|���.���D
�]X+��D���4��@�;�2.
B��M�i!�S�>U=[�����-��v�;Y�%k}�6�Y,vK����u���i���T��W>�K�[�>���j��_)�W���ri�D+���;�&3V<������]����X9g1�-�kqQ'���%�;��i}���G��.�Z���F��`F��h	}Z��)ZB�d������$�2�m�|T?�����g�]�W�O�z^{�ne]��l��3��������f�Y���MnA��cXgJ����@��L}����$��n��� �4��IX����g�m�(��c��CxO}R^������&L|G��I���5���������v��/(������\�a�
��������Y�� V������{c�q�}g��l>F����g��{|�����+{��H�wc�YoY�aVv��F�m��e�����X�|��3��?���f�H�E���Y+�7D}����Oc	6��)�j-X�4V��`�c��	�Z����p������	Wqp�*������n�;��D��]��pY���!nS��wlzb������p��9�m
���c��>�_�;�c����;�w�67�Z���=A�=������c�-9����;��[^��B����k��n��kip�^���ZY0�����.+���*I����2<�����.���%%�.l��i,�,3�.��������-
=�+���	��V��J��Z=����33M5��23-p��7�������2�e�q=�H�����uii��O�
3����P����{�S�?a��d�M}R^�I������-GV��9,,�+O�?��#H�	�u��h��_e+z(;��g+�����-pm�M�l��H�wc��OOD�2M�Lw�XXVz�����p7*=��`Y)I�j	XZ.����#�z��z�{g�{�!�Z:���;�S�U���������|��L�0�-�������X ���<�����<�
|w���S��5�3�=�?�5�K3D@��,�����'���������f�P1�I_�:���z
���y��d�N4�Y�5�r�s�o��g���3�4n�(%]��e����V= �G�c����nS\%	Z��<�Xsc��i@���=y��B1V���yT?Y�G�gs�����O
�� ���=;���������p�����;��4������+�s�V&��2�=|B����UF�eB�qR"�����-p���&��h��w��@��,^�J���k=1K�<v��)Y+�i���n�������3��v�.f�\3����c��6��8������l��_�z0�����n�X%�Z&���c��������AO�\���&je�Y�b��i`����@�G���G2||�7�=�����bYY����i�|[V9��@F�����WW9�p�S����*Lk �����5��R]�eAV���b�������lK���W���
pS��)/{P��;2��?�v!]]k����2��>�S���}_���E����aZ��\�����$M�"��$-p����i�_=��gi���l��n����,bO������?�����	.���`�'�N�Qhu����}��YV��e�S>��t��*��2!�8�M������.��2�e�<����<^��L������'f�����p(�=�f�\+G�������1O� "��x��a�Cz������c�]f��*���M!���;��nu����gIV��U�H�Q\s�a�������7�
��+\�RP��4�A�N+����mX���6�'���i��4�K��kLH������{����4�7�Y'�$�J,�f<���<���#�;��RF�L�F��O������S��$}1��>��LG�4P��v)t����a1A�g�����������)j.	�K�4B2����z����H�������F9���e��:�[0���f����2��p1���S�����o;��g���������H�f4�<=O��y<^;��H�no��}��bd7��h]��+�G�t'f�`k�����yL�;*������mG���
��j����v�V�9���TB��|	y����IG����keZZ��6n��;����-��?�$�-M��&"%t�
�C4��{�$�+��"��$��M��i�Y���F�1�)�����b8��1`�������g'�*�����a���}�%�>P���q��+F�g}����������>TE���\��k������3-4�a�r}.�K\�M�Oe��	/O*�o/+o����O��\��k�e���I�$Cg4�id�9E������t��c
������0��������Wc$3����`W��W�+��#��N?\����$><�����(�����w~�S���������u~�y�)}uG�����)}�z�	�!��r��n6G�#�������J�������`y5�s�@z�C����C'X#��lB�/��jw����I��B#������7����x�M���@B�
���nD�Y��e.�eI~��V-�����_"�B�@P�QCa�W+2M�hh"0\I�U��#d#&M��QCF��Mk��0�Q��_�c �J�1_�&���+g�u���������0j���mw�z}����(�lc�U9hb�6�� �z�M����&�B����s�����cgx�t�V�����&p��x��lc��r������0r�r�g�m���}����[-�O�P6��6���F�3����V9��2��k����@���������(\c�U.�(6������_,Q=��Q==��A*���"(�1���&��>v��u�\3���@�z��;K�#�-����W�����$ �`�}��DP���OO
"����LB�����:��JP'C�
������-��<���v!N/ON�
`	�9 r/\(��i���_��f(@M��j�\�*����}����q/���T�=?������R�;T����~10�731y��}8^���L�Q�1q�V�]�/4���D�(��O 6�����5�"���N�w��9=���f&N���,��E|�A�ap3�_���=�P���F����Jl�Z��%�oH��0��q�`�z�u��;��K���-��T��~	�LI<��CPaYYI�aTW��'�B�g'���c�Y�f�v��AO4��Wg
���\{p%�
�I������ �/�����t�U>B��Mv����2��Q��������1��^.c�d�B�NEi��4g��]4�f�7G����^�c#/��BN�;�C
�R��xO���g�P�����,3
�KN�]��3��Q
l�����u`sA���q�L�����@�
3!$7���q>��M1h;��L�#>���
����mC��V] Tm1�@��v�sB�Z8��k�bMlk�2{���������Z�5-+X�$�����\^/�=<����`��HlA2�7���3��'�����Z����7�z�B24����Y��7s����HN{�����x���������3:]cr��p�I��]T�8��k�������������z�}f'd�aHw��6��cd���&�TXr�5�9�6h� �A���1�)`/G%Nzn�����4���	����1�,�~�`Jv�DP;��E�^�RT��(�(�����\���MKoKX��'�k+��8��<��|7��
k����������[x�u*B�5�O1�^���4�*@�s���z���p�!G&����Z����t� k�#t�������Q9rs��3.�%��
~�|aZ�n���3j�����,�vd
��m����m
{���-@�ab�,�mry��[����7����e�[��d�:l���?'��(����Q�C=�����(j�MS���J)����8���� I��u�w���n�AV�E6�N��}|?'LQ'�2�v~�9�����c���w���)��s����Aw��bcx:����g�������4D�da��atm��$G���
����@	Y6��5�hy�����HGX9�>�j��,
�����}�<��i�^4+&-�{�\�����1i-n��RQ���3�����}������/g(�c����	��s��K�T��n�Y!�3�~�OLt��gVl��#&�=�L`%�;����?��;v|�0�X�d���R�o��@~�8h���K�7�U^G��,6��J��l���TY8g~<�[�>(v
��3����bpF
����h�/�n�Z\c����:7N�ck������M��F��Xd�{n?j�A[���h{O��
��	��;��Mo�����7�Z�+Om���>�=�}��zqA�e���y�*8�P�5�a�o�����f��m�lR�@���W% ���7�e%�o0n.c�6;����T����5L������b��# j�fL���{�1�o�#��r�n��c�5���_%�u=t��2q<4��pvR;�M��1��n����Q��{���g(/Z<�`�s�����������1�P���=c6��Q�i�6��PTO����/�������%(��A�����:���GU�m����Y`2rI��TaC�J���'tE��`�+J�m��Q�������VV��+Q�c[��{�!k3���0����U���.	�������'l���_���1'��>`��������a@�\(�K��vv�W|0�;���� ��W�!f-RO��b��k��u�M���Z��XL�rk6�@�M �~E��70k4�^�I�e"���#hf��V2��_����(��i�8H�>L�*G��PHl{�
@��[vx<�-E���������c"�z�v�T4�>�[��-�F�.i �T<�P�
F;��8��K�������I��J�%���.�3�����NGlo_��e?����#�=uU�� ���v�
�u��� �5g(����Xl����r;��������V�V��[.�o�!�,�mmm�����������-�~b�C���F��y=�J�1���}�_�>/����JC=P3~��6����ABx��~j���h\9�g����
��=B�0��g�m�%�M��4��E���U�5��1B��2��sH����]]�7�������o��������_� /��@a)}e���V ���t�����&�,�i}l����(�r0n�8*Oz����>OZ�'!����}dl.j�^�QX�;���������8m�>�-����<���4���%��B� ������[��t�����5q�mT!A�E�����H1ZB�_����X�������}l��E�}R`���6VA+6���jJ-�����{,$v�����y3���{�
E��U(�����3��&��W��N�H#���p�&&E�W����n���hOE{=��""BYp:	��bV!1�bZR�
����xW�y&`���:�g���,�~�{�����U�@���L�������5HM�&���M��@Ok�t��Uqtvr��4r������IYg���4�}���H�w��lh��*{�i����q�W�D����f���V����-aQ�����
��$���@���\t���,;��$Q�� #�&��MJ��9EYF7�����B�_\�F2gs}�D��A+���.�A��]��'v�
l��x��hK�B>���43�wp�&]�}���	MO���]d�i�){@�R��A��&T; �����;�(B�L��k�U^�Yj*����_���
{�{SNq���	On�T��0�����5
-��Ni1����B����Qd���^{9�z)������.zu'()�C�C��U �]���s�0�>�m���qs�?�����)��D���~?��7x���c�P�r��I�b��=3��	-)�t�����Y�`i\���<P[weS��U�9���i+S��e�s�����B|+
}����h�7;}��0�����)r���_��zh�������$�����aeU�wYY�i�,J==��@�.�#�,J>=��b���#�L��w�P�/1�eK�C0J����u�H���0��q��.�vD}�s/���a�fg\�85z�V�WK[8��E�b�X4�r?�Y9�5����>�&��Ph)��KQ*��K�27��M�&������Q1c��%�cd���	��C
4���-*Q������rI7Y�^$�L0��w��*���zon�Y��xC�p������������hdf� �M�n����;����9�Y$�X�vw$+N�n�rn'��#SB�����z��$�N	��	��o��,�bb~�����g?%n�!�$?��d��X�o#�czj�(����Fv��������������n6@V^L��a��Q<7
��D���d������}FD�xC�������2-���!Z��p3�3�������������S����$���N�����8�~����HP�')�G�xT�P1���|e�f\�
nt+����U�'m�r�C�F��A���h~$_z[��joXh";�^�D�Tt:���}�P�����	d���I�I��`�� �U��e�ly.4!(:6#q��4M�f�Y�}���>
��,iMs��c>���K����R�Pjw��L4������v����`Ae�L������P�..zB3�����x�d�]�,`c����x��s��e��c��e��y'�����w�i%&txj���c�;������^J�K9���>����E�XL�]�
�rq�"6��0�?��NP(��bf��v�d�R�I��3feDLN��MN�5���xd	2�U��H��Lv�H�a�I@�������)@��&$sL�kB������K����I/
deXf#����Lo�j�"A�*%�]���@�$��E��S��i,;��rT��/��F���#�&�,����{�=�2=x+�Kh�A�&�$�����?#�f��
��F�!3�g`�n1�^a��}P���!G����8Nx�_���'�8�o��F�x���6���z��C,2s��z^���[�h�uS����kBr#�p81�I@+�.t^��1v�tr�_�|�/�i,:����m7����D��Y���[��,�Z
����;w�]������z�;��8��Q}���S������l��0��2���-?��H�����$�J�?I���7�N���mF~�l�����B�o���� v�����I�������� ������K�W�Syp��g�8�F�����on��d���o����,����b+-�	��;@��	�b7GF��V��~�c�����}�A��?�����+����������1$�w��'�����F��(�j���?��.�h����+h6@r�6��S:0��"v���M��������-�S/��o����xC,aJ&u���LhY��O)��e���I��v�j�M�S����d�~mOf������������&%-}Z������3�o?�
�z^����O��>��V,x��W,x��W,x���,��tFOK�
n }:Z�c�p���{�bW,v�bW,v�b��,v�^]?1{^{���1�Y���+.���+.���+.��n����5�����eP+.���+.���+.��������1��"��'���1�X����X����X����Y����Neg�p"��1�X����X����X����Y����N�g�q"��A���������������������Neh�q"��A���������������J.KJ�c�F��'q&��A���������������F�����Ngh�p,��A���������������F����N�h�q2��Q������������F����Ngi�q8��Q������������JFK�1Ng���I��.dP+.���+.���+.���.������#��.dP+.���+.���+.���.�����9�c��.fT+F�b�+F�b�+F��h
KW���Z]{�;�1�d6v��<��Z��R���-.�.�
>�[�{ ����N�@`�i��w��F�A�����8#�
����La�8!\R/��?��(�jl	4pR�%"�:�;�c�Sv��@�s��o8!�7������}1��]G����������`��t�N"�/��;�n�bk����8��/�Ok�oE!���~J�0SUSc[ �U3i�`&���R�\l�������Fr�u�����s$���~�����C�8�63�x�i8
��L)W^����Tu�����?����n��_	P����t������v?W��N��D��o��w�o�xsyzT�������������)K\���M�?*��MxyR9}{Yy��
s8�����`�0��z���jz7�=2���F����R�vdLRd�n��U�J`k�����u��g��.������V�]t���r�9�E���z
���Uw4k_������L��p��
����g�Rwz��z^����q��T�^i��� ��
�^�i��N�N����V_����(�8������F�����o*�'u�^��wy�D�9:S[�������\����|%��Z��5km-�Lr�B�.F
���������7|�*$�S�F$v��"�����a�q�h����a��q�/�(T��Q��G�}�I���=�-��Ryx�vG���g�+����.�r|�la��iS4�	B�WM����S�S����Be��Lj���������Ht���}��M�'�Y�V���X�mej<���E����� ����xX���;�'
Y�����E%e>���;�Tg����9�7��s���}�}J�7k�i~��
@���O�h��p�?�v���'Px�`����=rfG����f���,j����@��1�f���	��-n_]
�+���a)��;���E�@��IF
��|��>o��.?����
������v�������Id�H����_k�=N+2N�,��v��f��F���[���gw�����{�+X�\�%}�A����
�d1�d2<������
�/h6��aj���`��-1l���f���TtQ�x	l�$f��v"@�}���^|���o�!\P����h/2���pGI�L%������J�7���I�&�������k��G���@kw���u��>�Q�
*h/�nB���l���?B�E(��(�rr�1������:�-���Yzv*��g�����lt+�����@��>��O�����M'�;��Z���k�bM}F�N�1E�U>��fb'������^&{x��Gj'������7o.���$2�H�0���"�M��c!�PG��!��!�"A��I
�,��N4K��8�,�P�=^�vQ������V�K��:��n�p����q�i�A�����\#�+�y�������6����s�7�y��&�X��g����0/���u�Q	�q��C�z8�o��'C>����{��X�8��O1�HS|�B��
�6K.�)�
�~d�[�"�G	����!�,�q�2�7���j]��v��(9_GY�$,g
�m�+AMB/Un�E��vy�^��K��\]O���[��d�t#��(�E��QEd���d�X�C)%��)���
�(��`t+�A������z_�>[��~���44�n�]���1����]����nw������{����O����r���HW���"�p���l�(6�tH����L�����Fc#eQ�����������r;.�P�2Q���c�q������<�Q�����(���qNh�'��R�J&F���}V������6�X���#F��,�a��&�@�n��������k���H;������"�Dj��
�n)����"X�R����a����m]�k�H����8]�;�1��[y�}ybB���1�n������x��0�E���(�_T/.�]]�������B��f�7v������m��m{dK�	��/����f��$���E��mv<o3���I�	�+�����b��#���fL���{�1�o�*"��r�n��c�5���_%�u=t��2q<4��pvR;�M��1��n���A���K�|���K{�t����&=��b�m9��mt&G��G��
��?�G�@e����)�<?�E�T?VQ`9���7(���rZ')�|UAw-� m��`g��X��+rp��I��Ra$�R��n����t<-!:�_�v�>���;
a.$PV5l2���F�6S��j�b�>I_�5�E�,��m��?�����&�2�S���/D��5�P��&p����Hu2���OR�0fU.�*����j0��Q�~�A����'v����z1o����������zFc�ne���)��
Va��no���!ixvw���|�~o��N5�m)m�����{7OM��Q����2~Z{;��Z����X��N��W���yq��W��(<�(��������O/7��w��f��D�yP�k8�����i�
����Nkow�y�P�m����%�w�p����!�'��������
`$N�+�k/���_���|?�������AQ8o�n��c����
�E�p�,�
�����������*��w���U���V
�Ja`�� y�r}k��21z[�����E������?��h0��7D�;�e{]��������<o;~k�6�|��� �nM��6�����f�V9hb{��,���u|�<�\Zd�,=��lN������3����������+���icn��Yw�m��(9��N��S������ny������r����:�~B���_b�������-���H8�;u��a5������k���| H�: �����>\[:]�BA'q`�_�^�A�m��J=��zo����5tm\[s}`CX����)n���~�_{_�A5�<.`���crB�0��^!W����@}}�G����q��3?��w���X{�L����i����x�|}{�����wFy��H�����HL����.���Fh��k:�@�9|�BlH�����[����X#���E(���sb�hH�
��+�,�R����-'��l���L�����UK������r�G���oZN������yr3|%����&�}/W����wO�b�P[B7��y����5'�^��F�+��o�|�f��[���} ��5��$��P
�0�<\��
���l�����?h�Fi�o@z^�t����|�a�Yp�����-x&6����?�����)�7��f=*���F�Fb�f���R_E���������l/+��������bcss#k��c]`��� fN��v�H�ubQ�����N���^)g�Hq�2�8Km�@{<�|���3"�Sf��r@]�D$����7�[�4�d��5�WtPN�uVkc���^Q6����"�k���:��!����n��,���sV��LYl��lTZ-v"�!�^�h�����'[�>A�y�y#l-�9��Q����P�ZC�a�H�4�C�$�C� .��{?���
az^=�{����3i��rYZ3�(�F
SN�m����~�y���`�5s���xa��OD�jNz+%������Ni��	h������3A��� EJ��,�����O����m	����k����5F��7l�
�Pl�@l���O���{���y������a���������@{p�m��
���'��"��7�J=�4���{c��%��O��z�V���v��qz� ����7������No��QQ�$�s���=�����o�����
�Tc����Gx��.sq����!|�eF�h*H�j���@�W��F��vt��j
J`�����l6��o���~9�b��R����}[=���-�����v4�$��:��l����H6�gtTz$q�={yx8�b��P9�����o<�sX����$Oju����'MU��#��7�g��a,�
��w��w�
�EKp`�����A1g�.v�Q2�`
A��H*��g�r�Up�[s�Y���H(i,]R7�)�%�-i��^"o}��z�|�T;%�#��oi�w���}z�+'�k�"� $��]��y�������1z���8��a�����mQ?�	<'�����I�_k>��U�=�
&����w�v�Q���Nie�}�?��6�Vq�v�;;�N��<(8�N�>�;8(����fq�)/���Z7-K�z7P�X���aq'��k�o�e�\����L���-4�5j������!��n����\�Kl��`3zu�l��p]�bF�����*�d5�����NY�K��Ai�����V�Xh�7�{)zS����{��n�����I����J���[��
h�o,e�g�q��f��:��6��1>lz�~�|�9�T��l2&�
��!*��v�+]e�?:W���]��#<�`?��(�i��������<@d��F�����Pk������(��� Y����&A/��a�v���Ay�+�����f��,u`�*����a�1�5���k�����d��c����C<���)�~�6�!(�tx���1w�-����� 0v�i(6_\�"���qd,�5��Y����'8��-�t�n�3:�����ca�Nw([����m�1����L��\?���>
g������|1��R������~n�5�N�3%�j2��2x�#�� 
vn�N'�m�����h�2��i����%���0���v�������9�Fk�Y(�
lib���&=oHX�-B�ry~���
��	���e��OY^i=�z� 5>�|�#L.�A�9�Gvw
�;c��'��*��L�4)���{�rX��h�S�c������h��
�]������c1pwWda������g���x���Z+�.�����]<p��%����pS�{`�
{P���\4����t�}�������M��5#��~�
��#�$���;i�H�U����n�����Z���Q��901v{AX����W(���E4��Aw��>���g�Gyk�fu������
����G��1��k��\��V&:NH��4��o���IqV�~�a��X�FU�s�no#U���-�.��?�}s���C������c��c<���#g��eK����QD����/���@�hdC�4�c+��s`�`y��K���,�|�b��\t��4�
���d�D�0������1\
���3j���@�K���]k�������m��)��G�6:������H���������>�����G�����Te��V��I������E��:3��~��nc�����$Y�q�p�����u���w3��G������72�L�f���`�������c5v����wZ�}�)5g3�^O1�^���]�E������[#�)����P�wKYi^�~�����gJf�������P���n��p�!��lE��$T����m��1Z1-������B����/ymM6,��[���	[�e�vL�t����q�n�Sa�����)� �k m��������"	�1��dD�L��2ZD�M��_�qv�mv�Vy�S�K�5Ok*{b���qa�,E���G0K��"����Vw4
�6�mC��W���}�A����!���+r�`-7S�%Q\9V$�967�ay�kQ�r�c��.fm}-6g:��#�}�L��Z��[R��/8,�I'tx�IC:W�������������p*�[�V���o��?�X�~�8�p��1��Q����8��+/���_{4
:�y���b����7�c�*�1������G\V���zj���q_�8s��X�R�,�'��H�	�Q��c�j/	R\��|�oo�i(��s��K@���$Y��S����R9v�_*��������[�����N�������W��V�`��n�J�;��f�h-5���sX(�����[����X)BX)L�\G���[�����^�<+3��WL��]o������I�Ue�T> ���mt���:�����*�����?#f9/w����� d�z���^�vJ��v�������y�k;������7MGUJv�����6�b�	�R�5�����8�f�;)h�v��w���~>��*����R�� h$�aH��Q�(�)J?Lg�S��x���9�1�\� �\PL���yN�`b�g�_��j"���qV�����C�������Hu����(��F���&3�����$����|��%�����}�����5H��X������
�3��z�"�� �F��nJ�������g
L���4��n{��J/�*s�`�,'O
���4������T�U��w��q���A�;;9�.�;I��c����o�����<@!��5��}����Z��pvN_N������*���
��4E��;�[�PY�t�/�R�y8�
���N�0���v*5�0QL�J�=�������zz�?V�N��q�@P�z�m��Jv���}�q�����g�,��Y�jg|�,�q����\�����+Q8"�p�C ���G���;�B"��Cr}��8F���w�D�#��h5� 699�GZ4}K��:�-m<�������F�n�L��f���~��v~�?'5���JF��~��*��B�{�O2�#0�I$\��������.�L���F
���J^����!/��bs�k_����:\�/GLE��~�!�M�$$~(�0�����1'��k���������e��B����CI���P�e��8u���1W�f���u:�9q7��d��WK�f�����)�lY��|�P�<�����3��~-+����� �@1��
/$78�`�9��� �E�����$J\`�)�w �
^�]�r])��n�}DP�����;���SY��/U���@dh�B���!d�H@j0A��el��5�k��-���
�!�?���P���s���e��Am��5e�^3��X���#�|�+���g
�2������?t�������b����G	C�/���38h��B�.B���z��x#8X;������^�M{�4�v��>��b:O����^�>���c���{�|k�Sh�J�N�[vz;���^�@iqnk/�F���v-o�j�&h����9T���	����*Pe�e���Z�7}�3�6��j�rM�F���4�L�� .Z���Z�fL���������Io�"v�K�v������[v��������V�K-A�qi/�P�o������qf��o�HIAWN��?V������r^m�T~��d�#��7�J;''.O�����t���o��(��;rz[�m�]�����c���`�����������S.j�O�fz�?Up��o�_���H����F�G���|E���3)����o��B�������{�?�������]{g�iv���������-9�R��{�sP�+w@�^����XJ����_a����\��*����Z�C[��SUAy8zN�����Z�p��v�u���A��r���*]x�jI;7���=�vl#�0)K��(�Tv<r�>�������o�l�XN
SLo3��;<=[����U@�x��V�47��:a&��0� FR��a�E�J���������^*��������A>o;�n�Jv�YB���aB�
b�e��������B�>t���DV��
LK�'�������_R��/���-,LsF���~��`�{�a���xY���i�u���9��n[5X�}�����Vu�����~�h�=^����6RY�L	i-2�����?�!��{!]������D�JfG	�u��e�K����yj3'#�|+q���oul������V��}���T+���J������Tz'�x���~pC(�H����f�x����r�)�=%�R�;�Z�q��m���@���j���(hR;a;��`�����������>�M_�4�����JT�l�;�}�U����;E��������Z+�J��h�XK�����EH5����Cs��3����k`� �@�C�m��*�d����6M0�Z�������{W1�<6�;����h�������&��������=l]_��G+s^=9���'��VV%���)|
�:��oPr�c_����x[�_��/��X��Z:t|��<H���4 +U\vl�Q��
���-����K�P[�JN ������a�!A�#[PH>���]k�A�Gsww�U,A��	���&��Q�s&�9g��>Cg�!��P�&�j�t�,1�����mNe���S�����jaw���@,��M� ��<��S�2��j������&X��n��#j��t�R����u���O�(�������&(#c�E,AvlN)d+��+�K�����A�U.w��b	
�Mc	���y����Z8��+'go3�X�*�<P�fQ(+~�{����n���u~�K�����~�+1�������� ���������I=������2KX����F�hS[<(L(���7t�\<V�
�=R�Yk���Bx���/������L�����G��srv�����u
�d�t��9�������=���P��R�'��`��G�x���K��|���TbMQQ�>!"jp#�uBI$&
�"I���
�	�ROg�������3e��5L�qX8��jH������K�(�_1y#~gO���25#
MA V*����h�)���f�i��m��8�v�t0
i��4t�������MdHc���g��t�����G�������CFm�����XI)E�����3��	_�l4�	m]
�8�����=vZ�����Z�P8���8��Dfo|u���7���#%q��+����������.v�u�7���W$L1���&������o������>���V��d�U�����Nggo�P,4���*�Uh��b�����o�1��?e���aq�pg?����k�����>�������-�,
+_X?�0zs�]���t���A�v�/�'����e���~P���3����v���;Z_?����xS�����s������+���R���50��^������[�<�m�����D�i�/�UM�m:b<h���J��MA�����Y���WC��������/��3��k�Zx���1����7��������(������6P�y���s����h��n�H����LMt�$���|�p��U�:�]�|����u~��)�2�q-%��b���P��
_m���['��?�:n[��	��BOX|Zw|�@��wZ0�_CP����9*	 A#�z-kJ����p/��c@c���Z���JAUk�t��;G���\���_�n�8����z��J��+�/Er<g��-o��9�$�@	*��F��(�:(��?�	��D��&^j�������o�����a1�kFs��|V��P�
��7A�l�����:4���7���X�����
`4�L�F
�XFAC��������>|t����i�IN�=�]��o��!��[�q���G
����J��m:r ����8
hJ�����H\`jH�``;_�&d�(	���1'�������(4��v�;6I�p0�)"�#��o�n��/��������R���8T�zX�h������gJ����1�\>��=!���L#����=F:JfJH����?Mo������W}���>���j��v��h���:�����\Oi�yA�T���-��+D{��o�P����q"�AjGy;��y��p]���J&������?g������i��\�YV�=���!�#������Zdp���-�m���L���\Uwt>��
�
���Va��aI�Y%+:U,�:�4���R��%�i9�V>ou�VD�BR������J�b�z1�O@��
��`
�9v����������@�q��/
S�(�����C��X����������=���;:X�S��}7x���nzu�K�C�����~��v�H���@A��lb�H��7D����Zg�j���V���>����a�h���i-_��������g��>��m�As~����G�����Am���Y6�<#���|��(�0��xJ�86���.�:}��Ih���G��4��]���iM����$�qi����L�?H�)#
��	S�z�[>��c����z�@y�!U�p��1�C�Q�G������e����.�ah���T�TH�:�gI9 �n�%�4C�Q8�#���n�N�0El�L���u�E?�o�7x�Q�b����q������ukJ��Q=�k|�I}q�b�d|�#e���������L�(��O��g����	"I�z~~v���d�����!������xS��/��x������Ay�c��/(���2Z3t7�hXGc'o�E��/($x�|Y}R�z����������	c&�
�&-'�)I�q�RF^�������q37�V�X��O�c^]��Br������h4<e��
<��>���%>]�5��$c���s�$f�Y�wKA�#D3��zOw����o[����
�"%6��^^'Y*f#��>%��/	N�'x��%'��
�*����	[��h�3�W&�l����w������2�b�"X �7���8D�G��������
����m1����Wx#���
��N��3�l����'F=�
]UF�'�P^�	
������` <���O�����t�<������+��Da��t�����z�N��*+U�P�7��.����
�gD�	���iN��d�?������(�d;"�E��c�u�]�VL��X��h����aL�f2���oNl��7��A%����[����{�<��O��{�w;�6@��������Q������*H���`��-���h�x�!o���W*�j�(:�<ri��n��7a��U\�� Z�%L��Bl�lxD�K�<�=���pcR��d��a����j���$&���nO
;�g0W�=%4����Tp3`C&��ih�y1�7:bc,��n�lF���������l6�����0D���x-<$*��u�`l[[���	H��J�F1f!�&YK�7ka�j������j����]c�T����)�_�Q�\�d������P�����X�G�GX�|d���f��8io��c�:H'5��-xj.��v����J�����0��%@9�[r�����G������%#%&+Ki���%�|S>0�������~�)E�
Pat�.����M,��'I����%0Oj��]�N?��Y,�8^��o����t��$��Y�h5Le�0��-+�]�5���}xR���,��+��zA#�O��:7N7����J���*)*��J�C;�C"D�,Z������#�l�������[����+���nM`���Q
;'[2�j�<q��)iK��C����j�-����r��8>~p����B��*&8��:	��4������Ij	�����|�(l#�P���Ws�������<�M
��2���P*i��V>�T�k���Q��'��3a�����&{v~��e��D{n�r��a)rg�D�-�DiQ@��r
$��������D��o��%��s�hY��y�6�E4&��
�0��#�1@PNJ������0��[�[�9z�tm]��Q��ncF��@��4��.O����v{x�
���uq��Q;�����������U�%�i���g����di���������`���z�������r��b�Y�C�q|F�iRWj�r�x(���).J����;>��^>+'<+F�������#�Oc�W����z�}����D��VR��W�H}o(i��J��z�Y�z���?�������V�
m��P�u��m����XU�kdU+�&�*<��*?+'<+F�EW%��z�&���,���Yd1�i|Ue����/�
%�*V��*<KZUx����g<I�JC���������d�>��9C�U�r63d����@�"�8��H�X���L�x��g���5{�cd��/��J��t���.�uz�tt�\/�q��=��6�I�������ixL���Yi���� d�����#%|��ot�:v��e�e�X(c��`��l����uzx�,��c\�e���ee].��ZjM�$�������e�p\��-����F6/�R�U���^z��L�
F��Q�GI���	���ThTd��n�o��8/"U$����m��/s���������>q��c���#�US���ll1���M�sU���.�R9����fg����v�Tvv�������&�3�����
��/S�p����@���u������o�*�.j�S]�vY��2��,���������r�8�UN�����j�<����V��R��Z�����[-������[���^�@���p��&���5����m���`�>���A�!��j�6�����S����k�s��5����������`����+^�|�g���A�0a ��)����'���_<�h���c��eb�0>j??Fct~�uj�?��k��s�����K�M4��=2��K9k7!�,�[���k��}Xov%'Qg
��	��P��o��34_8�mD� ��h/�0"��w����Y��}�mM�E���[i������,�V:o����
j�\sY����K��P�c����?#�k7���������	�C��QDe7c3d�����z�UO+��b�O7�bg'��b^���q&M�|���
�F�}�:o���D��*�����������q�^�R�'��_�Q �^�'v��<�/����|u}[Ea��k�o��<�m9)*J>�8�2
uP&�d�a������5��
��E�C���xM3�k�3�t0*�:'B����5:wZ�K7����@��Hx���)O�9ov;����
�	��s�G�����F� I�{+���-�:9���H��>��z<L(���,A��R'`^�t�1�k�g@��(�J��9���B��jZ����:)2kl�$����|G��5���=F��(�Gtx�
;�N�?��=}�Cw�}s��1U"''�c`�1Q��u����k�^��}��N�&k>������
��0
L?���=)��������*���Y0,T2�x��&�m�PBe&�Vh�ZI7x��T�B�b����S����m;ko����! ���x{
��3�2����������q�� >y��PH�Ze� #��1���e�nU>K�X�����&�cK��4���s���H���KE.sgX�P#�k:�y��=��Gq#�/C(��l�����SpILiUVg�H��>��� ��mO�%��,gKR�qg������q��
M<�@q!��H�zQ�	432��vf�$&M������_2�c��nvG�gE��ShEu��WN���������$KK�Q{��LyE�����E�/G�q���Z���*/����qX@�7l����3�����"T��'���� ��y�v��VB��4d�V�(�E,b��6����hBi��!�����4R�����L�1h-���XIV��!^.�v��������\.�]`����hp,n���]�V#��|x�xsyz��35*�o/��Pq�	t:��n�l�����"o���7@2��_��{�m��)����	]����p��I�z���Gc���8C�CjdP�db�Y:�*�Ic����E*ES���*!
�h���C	'+���Y<P9@��Z�o3�����
o>h��'�L�^+W�?�)1�~��C/�*��C�����B���N�*X|O�B��d���x6a���83Z�uo[���d���g)F��2U�`��}����
.�t}nZH�XS��Y�]�?��s��������R��&���B�d��<��$ql���o����n��v��X���s�`5MV�b3E�Q����]��?�h��V1eO;H��������Q��O�Y�����Vaew�����db)tBaK(�l-&�D��b���e�2%�L�:�$X:y�nK�x���R���e��`��@�?.�Bv#W��EJ�0�)l�P@$/���Ex�
���1�i8�/PB��U*H��EF�N���Oe{e��Tb�(������u-�V>�[6�JU��aM�,�����E�0������B�����^Q�5���~l�{��_�V��3�:�B�ng/g�,g�`:�(�Y�'�d|�K�ed�dF�	�������F^DJf�k%hD;��4.&S�W�L|B_��I}1�$����B:>�qa�7rP���F�R�X@�2.] \�R��E��s4����;2�1c`��AnwY��?��XH���SxMn�N�h��F	f�J0%�'�A��v��v�Df����L�j�ubU�����
+����c�J^yih�F�n^��j]��z�=��E��+J���E�w��Tw��P����K���K����%�Kj������v���-GGa�'�*V5�-5���d� �� ��s�="q�X�Lm���&��D#�x��Tc�QL�U��Il�_�������(#Ul4,0O�OW��g$A�����*7q$��2�HB��P��tMC3�hZ�Q�f�<���4v(	j���W��������`^��$��@I��$sx�.�F��S^�v$N��f������p���"\�3��][���������j9^]y���+R*6�� �3n�;?�Ft����*��F7��:�%S�}!z@4^0O�$��4�-�J�$)E�ib�5���.a$kk��H�(�$f�]B(�t����u�i?mN����WgA�B�n�)w-���!�.@~�!����C�.jRm������:�VFD$E�EZ]��1g
3L���P���C/8����aK�7��8�Q���zz��I���N�!�_�Wbg7gp)P�;X�������z2�G�HQ�����w��-�[s<�����gF��[�+I�"��Y�7R�H�*�x�|�����T��/��)�Q�S�aL���O��P�{!1� �A�s��=� �7���1�F��-���AG"��Q�s�u�(������-,����<y=��,!��M~����3�d_��H����kvNb���{�;������}uI����P,Y2��26����PlE����;��X@����Qk��]h�@-%M��1������Z����59���pQJ�\�*,kYB����3����n�i���Jb�n�C�*T��5�YA�G��7�p�	�@��I�#RnJJ��
�����k��C�����@'���G�����VV���Q�n'�Z{7y�c[��w&S<'��(��?�|�R��~{����(����e�`%�s�d�w��eQC���6�D�[��7��4����M����@�Y@^�K�����K����FN��N�j_
�c����T�}�
JW�<�������{@��cCw����4��0��^l2OmA(:���
}�x���8�����Q(���{2e���#u
���JS��q)�|�)g4uA�1���f_�8�5�tj{'�;k���cv�m�\�F����E�5����x�[Mg���@L����!.�=<�c��]��f�$���C��R?;�K��R������qQ��X���U�q���2������xj��0Y������lo���B�#��=���/���If�JT	D��W����E���&��g>��#���C0�W������A�##>�{�9���Jq���op*�.���5������<��-�#�H�N���vBw�v�M���������Sy+(Z�;#g���}��?_=�\T�/�������4��	<�'����s���
��&I���q-�N��5�(hH�a_��j���Bm]�$�)���W��y�R��bx|�����ih�9��(D�G?�,l"���j�jB���
��8
���������JF���k�L!��V����!������Cd����D��0/�x��<��)��G�p hp ���P��f#��S�����A�����
0�I�P������q7�6%��:IKw���`V�"�P<	;��(|xH�Uf�"��_���&.�m�r���R��X@,X���e+��v��!l�
�&~"����v72)���-�����"���>�I1g�C��N8�?I'�i�M�)����H������f��[k}o<Rs����3H��dN����T�\��i+|Lp3)n�w��zS���u�{���(��2��\,=Is�h��o�V��k!�2f�@o(h"�cdtJ���
�S�=H`�-
�����R[R�
�h�y�;�$��=t]�#=�&?@R�D���M"R���Hu�7�����K>����X�����z�Q9=N��{�l�x�OX��R#����tY	.#�S����j*�s	��7\S6���}�����
�'�����$g>�A�"��N�\Z��T�b$�l#��pKU�9j����(����;�����h�GvB�Y�25f����K3�$|a� �
������nc�CNk��W�=�4����E�j�����P�T���������M���yd=�M��4N2<������� �q��:��������5F�x�� �F��u���M��,�y�l�fLp��F������h$
���1e�pL	�+'>q�zo�ZS�������`��S������Us���mYyL �s>���r|���2"�N�\ln:����Y"���-�{�GgN��I���&o%�l
���]""�������}�)I��Qx���d��5�
��t�y(���#&�d����L��]3#r��D��D����3r%�X��a���%����Q��Q�
�v���BQ����C���R�%���v�������EP�����0c.A�=��=;a9��G&�r�
 "���d��k6� *G;�w�o��W���������1�C`���i��Ntg�q[L
x[2!��qnk{:N,������Ng����b���3�^��CKI�01s���#d/hMT����0;��h�g�#.Jk���{������"����N�(��m�R��#Q���5i-��ThTn��
��)�e�=�����'�Z��r�=����E����MM�02j��v-}�0cu)mG[�	7�$��4[F-q�G���%T����{����MN~��It�'A^�-2Z�uv�nx#��U���-�Ex9a����.?��Vz�$k��������=9����V|�h���f$���p�2��-���Y]-R�����X�R���x�C���[3 )wQ$S�x�
P-����a�bK=�t�T7���Pv�J������[3H��&�S������"�O.C�,����[��O57tz��B�8����_����6�]�!A')�����L�@"\��A^%�D�����av���#��b\#\E�1��E�I��q����
�Mu��LU���1XYFl���PK,���t�|�)V����9!����
�N��;�Aj'E'&��*��;*�&�XW�wP
e �g^�*�bA���si�S�Qh�9U�zJ����b�u�5U�b�OS�"�)������R����KE3�oaP�]t5%�tAI^��"��AL��*I7.)�yE�����Ct\�$$)7Q��(�z�R[oY],xU�s9������m��Q$��+#kY\I�;,��T������y>��8��l�K���%���.X;�RyO&\���� �Z���\�B.�t��#x�����2�D���S���>�x����'�k���(T�� ��aj1���Ld�l���$��*����/�L)��z"�}��R�06��2`�0h7#u\�b���DB�A$`YjJ�������	9��e'���"����YN	�3���ltkiG��n�3T�O������q�95�.�l�J���w�"nUI�/�{��a��bz��Ho��Q����i�3�&�@6����_	�}
+���epJ�4��H�S�\@������7@c��c��+�__��H�U0���I�������^u������zu1�i&dK���6'qv.�G�Bi�-��f��VO�����'�y#��F��98V�l��c�y9���������f��(q��N�&�����n^If�c~U%2x6�H��z��9��O�I��
���5������C�$|JL�������#�����N��������al��><��}��$�}���Q�_PBw���[3�xF�*�O�0����K��C+8����Gw.l��
5�_w�n���>on����
��-�c���B_��
;���_����U]���+��}����EJY��\����X�����m���*�9����'���*�Y�K�CP�s���\.��������A
�o��9�^c�]������K�����n9�n�h�T�1�������b�P5b��b�����a��[_���3�!�i�C81�&F��H#�����%R�K�d���x�$�|�����F����I	�v�����!�����qk+��}3�r!�0|J��g4s�jE&��UT�����X*��h`��wb$��v�G-��=��)*3Y���J~n�G8������[�w�J&T�����
�:���5�l:o�I$A�I��3�-���I�� ��l�w��:�,�L��/8b*�DZ��G.��������\OCk0;}�s2	%����S�>)\��j�����!����W+E�&�_�P�r"�k�`I@�����3�vP��'�����m�7e� Ch���E(��7�u��~���M��������*Y�l��c����L�;����7�u�lAF���y�A�1��S����$���r-t�4���xo"�Cq���H�1�xq�N�T�����e�P?H����#2����B=����IR�Ma
��)q$b]*D��Q6��"��_56vPr�~��D(o^jC4�q9�\��`�i5U���3�h�d���A�H.�{3�5�	�2������f��=��I���\��W��&tj�g�cjz������|c�����JJd�I����nn���<����6:�[?}�����[��r�6m�5>y�%`�|�E�i� *���p����o�F@��4� ��H��V
�Ys������F.���+�H$y���K1R�$�����z�����`t�Wb/6�ga���!F��@�`����8f���<�n���1��]���`�|�/�_�*�0�MA1+vm�O��\h0�'���y��rr��l���h�����d
U��D~�)U2yAX���
C�I�b@��FPFMu���k!����F^�%�V|��S[A@���%���o�]����Lr�7
Yh��������%=����2�Y6���%F�.�C�����������j%�H�	�
7#f�-���[�����C�:�����8�u?K�J�����-�
���RN��y�Ue�k��%�A�B��4i��k��S�q�W"�<�A�~�%1���&@_�)�����-�R&
���� `�i��/n;�\�M�������F��j:���m�4$x�.N&o����:�)�5�!��H&
��8�Z��R�Hj�r������Z���5��b��Xp�Yat�����`B�NF�����m���I����Z��v~O���t������])����l������
���B�lS�����s�Em�&"B��P-<F��rl����A�o��u1�\���- AR|K+*?�G��J�hymG�:��W5$Q|��qZX��$�uo�<]0t�#Ym�l'A��E6��o��j*��4�/-�-dC7�.�2��T�������L��
CXHc`�?Le����3KT�����n"���|$c��e���WvWFn��V�b�E���X9���(YM��e*�d*f%3e�9�t�B����GNJZX@���)r|-�$�����./�zF� ~��������i���|��<?�%��:��j�95��%0a�O��n�}��B'r�.%#�<,c!Sb�>��`;����
#������$��{?��2]YZ*�����Ao5�"�#����dJ�w��wG��F]i�9= ����AN`N�<`�'NX0���^��=��06����O�s��Q@��P�xr��hf��GI��mH���j��z�����p&����x����{������Z#1e�"y�gt_��0�����r��DyyvvhyJ�r��`���:��{c�Hg����?�m"�2���3����N��gl{�#�_�����O�f�_�h��'�l����<h���m�`��$L$�(�q��p��K�iy�
n��;Z�7���\��s�}�G��@�V38�����'A��E��F�
�jx�K/�7Gtg��b�G:��S�5��j�f�o����!��_�Ch����ty�=qCd�[C��Y���`[�UDh�3";0�����t_	�B�$\��2 r���	UA�#����z�Lz6�����vFNk$����A�!�M`�B�����\�kg��2V���}>[
����7]���+	U����[r3]�.�$���@�g��a���.l�Vg��5��s9�}]�Asa�a"L���u��B��(�E�;���\`�Rd�{_r�8�����?������;d|��U��u����[��b�x�e��B0�l���]%0� yiO�=�T����Q4wo���4�Gz�	K�'O����A.���4bH��k��x�!�NGp���=@y�}�e�r�P�q����[�����"�2�]D��!l��^��'�����o	
a��F ��	�}To�z�������:��?)IJ;lj����}����8�
o�EP�JE�������@`���G��8L�E}R����n�q���]Fw�~q6�����V�(S,�FD���p�?����: �����>.yL�a�2�4��6Z$�no��b���[��y�?L5@��5.��
��6l�m�T���p&����vo��o�&��k���GNo[�5�LR�daw�����N����R�*��*v
������P��Qx�Q�/��+o��^n�����
J=N�.�����r�|��*���g�����4wZN�������x��3��(�?Q��\�fE��<�����_�6h�#������u���n���^�#;o�n��o $�B� ���B��*���A��~1���C�����;Q(=�J�"��7v
�N����#`����SH�\_��Qo�N�IcK�fK���t�st��8 ��������aN�k������D$��B�G���;KQ�H`N��q�^q�����������F���r�m��O������?l=o� ��������Tat� ��!�bk���Vw�v�
^C��&����<�um����Y�!��-��-��;��]_�����+���S��Zd��o_E�U�����N)��v:����N[X@8�e����omm��#���:j��Ax��,4�3O_>�/������/��Q������q�����������a�;����4�����(���W���Fk-2��X���2���X���*�gvk������%0�7'��4���F���)Bb�\zPu����r^�����p^;�c����ZDbf�].�-g����v��vs����c�����X��������t�4�*<�w���8�e��0I�E���/6s����}FB,�uC��.j�|�	���8~���/z���[d�/d6��!�'�%�{$����I$��`�+J(��
z)������eE�?��v%�=��%��o8�M���/w:����)��=�)�
��=�].��i��o#qj_�A��>}��?�=��/�i�������b�B����uX���4�oO�}V
��(��N�~�
�1���Of��,��R�z����6h
�oT <%<A#��!C�I�S�d�,O~.\��a8�B�0���6�~�=��u<Id��7��]���������m�)K��m�7�g��d�_��u:����P^M9� Y�SA�)����������z�T�9�_���V)cd����Y��H�1S����8rlr�1si�p��#����V������
�-^>=t�G����F����]���1A����b�2����A4?�"��X,M\G#YR�M�K�~��s���5������tn����K�+M
P%���x�HC�?��5�#P������_:(Fe�RQ���?a��i%��eu�V�i�
�|~��i��E�� �'�� �'�B�j�Dc���
����$��~w?�t�y��D�/8�f������?'����=�>t�i�,Xd/����"�������(���+@8�U�����LT���L6����{�Q<��44p?{��� ����x�v�����(�|���E��FZ�������$���owe�4����@
�t�{��O�����PA�m��i
�������=�5R�j���Q����H������
�}��	Ui#�����:�U����Qr��F��k�h�����8?���b�"~�����
�3���&#�x�}�����:
"9]��g}�	�0f�&�M���z��#��#j����-:���e��x(�ft�06����T�!ZaN8�V5Lar<1�,���8WZx��{t�M��U@��U��dR8���3������[�a��l@$ 'B%.F=���(e/�4��6����~
c��or�����F�[�^�����7>��2��7zc�I�[9#W:K ��y�3t�)D�g��=>����q���Z�_�W�g�����g������$�x���l�.5�P�J>��L����f����F��k����&e<��I���z������c>�hJ�W�`3��2s��)|�QNg����`qXBtC�xC��<�6�smTk���
�my�[&}5&����\�G^U������"���~����#������_�
���.�@������������k�+�o���qL�c�a�3���S�lu��h4�d[)���<�	l��>@98q����S�v	<G�����c9�B�����������(]h���#�V����{�������P?T^8� SH)����Co<r�3�=�]fx��{�Z����SD�9��oF^���.h�Y2��KM?Hx�]����j��-*�!�	�^C[E�!Qr�c9��x*z��Sc	f�nG���k��wf��w�D�
��<wc�fS�W\dt���Gw���0�����Im���V;��me:<&�^��S�d�5���#��T�`5�z&�:�^�
����9��D;�������G$��b����M�k������"N�����/�
�^���������c��D����<���}Y���hEV�\�YR��_tX;�'TI�������(L����]h�8	yH�g�����$��W$�}��>
J�4"���A��|E�������`R�/5�����b8�`�	���'`�(�U�,���j3D��)�����s�����]��j<�)�d�m	k�������^0�g�@p�E����aC���z�LN(�iDj��[QH����|����4"�u��r'�L�/0������&���5K}6Zo�Ud~��LOPP����[n��}JPmY%��z�=2�&	 G���L�Q�X�|%/!#�f��VK	d��DKV�:TL{mV�=$��C�F�����p`%71_�8�6Z$��^��\`U7����9h�9�Fh�����fH	��w�bbY;��r��
d1^_Y���^�w�����Pq{�	f�5�P����"�����������]�l�
��'X��^�����n���j_E&�4��vl�|����o�������������]_�U#��������0��y��z�` k4Fo���5��_��up�ZTY�?l�����"�,��� �r�B���~����d�
��'A��<��BD��QR����#{��R)������9@�X����nf�p�l
2TR$���t��6u�����3>C0�Q}	J�����}s�����r����i�����t����y��ju���|�&\7�i����S}��
ydw�K�_w��0>�;�C�H��P��D~J���222�ed*Z���I�'��Q\!S�T!S�~��*���qp��5�a����+&��7��gk,��P"Ry��*�V�X$��X���AZJ���G�U>�d�)I�'�N�;�+E=��1���3Try���r1Sj�.vN����8]�RI���*��,���X�d�('������s��]'���}>H�o���G�������u�������5�5H���){p-y�`Y����u_8x��;�\�B�r���ap��QMQ��iVd;,����]e��A8U����NG�r�E<H�h�8�_�UO��8I*i�fj ��g�]s%��O���m(��cm%�s�0L�Q�V�n:�;TM��$�l��5���r�Y�G@<���!��PE���)c��L.�
T[���3uk)��E�Y��;X��Q����tR;���L�K�wm'���7?AL94��pu�%���K�H���vQ"�$U��AX#���&��mi�������[�=QA�U����)��r�$���W��^]�e��t��[��Yeu9�l�1�@�7�`��
�Ca�8�
0�SU�l:��^�9�G���w0�yCF����)Ch�g9�p��?��1����y�6/�����8J9y�#$���zR�����N+'6�Qd0��/f	�����/�O��`���4���I]@�h���R��-�v����A����?���G3hh��fPH��r�>YS�(��s�.:r�p�v��d>�m��<=�������1���[�L��Ce�Hp���g2���P���d����0�p9��f���4����p�U	z��hp�������z��F~o�w	��"�2�����C��wCL�k��}��8�7IvZ<�v��I������aw�������A&o��N}�w��;�����]�z�?��C������;�x!=U�uC%3|�,��G�P.��FW?�������xJ��kf����6�+�)����G�>t��J��$������7s������5}��]�?z<�p:���!�0������nI�9=�,��|ww>Z3�"���}��F}-yD�(��7}�����O��>]G�,�$�_�
�(��c��BH6�� Py�"��'��K�S�P�EE|g���}
��>�tK3��;����X9�%�}+W�1�,'��$�+ �D��N���Ubq�"Ge�I������N{HG�T�N�Y��1SSBqd)B�EK�\@`��4�Nu���B���b��/�������6�}�g�4��FhHb������i�b$�H��6�Q��6�Fx���;�����4�t����\oH%�!�%od?mW�%��&�;kdPGK���1��u�����r�����E��aQ���uU�#��=VU�s!��Z�{aeE��=ui5�_��qcKX1n�\�o������;(Q��`OZ���B�pO��WaGhf<���8��H��Q`�z�P/�u��p-0�R6��k�X�DFk��=P����>��0S;���u��F���_�;��t�('}��I��JE�g�
����g<@NI���Q�� �1ko���e�6�Uf��T,)6�X�9�������6��8�>�S�N�lH����^�Q�MN�/�;=m�x��Y���8��f��jfo}�(����T
"cc���~����}g����4/��G{]#�y-1�:��_��B����Y�g~����1����'����������.��N(���������"�����7���l�b�r���z~TmT�pR;��G��r�����o���G)�t���t@��*��@r^<�P��F��:�c������	1>�<vr�x!*��IY��32�q���@��[�����b
z�a�u��>����l��1��Z�����������JJqE�H��W�@	�K��b�[#�24@L�5}�HQ��/����i?�H�P�T�����7`�U=��(�������*�Uv�5Z����N-�L�h���h�59���1n��CE����P��G>s��${�A��'8v����ddy)U�NG�
A%�~e�P�Kl���#��k<>;;IdJ2�5�*�>-������Z�?�sr��:;-=�����������N���=������9�1#	�jp.�X�
�A��T��fDU��K��i�B�Fl����yZy_��|'���0J�?��$��(Q>�s4�"%05xq�t��r#����3�|�G���6��w-���Y����E�T��Y@H��
���0N��px�������Fm���1w3!+�+���D0�(��X�c}�-��
H����SW�i�Q�]����5rL�����^���:I��$l����{�Y��Z�`*�x
����p�#��$W�//��TI� �$��V�c��M�Y���&2��`�5�FY���������.O�1E�Xd\���WOd�	�|���H��l�qFY��6���J�!"���=�k&�Sv��ml��!������"����R�����F�/�e�A�Ru�8�Q�DzB�47��!��������������`a�^��vP�����7��Y����U
0(�3I����f��5i1�x�u�/1%p�1�(9�)�����$��C6��e`[�lH�"(�g��\zN�������l$�z�&9�a�:�M��HC��73A���,f�����",�&���FK6G��W���|�	2�B�
�y,�/9�|������Uy�L6z����l�)�zh!g@��i�zM���6i�V�aNqFo��0�}���t!in�tG��8uSo���4��R�gssw!�O]�m��z�_a�bn;�w$x��P`�V^=����H�cl��@��K��\Q�E�<�N�wG�r���*l��kbf�t7#��$-JhbR|�@�!	��d�m>�m��q ���'k���hS����j=r{��,�>��"�3����d����[�J��	s��v�vUf�.��x��>4�vr�J�<K^���G��!tp�
pz}�u�|4�*E/�2��5�o��uPu�$^)HWS�]M�9�)����<�t�!b�
�r�|xE;�p�Q��=��Eu(�V_��b�^Wb�?VcF�~�7���60� ��n���6j�}VFKkw��K��$��	)8�S
���������/:�|���l�v
	I1'4��t<^�w]�K2�����9��{{�W��<k~�vu��r��GY�hz���oZ����Xk��!�IY���Ga�tM��Z����{��;m�`�wo$���a�"�~��������/����HN
2��J�c��K�9�������%�������"��������x�mP�m��=���?V�l������X����G��[������W�9��-X�����S*J�4{�N���.�/*�;��S,���������a)��+��'�����������Z���P�X�����7�<�	Q,S��|>�u@2����� >���Cw-��;Z{Hq����19�.��p�7,�
���������r4���^!��{gR��	`kNx)���va�n���|������Sj��B��FX��P�\�=����*�����~��]e�����K�d������������h%���F/^>7F��K8�|>������IP����/�Qh���kK����^�y�!��`Az���%6�
�X��no���KN&�q�n�\}������T���b:H~����a�����/QG���1�L��|S���T�Cg�����jQ�Q 
)&��:V�@W�����;9k��G�
�o�^(��|�	�� ��e�)��1�^��o{M��:r�L�����.��������,Lk���a��������|A��b��m��j�0��/C�����B���$�\��wj�EdQ��yl��S����J</��	�(����B�@75�+�����<��j�hj�k*��l��\���%��(#�@:�O��[\����xC?��/�d��x4:yF��2�s+��������M��;w1�J�w�B��]B]���+8�jJ=��T�������BP]<�J�������3I��T��q��(������A:�.J4�� �m{�U�/�;�|��.uv��r�8��No8�O��|��(��?x��j�\��$,���u���7�]����^���
G��J���k��V9��O�X�
+�)jo��D��%B����7a�^��-B�S���zZ��@���@��O ��S�����?s((�Kd��T������Pox\���6�z�}�5|\{�Hc��_j�w"3�
�
�+:#�G�����]	��/9%�sBt�- ����EJ���U%�����56�
=a��2Lh�R�����]8���q�0��?�rQ�}��l����������)17�T}�7��A�Uk���Y6��f�������W��T{�
��y��������5Pd��E��
e9hR5�2�����������K�����N������:9s�7�g�������!�a����G����%�b�%�B^��Yz����R���[C��{��������6�����j��(�����Z
�����.^�.��.n��\�`���g�����+t����� h�6�l�>��7��!t��`����S&��:�a�������+)}q}G���HlG�L�[���!w4n2���H��)d
��	t�/�U������
J�zR��X�`�CG&�4�I�xi�����������SC�o��r�\��'o�.u�[Z�\��
7�$��Z�b�(%R�E��"p x��6=��+��=_�g������Q&l�������~���NH���������MM����+#e�Ej+p������+�|4��U ��RN���nD�B�����.;��n�S6����������N���h��S,����l
t�|(R�H���FO����{^!O-2������F���6\A�EPsT�{�mF� ��r('��O��x
��8;�y�me���N��r�����f[�G_6�J���|������;7�<;��&�Qd'�oM^����8Kq8kZ��1���������>����A(����O����PiI$ ����S��nw{��}	Fn
����������-����]��'��r��&k�o!�����$z���-i~���E�lEZ&�HE��H4��$��|����h�C5f,�q?D4p�M��6���Ez���HM���U�{ed�g�R�P�����q��x4j �Kj�Vd��Y\���v��W����L�n�N������x�����O]���y������t*4�$^�=aD������<�!��V��&��I0t�P2p����+���F1F�O<U��$_fY��*���0=b��C{qp�	�%�g�9�l�S�����fc����q�9f�qW�7�/�!?H�<�$.��%���V�\K�kH[y���Fc��L%�^/m���vTUHY�;X	��A���x�)v�����&���M���!h��Ic���?�?���oXi<�We�W����A�xeq^�-��m�.���$���}s�=���_����f�w�����F{*�g�HSn+�H�D2��G!�* �n.������l��B�Q�E���b�+H�/�C����]p��w&D�$,�A�c�;�v��Em��0g
'
�wS}��Ad��;��Ag�9�K�������[&s����V����ysQ�O�<��4�����:�x��cp�1n:%�&/�`3/j����0�L�@gD��=:h�x��n+��,�!vt����~~yz�dDF�P��W�5V�v��rF}O���H��q�y�����B-��H��w~��$���7q!M���DUQ�b�R!�:�W������U�4����:)��n�`=���n�7�X���L���	Gk&8"��z�L����'
�!~���7k�~p���z������8L<�`#�g����pk�g)�{�]���%���e�sB��C�w����P�(Y���R��e�i�t!���D�I�K��V.���2���������B�D��GP'��� Lv���*�xJ�M����C_�9����%�hTK�.`����x��� ��f��#�)t��]�R�����7���w�o3��*���`�t[#�,e��&���P���.���IX�PJ{a�q7��"�&{tN	�Hp��\C�t������r�����[���N���7��sJ�)N�Sj�P�
�����������������o�M��E����}Aw�4G�<{�{]�@���O���	aj�u��y��+$��
H�@*�<�����B����Gk}F�����)�����+u����G6{�������(j�V	ve������Q�����b��7�=st>�J���f�����:��}G�C{O��;��a�/�%���L���D��OPb�������!��������37�19�4��[�V�(N����� ;�b�)w:{�|�[�Y����I$h!�Ro�-�|[~��u=RX��
�,��7_��W��8@/�N�M��M<R��3����U�����}�5�(��%}n��
adC��^��bI�0H/	((����N�1�U�}����'�
�%��j?��f�'�������P��;�W1�^�����~w&��
'	e���w[:+��^���:�VB4�x�=���H�����9sS�e�L�[�wt]V��M�M��3t���F4��X~k�Q� wI	8"TM��P2��n#f��u��{�t:n��!��C#fi��� ���#[���:�Q�y:_W�|�R~��/�m��y��,*6C�� �"����D9i����������p�wF������r���1�@�6�{�����;&�����8�7������d(A?�����[j�F7J�l��(���#�,������Q������jKM����e��)Rw5����V�}�����c����2<���qk���� ���g�\��&�0�'}�lAyUA��������%
 d{}Qy�5�v�������s	C��1�l�f�I�l'��g����c��!�/�����
�m�
E���I'�2'�alQ. ���N3�0@%0�4���'��p���[�+#��L� �Y����&�K��c�%���.�&�9bK��LJ����x�;����l<��a����z��z(���64cVPl���Fo#��[�j�uh�(^=Y0'�t�������a�r����#��J��&x��_���F�D1SY��F���E�����
��Ad�x�.���	
��CH(q)e$��)��$��rC��)���k���n����	;�j����NBC�2}�R��;��
3���`B;X�^N8Qj`�o�������"��~�jLI&�������u���l87.@�����	�W�g�s���Z�(�i���F��Thi�'��)-�.��@UM]��%�"}��6D�����o;:��3��U�.�>������L���s23�/�(�(��'�I&��79���g?�W���K��d��+(��!�)��
�"�)��� �H*/��d@J��7�tF<��%��	�J����t`��`�F3�s:�������	2��&~����e�	��A%"g����o]7`{�K0r�T�� ��x��x�L��n*�������;�5��3����1����������m��jQ�R��,L���fG�
�T���L^����,1X�9���xE�P�x��P�.��:�#
��������l>��
K-1ZJ-��.�vJ���N��B�~���s1�3&_����)-|��n�;T�w�G��FP��m,=!L
�,9mH9���&�Q��	;o<m��F ��d�f��>r;tGZ�6�iX�`��w��p��l�K����U�v�is&�4e�T��l��G�`��R�����8�1� q3���@j����py���.o����.U���ni�Q!������G��vL29����=��b�1����]+�"�������v��w)=���
�f���Yb�'�%�>K�{�(�R�s�zc?�"��P��\��.K�+�N8s�RY�(�	�����1����B������[�z�\!!��'����}�w\�U�S2�Q�jw/_���R�B	�ni7_,�(I�����dI"uiS�����p:�`�X|�O����3�����z2B�!�3�vTX�4������d��=k�4<��v�6��d��$�t��L+m0"4
&!\A���#uJ��V��sIu$�I��cR,����	�:Qmy�Hkff,����X�\�]�Sf�H|��}�*���+����K�����N=i`�]����>>g�]���e�*�m^THJ-��[JIJz�
-�	_��!����/��Q���#%	�#��N_��i^pjw�}�L���H����i{G�P@/�4A�
O�^
�!����1��!M����:�@cVR�����W��`��U��,`SJ7QP�R�����6'�������$d����(�|-���rIs	I'�,�,���	E�99��8�����:%���@y�x0o*q1D���s�,&�h��"g�o3�#��,�p�,jD?��itnM���#��M!�:M�����3����H&�7FO(t�fT��,H�W�7Oo>�����N�K�k��]��l6"��G�����s6$6$�3V�&4k��%�����!7�����v��#��Mu������f�W~�����12�9�\T��c��-����r�\<d��R��wY1���A#
@��+��R�&^��{(���}N�	��1a��?H �oo�W�[)��Jf�� �@�T����������������7r���}s�R��,�xC�}�[���6�|�	B��M���Z'?iI��J���|�"�����	�saZO�=�2�7I�s��F�7hAW�����|�p����!�l�J�4��GB��D2maD�]hY(���g��px��59n������e�>��of��T<i��$O��1N`��&X:%�J_���T�j!%D�m��\&���rHE:�X/�4}'��,�$���}�(�$i-�,�]�C���5���"��:]����,]�N
���7Fl��r�fJ�o6�n 	��������8�kQYg�[`Bc�H�P9A?_����d���I���]��b=]i���z%v�=a�eGd�=h����$���������?r��1�g� ��'������j��Ia��2��$�\((�<����#�o������0Z�����?����dD�1���o�����S1D�#�G�P��R"?X�t�n���!iiR$IV�������Tdn%�F����8T���;���Y%���2�C���2�&o�)Q�bW;MS��=��#����R��8�#H����������i4F f�\�e�=&��Q��v�ECfI��Z���}��Ic+1>Af�E�!�xCm������CW�C'Z5B����'�����|����3gR����d�����V6'V��I����t�k�J< ��uO����v�H��8W�[v�M9DK���Yn�#\����RGZ)����_@�G���e����w������Gg����P����Y��S2(=�G����;OdP���6K��i���y��^=����W=�~/�������q�r�\T�8�
��a������#����h�����]�d�v��C,�$%j���	`o������
�������]%���0��_��ga�HV5%����kZ9Y���6��)��-y�������r2T��	������b��o#7a (3��v�H,�y�m�����3�6h��
���Hg����R��r��}j6#�����h��~B7�LcB���'8�M�����f�W���L
x�
��!�26������"A�����X���c$���^K�}�<q��/g��q� ���a'�>��S���;r9:�V�PsC|����s���7t����Kdla����E}_�����8�����M4�$����ZS������>G!�&����~��v�W3'_R����R���7�KE`��@��n�5����i;q�v���oD#��"2yy}uv][L���'<Q"Dp��s��
��xMx�i��g4�78M��jtf�
���v���~W��d8�Y�Y �v��t��;�p�	����qH���6����O�VT���5
jW<��1
9���<(�
�������Nmy0���SxD��6\|rx�<$�\�`����x���
n� �}6���h]�]�<`���X}���c��qN����n�
�N���1r9�q�kE�L����W+����=(���q���-H5G�I;�����Y,����<�Qmal|�����j�!�VH78�(���!jg'r,��,g8���1����tD�E�����/o�����8�o5*�tIQ����
��bQ��WE=����h��R��V��C���W��w���#s�G��h[z�0�	V���P|�1��2y_Rp{VR
-�nW\��j�m2�+��m�$���0����/�0,LA��rb��������H��%�qC\`'�W���9Z
n�z
UPq�]��A������������O,�.��Ph)�5�������d�`���)R��J�%�����6��5lc
5!���������DCa2��Lx[�"�	���^�.i��q�*��������+V��W�����������T{{[���^�Z_����{��}|�`
:�o��W����v���&�_
A����6|��y*?G�$bw�����N���o�n����U.�J{�=���r�/���Yc4��������O/7��w��)�c��[��h���M{ww��S,9V�]�;(����mwvww@c�kkO
���(��c3��7��8��0��e�>������[^�����1��Cl&'����P�X����_� �
�����������*��w����*�KV�+u>rL�����:� BFu((�:��K�Y0���?$����YTe�ZQpP[���fX�����b��O\`�Z�0���G�'�� s-�?�]A���J{�������2���x����Y�p�����_�pP'���~�s���3�B{Y�O�_y�32F�*��q(���p�`�{�Ko�
��7�!����z#��7'�9t�|H1&� �*�{� -�u	�\Uka��UL�.��9j�-C�O���
8��:���B�g������.b���`�0���j�.��
F$��0����Y:�[�}q85�#$B���� �'S0:X�P�,f�<Q,�O�,qA�����^���X�9Z!C��(Tp���>>dB���������?|��uL�h���yox�	���%���Ekw���&%a8��a};�8�n���t���!,���K�{��Nb#�
Z�v��qu����>�����sFc�P��J|����mK��[��VX&���Px�O��v�����b�?G��%r#�3[Y|�xL����L�����VBf��;pZ�@Q;���sv���n���=�%��=�f��/�>�/��G�}�����qk����O����������
Z;\��"x�\?t��������g|G
�;b�,Py����g����^��(���m`T���3�
>m���7�`Q��g�W{�A:������E�H���CH`�����i*k7�:g�������=��Q���~�v	f�;�%�B7f��C�bw?C��hTz�'�����g��W�s0���"��hu�G�H�T�mN�c\����p
��h�gw����Yj�<1���m\��H���n�������r4D��0��l����SC��������g���g@h��PaC��:}��l�>�WOX{xAK�h��V�:-�JYP��|,q.0�U<P�kN�J\�z0��a�k����|��Ot��������^�^[��j�h���w0:����o��w�kI5���g$]oHO��f"�lY�-'����@���UH9��n�.��-��&�C����o��[�-�{j���J���_�f�\]�0$���i>e`�.�;XmQ�p@{���|����M�2�p�������j�M�R�<�6N����������lNW@#i���l����g���[�7�Dk6�YB�-�O��!�x%`j�@�r�����!��@b�
�v�:�m��
~�8x�KP��%(rVq9k ��
���p(���WB��?���z�����\CCC���zEl�x����'!$�(���5��5�l�,?1Vl)����
��LZ9E-�j"'h����-aeUc�$z-��b��;���G���7��
��h��e�7�`C�H�$�<U@���#�m,p|AgZ�BH��}*�4
�Dye��s#�<�9m?�\o�����F������g��?�[[���y���z1^Mk��]:��&�A���&�r����K�{w�|K�}�����%m���`��I_�A����8�4�$[0��C�1hb�<'�G���ut��1������%c�n,�m@x���(�C��~J"[o@dG���H�h�����Kb�*��_���*^���l�u�qMH2��J
m�F�����X4��8�"���uit��k(����{H/�m���zI�s�==1v�1�5B����&@��������}mQj�{��i���=lhI�����@�6�l��H�q��B�����F��]����Lt8� 3�)�Y�Z3����1����RJ��f��d���r��x�O�sb��O�'�� ���)�Q��&�,:��7���<����������1H��O�(��d��E���^=o`����#7����F���\��������P-�{�������� ���bD�E)������>�b�I����L�@���V�g����1���,)E	�p��:h�����i�����i���F��!M�|#�d�=��F��by�.��tf!�����������c�B�3���������%�u��R�LxY.r�����-~��������x�l��,9u���
���q�t�rm��;����|5~����5�������m��2���Z�L���(E�|�)mq�����mm���B����p�\�D��)��]��k��'��7b��I���[4Ca������M$��J�J �)+��C���7�~
������E�r�k(n8q@,�`��Nj)&���AcX"�{?�%�B7������	������A�T��"���u��	�*���������9��b�14���p�2�K!� a���^P��n�7�n@�C�D��A���1�,*�x6��5�A���]���8���vbv#�f�n�	�$�����G�����j�����n�1�. OM���~g�`?�/��K��n�x0��i���C�������Q>0OM@�u����T�ViWO��F������*��~i�'�FP���8Z���u1M���W%'���0GEr�s��)���G�C<�2_W]�q�!W
j-�?�c�@�s^u	M��$:���G�F�����������g� �1A�0c����L��b����A��yQ��r�:�+�%��c���HPa�C��M�zq�i�������}�d�=�?bc!���P�=�ot����^2Z#�^���%7��y���q���@�~{L��JZ�fq�p��_E;�~zQ�`i�U�2�j����8��1N��P��^Bo��$}$\���;�e��U%��wlKm\k�hG~Br�X�>���Tx��C&���)mZ���_��M�
�� =�g�mM����W�R��P��	�%���xx�&�a��qC����tB����tlP�x�9���i9�
 �3�[�n,�Q<y�A��W�2+���}$�����J���9�M��%K�xF�i������t�@�
>�v�����P@!�����w��{����'�k�<����������q������C���kD�����0�(z���<;T�<a���EZay��%�XuMx� �L<��7V�W�[}F8l�I��!		�K�b�!�h-uY�f�n$�t�+����W��D��6d������g�lH�:��K�K� 'z���C��>�Om��!"'����6�l�g�%�]��$<�:�����X�yI�@�T�#G�N�
5�l�TP ���O���Y������~A!i<L(���Y"2��I���
5�s���w8��xv���-���V����7���a���f�/�:r��H��#$%��9�����'�gn<��7���5��qo� NK%���	��c����e?� 9s��F���x�~M\���nY�����G
�P��0��4��4d�C9FW<�u��1�-��J"���.��G�a0�'�H�j��ShEu"����P���'��0a3p���������"��a��}�x���Q�U��R?7���������2�9�^w�J=�TB���=;.J�Y���U�/3�D�G��l.��m����Q�vv�����@�&����B��|��$n��G��=D.O�E$�-����A�
����������#X2�N�M8�,�k��X��<G�,���b�26���X:8`��������_KaO�D�0��5���\{�B0*�d�J������y�w���<�#)��9����n�\�����>�$@cZ������]����5�����Q�z�8�P=�`d�Jh���_������������4��*A: �V9����S�6r!DT���L/.�kw��JaY='o�$w@���w�I���d�T����!>�:�M��B���$�H�%��y4�*(	R�D DU@����3*)��vv-B��R�[d-
~����X��f�E����B���@��AK��|��"�iV~G�TD�#��$q�9@(vwr���v��}p��<
��$������:�y�O�^qz���q4Rg�Z5��9y�ED�����:�d�g`}o���!^��R����\��`���BO�4*�QrJ���/f�$|r?���pVC����0Y(�j���}�Y��������%7")�)�W�d��� �i]8#��;�o!	���k�G2T_H�F���B�����.�w��4-��>755S�
+e9V��#�8�r��D�KG�	:V�y.�����H(��d
�5��F
g�S���_,�����E��#aX�DsQ�m)E���	�oAc�_1T�Q����X�iu=��A3�L���������(������a�}����3�5C
H������:a.��`�qQ=���(���j�x��P7�����C:�b0�
b�����(�����c���c�qx��m�/�8;��dY�?�E���i�oU��Kx��`�hx��"�?-��,h�X��YAd��,�����T�
m��X&�Ppk�c��Z
���u����EI>��������A$lZi���8 G&KWg�Y���!6���c{3�3+*��������q)����#"
���re��k#'�U�u���.Y���D�o��� $D�"J^���?|��g|��dm����t1Jx�#���#B��=d6x��"{�9wM{8t��)
�?���?���e8�M7���2�J���a2��D����o���IodC^q�'�� ]��l0i��4�k��p����X��I����I��N���6���7��9$�&l		:��N(w���v�Na��3���?O�����9����G2�Pp �2����:�P�(��V9���O�����:K;�E�2��A��-b�t��h������B=�YQ���A���u��d��YV�lV��I���Yj�5����7!G�m�E��e�%�����bKz� hr�1�:�p������&Y/�G�T���	������v�����c*h���T�~��j���n����
��X)2D��#��k��c��r_�dCdV�3���m�NR!���v(��8U�#W�c'���t��9��)���$Z��yd'����C�8��>������q�+��Mg��q��������$��0�p���4Q�����di�����U���j�X��i���{n'����H��}{(��`�uC	Y�M�T������Jl�F��;�i��!��g�iFG�[�s���<BvMH��	 K�$N?C����y(�wT#�����c�� ��C8�sm�C���ef�����o�� �;8XF.mN�8�L�$�H�������AV�(#�0�Qu�P������hF������L�G�2&�'��5)uXC�v����86�J�+�4��}�!g��h�L~����p����J��C��dL��
�E�c��A���D+I
`� ��P/g�.a%��������]���*�'�4uF��RBUZ�p�yY�����B�F:|+S�2��:&�Xs2/�A�K�7{A���h�a����E��C�8	`�����p��������%�&�y#S���H�DwN^�����`|0V�c#�0g�[IQ$��R$*2��1���Qs�D����'v2r�(����9oCiF�J��i��oQS��}����[����%J%Tb�)S[�C��$q $#$����F�!!2`o����|-��'V2X5z��Q�7�8����E������W�w&��`�m,�*�Lb��@�
e����_�-f*�U�lm�+�
�|�?'R4�N�a)�!�2�65x2,0��kU��Y
��6�8a���IaZOOz���5�$�E��IT�e)�%���_����=A�
�`$.V84�0�9�H��j?B.K�ZC[�G���"��i��s�����D�5�
��Q���k��3Me���b���^t/�]03��P�n
k����q��l����c�S@�	�H_����<�g����,��D��yh������|�c^��O2��J�%rb9��LjOj��yfa����q�8`*9?����D��[S.1�T�c��Z��
����cQ�k�kn���rx��A`���!F��>���	�2� ����@�[�FZ���C!���T�k1�D(!�k�������30�D���Dg�5&���L
���$�
t6��T���_��S��

�������{�y�"�2`��E���J$�O_eR��2�u��:��'�j���~��M�'��|�jC��"
��v��%:+:^Z�k�F�<��bO^��\����d���1��f>\��t�K��E�yr��w��'��"�1�G���I�M�P��r�f�9��VXM�?��y���ZNo�
 ��C�_������a�@����p�����dL��c�0�B�)�{(~�C�j�L�=�.���iyW}	����z������n_�]G���J�8�����������@���<�t��t�H<�
��Y��*]���KUq��^�F�)A8i��n��u��9$��,��I���F�\�|��S��'�%Y�Y�O�a�\{��������.���KwT )��2h==dt��/G^��Q+�UN�9m4^dRa@4�PC����Zz�[��+��`��
��!�H6�l*2;���������$Ld��W�iz�*�@�wn.tP�0n�� �J(�U��
�����kH��d{�_2�br]��0t��@��E<��vU����|c]��^U��`���*�VtbbRpg4%�v�'\����^��z��7U���HU��G��k1
�f@��r�M�hg��biH�T�� �J��`.MJ������R5z�Q[X$"�`oe�����aH��GA�2r0Hu�`'�p4`�+����$������i72Co����p��9�Ss���Qp���C+*�a=l����5QB�Q��(QLh�8Nh|���G��Qa�9}9a�����c���Xx��?�7�1�t��`0#������\C}�0��=&b�j-��i6���+2����S�H�z!�������pr%Y<Ig4^�*��R!�u��G�k��>���V$��FB�cLn����D�!�mn����\��2Pg�%��j��������O��cPB��*�����G�.��q������T]��C|�]�_o�V=t����@
v��q�A��������:���M�"oH����y�g*
\r�����w��9=	y���fH���{��>u��2��#F��o�3�1�)?�u������"�OJ��s|q��j9�Gu�<�@��#L��3��+��XX�V
�k6�oxa��:k����#0�H�ah_Z���P"B�'FN[*���&#g��"�� 	w�"`CW�A�mT���"���4u��h���&G�����J�3�����D����3mJ��`���}�5E��!c9�K�Xu��Gl��_�O�"����%����Y�|�����b�O�`�$#�T������t�FD�2�f�25��d���
�0��\�^9[���N_6���v\�`d���6�Y�����z~Tm@�'��Z�qTA�l���N��}u�h�E��V���<@m2��%�_{�)���������:TQ1��UE���~d�L
��0�)x�xU�az���W�0�#V��?����2�������hL�>�T������`M�B��v�������>������h��	�$�����D@�x �!0.������FN�.�3����Srz��z���v�x�3�[��-�G�������U.F6&:��m %�}��X�F������Y�
��I��Qd$�6��3!K�z�I�)���
�!P��s�r<�r�� ��"|�W�k0W�T.O`����6�UA�b�Y������k�=������`�/(��vMp6�0o�@.}mx��d�wJE�*�=����RQ�UT0J����g��Id�l�?����K��i��0��@mg�20�x*�"@�R1.O�:�c��{���|�7~�6�S>@�#H���Gt�wF�r���n� �����`�X����9*E��G0�	����Z�Q������_���,<b�R����|��~�9v�������	�fPdc-��O|E5#��`��[h�zK�q'�1&
�����}���3Ony]ip
�E��^ ��� iLmB���'�C�62(�#�xdf���S[E�
��Z�E��h�����FyxLt�%T=Dck'��F�p�Zh�]���nnv��ruQk��M�����}W�������Sny-i���d����[�����c��������9
��B�(o[9^O^��NOl���Y(�z��>g��S~���,�D!�3�I�P$��v��.T�k�D����;�NsG��x��v`(}��$�D�=�	��	~Km���<����,[~�kun1Li���r-��c���:��`	��k� ^��/��"���(�U��*+�<qE�3}��eN�f4��@�O|��'n��6��v�d[�,�������Q��V��|�#g�mJ9����lE]lb+�RR)���"h���E�����A%h�v�����e��,R���h�������GkY$�TV�$'e������2��h6'8�T�VF�������;j��+�E�C-`���U^����9J��c9Lr����P�'!���4�2�d$�GG�b	M�����f��YT�o���]��,Y������?^���KK9~��dNDsI�d,�M���2���[7�.�����)Y���!6b�J���IA}Gg�W�w,�D^%�(��)T����+�����E�S����&�O�Z�H�~���6�	���f~�����N��Z<�;?aR��u�PZ�g�������!���T��KL�M�~P$-�S/�H������2�p�N�_�r�D���
�:�^�8N�#�"�n-5y�Y�E����K>2|�Z���'<�oc@bQ:2b=
�^�%���F��iU�E�p/bg�%�<j��?*��([�S�����xn�u�]b�@�E
@H~o�!��N`H/U�,Q@�0n�8���Y3�����FPa�����l�T�U�
����
��C�
Ar�lz���H/��O��q-����lJ	m�bY��}��%����r0�>9m������~���H5����az����9���[���]�t�htj:���p,z�=��������N]e�����rB�j��bk�7�L�$WF��~��27����_��t����~�����)��'��N��
g��]<�H@�@E���, �c����mh��|�A]�z����vn!9������K��t����h=��$73�$o�D�a�^(,/r���A�1�������S�H��p��9!��&i��H�S9=�H<qZ7O�0q���23��"�P��vQ���7>��u��S�q��D���(j���eO=������%��%
�%����/���9��K��Rdt&TA�Q�c�j�N���x��n�[.��H���p��8�~��}��J��E��9�:���	fpx�g
�(�MVQ�a����AL[����*�T��gz���TM��������	��p:oRJ����%��[IZT�~��y�����
���@n �a��y��������<c��M�a"4%���x�&l�u��ty�� QV���}�B7����Pzj9����192
��d��R����l����b)8r-������L7I�W�8��0|1�=�1�0��`�|�������4�p\L�C��p�:%�������ooO#�������Q�����j��$X�6od���l��A#��h������=��uW��L�k�f$uUW�����N���m�w���������k�����[	�{�j�_�]�"��2pe����7wo�~��5��~�n�0��"��c]NgS �,�!9��.[�(H�)��9��-A�����AK��r(<"(1���7��^���N�4ts�t���^�� �����R%��	����}�6�}����U��Mj��*�e��9K�T���	]��
����]����=rS�~�Z��_���u�+�����)�6�������]��m5��m�Fz�kJ{��Na��Q�U��wFm��j�~��	��c-\������O���
�i�~���,�����b%�>��y�������y[�l�Dj�OH���4�� ����2�0j� �����K[���G����6-��e��kw��Z��>}����&&����MR���Tr���I*���S`
���i�KDG�r�x���a'5[?�M�.CG�C��~�*�����*�>b��y�_�j�_�������Z�-U�J����}�w�H�&�Y�(�����N�\>������qoa���h�=��m�?��f�^]�
h�|x��?+Ge�g��{��w����~���wT>���������T�_1s�c�����W��8����������/{����G�^�;>��������������^%::�?���-��-���#U.?��W������3�3��:�^!��1��S2��IR��������q�L�� x����)D�W������*O�V��\^o�������f�P������)����
w�rv�������8��\��V_�kM%��8J��t2q�����$6'�(�-��szcL�5F�Q�/�1Z�\��92\d�0����6��Q+����!
U]��jM1�^����$�d1�5GL(����^�^5��u�k��Z����N]��K�0HE�a���X[Rm7r��^�leS)�bC���uJ|�Bo�����8��[�tH�7
EB
������i:G�E+�em�
s��P���.�E7���]�k����hmykHJg����"��u�Z#2�3��	�y�)F��!n��8����*�#n0��w�����XN��zG9��NH�^~s���z��s�����[� �N�[�t����G�#\(������FP�;W���n
U1�I:J�u��6���mC&�I��U��~?k�74�}(���IZOg7���g��������
������
���y����v���{�c�.�^��B�'��%�;��U2��\v{��N���y|�6A�f�#�RO)�O��:P[~mh�a�vxn�	����Ow�����9��n��"~e���M �#P�rX�s������m*���8�Ut�����;4���I�J�6qt^�E����ozam���*=��)��h,xk�������?O)��TW{��@����ngr�A�zdrm_�������qmk����|���8]�g��I�z�w�v��'V}���[jR��,��c���7��I<����Twg���\^g�#{��+����GQ��?<�>���U8��}<��|o��J�$�UJ�	����u�{�;��,c4����7B�H��WQ�T/S7���p+Ph�0��=�y��L��5�^�$�!���Q��	?92�z	��p���US@�5:hWgT�9�g��gW��j���^G�m�v'	�&�G7u>��_������?��k��
$��� �p�R�X��� ��@#
�k�1�~�&�,��*�W�p�+��Ih����O��������0������������/�,qr�bj�1*7aD����N%�'��j�J@z������az��g�6;��Y�?�sJ�>P��t_Q��>������w.���S��EO���+�p
���n��v{��0�R��j�	�-����-�WRgP�-W��t��Lc�H?)����M�}p���_g8��s���p�hN��I�xH�,�&�0�s���&/�h{2��%�XoT�D�Bg64����)t�f���}8��&��lx3nmG��r��o������kfB���W��i�e&���Q������p�4���-*�������t/S��v��I���v�������������>���p��`�?l���q�ZJ��s.���������R����G���qw"��t���:$��QL�1�
)�%6<.K��:M�|����Q���(�ak7��d����Q2kr
���P������$��Y?�0.��
f���w9ox�i���H����
�x���@��C���/^������6��B�]�M(��l�x�5���������&^g7�	^�O���;��F/�8�`v�O�gc?�X"�����R�:�Ym+�h:��8c�������U,p��/�(	uX����������C<F�7D�,�'���S��S��pUz����d��/�q<�d��b_e���f� H&n��r{c�����m4��������w�K��{(��k�����!�&�w��ht�Fkv�	���`$�I<�$���U%V��_)�V���S����~���o��Z��<�wd��7p~��j�#���V�Q��ys����.���W�d"���-�r��`��������3
F0�}���;>LRh��A`(���s3��W�\�����/^S���G�t�h:�A]|z�8�u^���w�Z������]\4�����A�����!��y)%9�QY�K���1 ��J��
���p}����K!� ��]�����_��Ice3�$nM�����v��xJ������x�<g���"�q�.�!���2����T,�
wn���:l�MS�$��KD�&�,DM�*�"Q9��u������sRsK��E�Y+m��;�0��c=M�@�2S!�#A��Tg�E�|=���&�*��Q��f�c��x�t���>F�o��w��C�x���M�1�.~:M�df��G! k�h�*���q�����o~���x�#����Q��k��Z�>���y�nC���,�����'��Ko�����e��l�7��-�xb�oJ�L����	{�=�4=2
����#?�&	..�	�$MkQp�N���2�4��G<����$N��E�[�r,���o�%u��1���O�x�8����!*�	0�8M�Y��1~�Mx''�{�������|hf��q��RN;�7���-���K��l�T�w����ms��j5��O�*�5�eS�y�:�b(��� ��!�O��_YB��� �*F]��he���0P�\�n�����k4'S
2�|"����M��{I�5<r�s�����A��qr4��M����N7�;���������t0�
���<%'/��=1��[9�u���P�R0q�EH!���������uRh������.q���ha�����T9{�\��T��+���\<N��U��n/������Q-�bD�@Tp)J���4>�_�M[��G���o�"���^F��~�i�K�
�Y�D��e�a<P������="r���,(�\]8=$b�!�������NO����y�(�������.�����AM��\��0z�|$���o� �f_zLCF-���!�4��_�����5�I=d��<fc�����+4�����UNy�|��x�v�a����x):��������r��(�j0�rj
������=�����
�A�f��}�$�_�!�
9wP$s��P�+^2�1orWkc����\k�5�]g1*�^��W�Wc�L0�^��w���9��f
�"}���N���qqOm*��YA���{��N#�}��2>����
0��RI��7�r(�=��nK6~�5)��J�u�HT���K�)t�;�D&@�)3Z��K�������$u���D�d�����)PQ�'[��_R��\vp��#%*�s@��It�U56�_p���R=t������$t�U��uQ�>�H��\}�'��$�"F�%K�G�����fg8N:���
�,[D)������/���Y�jN��h������=�O�y=����yINS��*��f����@�S���#��`;k$�����*�ir���'m���d���L��x��)4�"�b�.���|��G9T�=���kBd��yE5��C�H���~�Q?gl�����OY��U �VZ��[���

!�}n�?��	'`qE�KkC�{�ni���'uJSc�d����Q���0�&iw�J}�'��Z������E��J��)�C��y#�2\'�r���|)�R��+���z�R�"���s��&��{rs�����O�K�SZ;��7�L UG^��;�����>�K�)a�G��]c\x$]$�&���d��v~Z;?���zc��|�%���vo�k�N�������&����O��-�A7����tr�;bh���(!�V'�������
��|��g:$�=suV��*�����E]cs
t��h��4+:C��r��~utU$0�5�z��}o3�c���<��Z+�l����p����A�9+�5���T���_��W������P��3Q��m8Bs������HqH�X�v7��?��i��^��5'd7��1�S��/�����W_����z{���U�������&�h������������q?�@�_m�6��IH���!���V��W&����wHb`�����8;{wA�pN���it��Cy�YcB	�p&��yzw^o����Aj�N�;��Njm���1�tr������1����6��f���>G��WY�i��J���C����zSm��6���8�d�&gLY���
6+RO9���D�T�>y�{���L��V����C��?,���vFn�l���(����w=�?�,)����t_cZ��h�������~�Cw*���jk~�I���Y�O����Y��j��>���U�D��z�����C�/<�x>9t%��S��{������zx$�6��$�b�S��u�}o���
iv����f�r�3V�.�������N���{3���B}?O-�����#|k3�'[��56���9?Z}�I��a���I7��M+�s~�/jq��x�w>�������m���e3[��4���t,��Z�=%�v��k�jc�g%�#Vk��c�Iy����U2�F�J�\Cx���25F�3;x�u
��������z=�;7g�^��(���C�ua|��B��?K��:���[a�nj[��S��g�i%���O	��|4�h�
�$��D5&<�����T��y3��hp1����O`z�����������C�k��b��G��PE�+��~?2��N
n����/���oj�F��
��������	���@�Ih��s:zZtg�m�5uM�^Z��j��M�`f���o�����`����g��/��G�>�^]M������}P��>��-�)�m6���_��#t����e�m�}p���x������2Mf�����!G��
�08�:D���_	X��� �]X�Ld�5�Q�>�j�91�NS-�8}
Lm����y���I�,<C��b������3U��Q���l~�hQC`�]l�	9,��M��g�*�L"�M��[tR)�|f�AP��	��-R��p�C������co���sAR����p����p��������s'�V��������5�u����_����M�:�P76���������oF�V��A���_��z{��`.�59sq��Y��33K1���7���1��a��
8�/�W:�a��5/I�t-�|Z{U}w�F���O����ED��s��60�p@6��G���3������=�������^�A�4�
�Q'���9��������f������G��Xh-�������W�4n(����z���0q�\����/�@%���#���I�B[9'�~}�j�sB�v4������&�(�����i���U�=��*�/�e���VY�HTm�^����	��!gXW���UI��p&��;����jJ�S�+ :�q���
�	9�.(b.�����j���������XA*J:����V������W�B��Y?v�I$
����f��
lX'�����~���6'�p1Jyz?�sl�*���l��zF�>G���g��4���&Q����P�7QwR�?�\g��-������*����������[r����'p�3�Y����\����_������i���+�.�"v����v���g�V�1]�z��aoav:��J+����I���E7�*�3�
7�>��7�m�����Y���=T�mX
�}X2��f"��������l����j=��H����[WCY �w��%���K,�����V���Hq<u�<��,�_VY3�Q���A��c_;N"H"!�HX�C��i'����Pn��V�%*�����;C�l���"�U��w@(5�>/X��� '�A?$�}<P)�n����������=G���FOR�lOw�^b8���C���i�I�u"���j��S ��'�Z��� �����������&f��K�
*���Z9<_v\u�����J���.*�����1a���2Z1��M��Qu����L����J+m��n���d�|F�u�5G��Q?�K����t9����:����^c%�9��<�KZ��b��yW����9�+|�@N�K�;�O�im�S9�����m$�vA�i�������]�8S�N<����6L�����y��].;\6������Na�#���N8��rz*��5��D�d�@5�C�)+n ��:QM�e����tP�p9A�P��������L`a(C���#c�sP����D�gnfVZ
S�$��#�!a3��Da��I�U�	gN���q�.'P�Nr�|�H����t_6��|��^�
�J<!���w�I�a���M�����(��m�K��%V�p�C�A���)0'��K����w=u������\����1�\<�@k2sk�ry�g~�.6��&}z����*�>J�\��c���h$�<9�OA��OK�`���n4��r\��*���J���$�rv���������-��$�di�����Q(x���Yt�!h��#�����-�Ef��v������$���<�L�pR��|q3m�������e��"���B\_2�-�5w�b��;U�����h���������e����]>]��mj��[�a	��0�]��t_�_����xS�v���`���~v&��Qr�	&�|xP�?@�W�H��b2����r��b�@��N��C)��~[�j��P���a��P7�&���3G�*�����a�`�9/A�;�{�z�F7�1WlaH��?�F��������lm97>d��T3=�����u������+?h�(����Q��D��k0���R����U�=��Kj�c����u��Y���t�w��-���r����d��.g,�1^
�*�O|����/YM��M4N��-�
0��+���p=�{�P��@*;�>��5��M;�77�/Rk����H���
���o�S��R��+iRO�-�x��b�u�8G�l2�/������{E�	{p��g�<�\C��\{����w?�-���G������*	=lC.=[������
d?/�u*��"��#���{�(s�����j���r@��U���87�����o�/J�q��:�}7�H(���eI���@����h��������9�?�)4�������X%�G^j���=zq4�,I����2���1����Edr����L-����9�GF��#�QyI��#�2��L_`I��$��$Hv(���q��r�������~��Q�;|9�����q��2�������N�F4�TW������c����������������U[�:��"o��5iO�Z������I�?�Q����a�W��*�h��o!��N�����Q�Q?u2�����c��
�&�sDb�S*����?�>����3/|��<��>b�Q;m�*��t(�,pt�����1p�����@;k=iH�����/��uzN3y&6;�m�j4���?+�CQK����JE+,��zq}H��s�h^��O���<�'����:���:B�����7�
Ij���,-���%�=���;��z�a;N�bgZ8�1v8���{�r��fnC
`�0�n�M�r���/�:����wS�K�8x��_�����gh�Bk��9L�+bXL��]�y��4����4�h�
�� &e���h}i����M��R=��u����q3��9���%B�o���	�y�G�kW;�*z��F7l���i�.��fRG!��t�q�`�[���g��
�,�TIP�*��
E�)E�N�W���I7�*{���M��}�Ksj�_=������Z���~��_�u�������I�������5��=�g��T��[��j�_��o��xL�\�XG;�O8&�<�u�����e��`��_�a��;���cv�
0N�����;���N4��
i�t�[2���rES�Cc�?��b��U����Cg_�;qu'��Cw����*F�<t�l�����-���V6���YC�	@��4@��L���������:��+��i�l/�a���v���j��=��:@�:�i���;��"d�K�����y���m�s<��r(����g���WC���!������������h<e�}���D��.D\����R�[�9�^@ip�)�2�NQe�V���!�{���1O_�c�Xy�pf�f/���`?��������~�6Zvq���I	���`!gFS�*M�q�D����C��B�w��$�����s�����$Nf��j�w>d�m`�62�w�}����N� Mg����eLq��)>1=�9s�j3��s����?��0'?��4��a��#2D�
��JW&�����yJ�|��a8��>����+���������5�)�����l�^��{���
}�w�~y�������!��{�)��0.j��^Ecd��N��
�#�/����dF���������k���'��H}<�� ,8�)���$f5��'������;��)$i$�3����V�!6e�M�u����8���o�M�jS�]��9��D���\!{��nE��������N9����Fr���d,�m��Q>�n@n�&�J(JfP��y�h�����$��5�L�d�{�c�y��n4�^��@���&���6�"�����D*���>w��u[^�b������"����B�p�*)u2��`��e;������I<��"+�o���w�����d��n0���3>��94q_F�.�AR�rS��a���i���0��_T��C
�$������	��Aj�%'���xrW)����0���LT��+�?���Y�z�M��&[L=�v#��'j��$	����~.��j��y
�D��^���v��I[�]N���|�v0�4o��qQ=���@J�zn��"i�[���)�2J;��pv^�K�ib�bb��V�;sJ}�'1k�;�X�V��n�e`���a��&��rF1��M����j�Zo�f�a�'E��@��uBR������~4O�/r�C�Yf3�`����k���9G�q:G
�m�/���4����t=�S6�>��=������\�Ik`�����<�KL�d�]6T����1Y�O#�7�.�07QI����8��c��C������Ce�a��Q,a�n(�O9U�c��ip���Q����X9�C��"��/e������i dF)�(����H�YT�x;'�������4�p��va����������J���v����M�d���'����y��gJ��c��x��Jc��ctT���YL�5������H�)����y3"�1�
6�S������a%u1�ao`�g|�N����b���gG��b"���h����+�;��+�%�r�#�y���^[KW�:�y��(QP�M��\y�*U�Q�h�&��^��E�����������T��?�����
l���������5Cl%R���e2s�I��c�AQ�9������&V:�0:q��n���p���k�����S������,������\���|Ex�O��������:������Y����a3��Kx��:ux����UOU��|��$�&�
��\���f)"����u�Q���`�����������|�Px����,rr�i����������bi�����:��&�w']�����s�jK�1nK���@}��n�7K@�lx����w��@���H���^��//G��t��j�T�=�#j�;���TA�����.��tC@�-]6f�Sk��PNaffV#
���w�mA��5��Bq�W���3�����������D�^����T�Fd����r����o�PX7pC]��K�,/�Oe��~4i�,�����stG��q�K�+�1i2q"��'�g�����h
��=c���Q���~SG.�@%���%����w���v.-^�����	b�}db&]�~g$|���l�	'Rh��`T�|�6Q��2gXVjB���a2]>�����vg�Q/VxS�%�*��3�,�k�z��������;
��B~�/�VF��<���%D��.��_FW�1>,������M��$!�V2N\����G�@����p?�:��;�����Az2�m�Z �;�#�	��+J���3���Mtr�t�$�e>���������ZN���F�����m=^����6F�E���E��]T�������F��'M�L�=��G�!�%xw'�MX�f�����L�O�EuQk�j4�rr�z��C$��&�����'��ZC�J��g����s��=��B<k8������`��fG���.pc��~�����0��� ��J��4��h��6^(@���[r���{�
���?}Xe���e4A/~��gF������H���a����o��5�����r�pdja_�1$+=��`t��Kg�j�,;���������spf��c'd���{�9wqQ��^���~��,l���l�N�������` ���6y4u2"c����z)�i1�c����]/�\�����\%�������<=�3�Z����O��4l8���#�rp|�$�D���TF=T��
����l2'$�x>e]�&�S>x��MBD�T�}��;�<�>������Ir�
�&��~�x��\�Q�f~�^�G��3�!3�P���:
�('���YE�4���h�5����v������K������E�t��5_����
�g���hRR<���8��f�),��
�o�������%��L�
X7o6^���\�����������,��a��J�rP���@����U�f�B����b�w��Tz��fJm�3�
�.[/�'
�����9)6R4��?���M�>3��9�����ly5��}��n��}��[��Q}�!��@�0).2^Q�z�^�����������>��P��{�4W���L���Hk��y}:D��u?��>��V�D�f��0E������C������D����'v*J����h
�NAi��$T��q�D%������~��@�m"a����EL�Jz'#}��'����3���`���A������
�;�����h]m�n�U!l<	Op��@@n�����o��������;XF�V�\�WN�+�r!�%k��
���������z�T������&3��|���S�{k�V^v�!��fBz/��M���8���uE�g<4�H|YQ]4[���y_m�7<��I��� ��$g#~v"��P���?��v��%{S{�6=�4�����-UA�-�[O`�������W�v��u8~��HDS��-|�.j3���K����m�];-1�������Id���:���X_#���/�
t�<0�iw�D6/3�f��V�|���X7�W*ba��VIw���!�u����~z����&�����V)WuL��z��z�D�O�s+&�7�>t
��J�3�XC�^E-�h_��h�6%,F�a�v�JE�$�7��(w�5�0,��1,�J����,�xd0��M; �rFE��T$�0G����q�g������p�,GB8���%��1�6'���^7�a����g�N��%���y=��>��R����4�N\.���q@���ug^�
��BiU���A�.���7�&}��������h� �y���T���P�]T����d�����F����gL���,GB7��nXP}�U�y�Z�^4!�?��q+h�H���_S!�.�H�CWN��{�=�M�j������i����+[�sy.�nV�h��)�d7E`���f9����{_R~z��C�sxJ����O�^@3r�d��6er�l�G�I�^U���]�z��Q�E\�s��+i?��6^���!^��`^�>������2v5�5�`
\�X�\U���7����� ��K"����p-�P��]�+�1r�o�S���I�r�I��������5������
zC�f�����!9i";�	����M<�;����f������>� �F������������N��X���C�3�fm'���t���_�
�����U����!WO��t�pkmM�F�m�Y��\<d�k~���P�/��w����_����{�
V��D�,�h��^��97���l����3�����0\f�D������K��[��d���t��L8f.Wv�Yo5^*{i�z�o�&;��R�����;��������w���x�p"'&�~k� &��+$�]�����a)s���T$���H�4`H��F+	�.10#Y��V��{^���g(<�<�>�u�So������*ha����� �p7L�����h|�m��E_�*z6��*��0sj��*��.,�7��R�MG#�p������j���T��������f���+�^o7��_��@\������;��V���g��.����6�����4G>J�U���K�{wqZ]�9�i�c
��Rl��6{��5�
m�::��n�2q�H3��G_�H\��i6����X�����]����Q� 
0K
��rh0Is���A�����Lt@���Qx�r�Jv���w�����H������A"{=��RN�\���	�s�/�[_M��R�fc-�+�����Uc��R6�`����^XNv��'��h3\6�S�1�`�����CG}�m�
U��M@O�M
c��n�N�TvM.�')�����8���i�����N����_>C�iA�0*�����9��� �wg�s���]hr�U��_�sg��������:��=5���^w����������I ���)O���������8�m���8EX�
�+%�����;��F1����������&XvlC1�/��&�;�jL(;�l�������t.~^���5���!?f�h&`��N������zrk9J\�T[x#�^�|+���2�V���U;y���6��pK6.jM�a���Y����9<9�����Ls������7�h$�u�G���L���d�I�1�%e�3��r����������$m);B�"����s3����/��9���G�`���2����|��5���}~�D�p�B����P�-��An��j9����[}���UZ�����4��������K�s�a]����f�N(��.����ryv��H(�(��V^����D:�h*��A��?���x����p4�<"WuI��V��o �i��9�h��V9K�w�-	�����v�
5��}���x� ��Q�b�����a����������KgT���%���a=���ow��G�E"�����`!���H$+��]��8�G5�C���+���l������[�`�TFZy����BP)H-?Z`-]�/��Ngx{#!#�r�yv)����nW��y��MWz$���:BG,>��,�Cg-�]z��Hy�k�������N�Z�}�^�>��Y�B[[v�������Ff�r���n�,FVz���@��?��	���%�.��+����;��<f���@��w2@ek:�4����>y�_���k��7��f��~�l�XH�]1Q���k����h��Xf	��?!���L��k=7�����z���fA��_[(zo%8��<�4���3��W:P�S��D�h��
����5�������Wi���1Ss�����"�h�<�:��s�j�p7 ���(�iY���Z�:P�/g_�q��Y���8tJKO�h���	n!�m�^�K�m'A2����i�K�LP�e�������\����������s���w�sF%%������Qz��er�
E��|r�/.5��\]�=G�Th���-���U�NF����i�T�u��_!���C�U&�}qj����ke6������%*�,����z���&�����������*�;$9��P��:bN Zo���T)���+=� ����(1�!n��1�����\��_/EFs�7�P�5�J&YjTtCDhy6�����h�Yqu���|�k�;�dMfG�����Z/F�N���L��a����1SY�f0��,m��h�'#��3TF�������5�{9Q���:��30�;���$�p�lJ�c��yw�Y8���V��L��{)N�c�kd��'^�E�"+LM
��p�5Y7CS��^���8�����������;�mEo��\Q�+��3D���3�]����S���;�k6��%")	��6o�,1��Bf|_��@���;$'�g:�)P��v@4��PWi0S�����8�G�>�K����:k_}�	n��FK���7���!d}LS�QL�����w�!
��8��~E�����~2	n��J����Y��n4k�����!��N�{7���Oq����E���_.� w\#O�����~��/�N;(��ON��m,�3�;�E�]���T��<
������9fQ�i���!��e��Bcb�6ZD����@���CgVxA�����&��5��sS�"}�3=��CP�k�SL�6Y�(+�������<B[���~la������I[]�����G�Z�Y/�|������0.������!J�J��lL�
F$g>�e���E){�:��Sr���U^�:)^�X���O����_@1�l�����L��-�&�J�z�;�>_�2FU�j��o�:�d��q����Ci�, ���#��%��������3F.������{e���l��~���,��h�BEX2�uZ�n�Y�i�to�Jm���������jZW�7�h�������#�*��T	}zC�f%������D�6�D`:���0+}��7������?{�Z�g�`Wc�myS5�������~�����t��o��mJ�����Z��4rH|��!�����*nm�"<p�������zv�6��knh(3;���0�"'�p���
:�O65�A
��M����5�?v.��V�sZ{U}w������
"�`K���v���'��k��LQ������a'q�l*I������E'*��hTYP	o5�9�0c��fG����t��dx~)�5��/e�}/w�H���4a�L��&C��zF�x�$M�3����)��Lv�(��N��e����G��B�������n�puM_q1%yk&��|p9Sb���Z�����Hxw1}QX''���d�hZ�^R��������;�K��;������5U@�G}v���CpI��#O*����?�)(_
����e����=�s����&�������������8�c4$�A�6�����Mo�"�0���&$��f�7"��&�3��;��*��8��B}��<�GIv��6T&.��"���w�r�����2�Blx���a��"�����cb������D�C�\�,{l�E Q���+f%��1)3���(�8NJ����E��?�����_�RT������-{'�^�	\o�A�2`9��Fd�������f�Q�������)_
h������E���k#E<MlB����cv]���5��8#��O�W��[�����1.����S�S_��c��0��5�4��h<i[��A�k�b	��@<+���r��<"5s/F1u�~[���]���u��D����El���������'�p��uc����e9����<�/��+x)'���{�a��	�(����:�\��Dl����{I(�@�G�Xh��[��I���e�Hx?����&9��I��<�u����O���I���������+Ju��kR4�KW�x>aWL�����#�WW���tH����)j�G��
q0>I<���_����
�AczB	Ky��`\+5h�����8>��Q$YR�=gM�\�h��O:j��:�	� �������T1�[���U�3e:��,+���.��[�Xs�N�S��>J��	0x���D����Q�b�W�-m��[�Wg]���i����^�J��g�x\�`�=���nj��
j:>7){�1�J�|��
��]�����y�8�>��m]�V)j=��fa�^C�hk��Vxc�w596��&Re>k"�'�&k����i��#�_�g]���4R���%��)���C	(�J��b �+*v)�U���,J��],,;��*��~���EL����VV[�we�]���A��&�Y?���T���=P.�P�^�U��j���:?�j
�kW@���C��6Bw�!�%���o�8r���&��=��9:�Fv�6��D�E���:,���D�����f��^;=�����-o�������)��������t�ZeY]���L*��a�;4���p"F���R���y1'%��0��S
��@�r�e��-��;��j���7��Kn�L�$P#����-rX�0gav0%���0���a��,z�r>x�-b��C�Jj�@������Z��h�M8�Ktg��<�2Gs�t^~A�b���md�=�:=_l8^�b�b����������O���3v��"����pz����/L-�%��+>��|7��u�����1��U�_��@�j�s�#~�"_���`V����leT!.�5,V��v����ge����p����c0*	�����A(��$�_�����'����9���Z�������'v�����E]�F9������h��t�����I���v�N�~P�j��h!C�k�s�D��Ki1�d�}j=/
O�X������zm�8��nH)�&�E�w�G��S��H�eRk:g{���p�&.�n�3�W�|"��������?���b�����:�I�N��0��Q� ���h��m�����^�[�Z��Of���v�e~���Z��I�4�@yI�TZ��k�f�8�#(���8gQe4�2��;�k �R_Z�<�(C��>d�o�NY�.�������)S_��az��>b���Xz�Cg,U�7:c�@��@������e�9B||����e���c���K����n/L��M�B��{JS�.l��x����y��&|]K����Yq4D��tZ��c\�����i�XUq��,��,���C	�d�*�=_� L�nYm�"��Qe�1��`������DM��Df��-�/���3@���:-��7��}��B����#[��3���u���k�x�e���LZ?%���5�2�
�#���;��2 ��N���a!��\W��A���!���qr2r���S��}���z�/��2�.��e��z����jK6��&����a�����P�)Z/��p0(}��d���|���T�
RU1t[�Yq��'��bS��B�&����H&��P����g�9���-�o��/6�mk���<�Y�
L_76��
��\<��HA�f7�������Uk�/x�����T3,K
s&�V�B���}��m=��1���LvY`d��b[�M�z����V�����a����I(�Q��C�����qu���Y�M �0��o��{}����/|Y.�gJ�NKK���%�6}��r/=�e���I�K������ow7�u������m#c����d��(�
����*���N:���.�����-�c��a6:��I���O�n�}�P��Bz[?�`�
6��T� ����x~s	3��b��(�b��%���&n,�z�����sq+e�n�?R�����������V�����7������m)�������n���w�KE��l���q����DH\}�c,���C�K2�����C�EnQ��O�V������!��N���
�pC=q6��t�%K\s���=��T�r�B�/��~d|�]D��i������rS�<z�[�����/���w�H��������U�h�i:���O��.��6�Lh�W'�d�*���!egW4������re����6����s�^u�wm�Yk�k�� '3���B���C���,�#8+���~�����jtI���T?�
�x�3�0@;������f��T�%�Z0��Ex��?G����H���|>����?����+�x
^���S��`$V<������]��U������<�hh�J��F���{R4������B�����f>9$�6|�'*��y������9f�D��`�>z�G���]1��_����"d>D��1�J�W���%�V���
NC?���aWD�,L���lH48V!�2q�;��p�	+���>��w�i��m��_�����C�b#X5�����M(����X��	�3h�v���vGs����p1H9)dl(�~1�R�S
��$��6
�Q���J����3�����f�wT��$J�[<�H�?7�`m�w��E����tuI�m%"��N���6��TD!��������z���c�/��G3S��1���R���]�6�@�x��F�&���%8��x�_��OtX�z�2��p����R���u�O:��fx��?��?_�N�'oj��_:������������Q�Mm����'S^6�I�@O(7��B4�b%|x�8�u����^��k�&�vUP�&��;	���w�-��h�\A?��FR'l������kVB7���.��e���qoU�$=N�,�y+}9e����K63/>�e���'�3 _3I}�$�����ZT�k��
����a�����PETH����|�3�O�/�=��-l�}o��a�]�h�~^ow�-G�������x�������������Vz���%�6D���w��a��<%i67���0.fy��5������&��V��~�RI�+N����r����"����v�*.<���r{u�`�F��O�yQ.����.���
�
RaI9/d��y%q�B�9�!�u�'
F���-qBai"o��.�E:gz���<}�������N�9zs����B�`�>�����e�v�m��:�
9���������j;�ls1�j���G��}!�VW{:���)�L�����f���O�m$���{�0@�Yc��+q3q���p���S���p�j��r#�s���!������{q��UTe�i��+���M���	���`w�fQ��g��������l��l3����t�c�R$!]��������U��2���������r��,(�dI��o�=m���hB\1�N�����P&�����H����g�)^
L�$
{���k��$c*y:��;-#LHI�<k�x(
��D�i���{���`&i�	?x[f(����C������y=������7&��\�U$���CK^�����g��/��������K�z��2[N�b�]=?]�+�=D�o�$pN���d�-�%sF�S0C�U36��h��2���7@o��U���H�����l
`)����QF{�t�u�'�rSjP�n�vC�V�b00q�$I�����7�O[�R<�~']��$���G?�@�
1Ho�O��!����Tw'��v0���;��&���{7���S�K��sHit�;�-���wK���A����G=U)�������W������*_�����A�x����G�OZW��tj����v�z#\����W��8�,�
�
���2%c�G^%&�y����#�������#�����>N.^*�G�1\v�z��9�J&i�C�%
Rj���}�)�C��Z�_ �#�������������3��v�Cy�1���D�x����6R�c���;c`�������+�Z������]\4��:�H��{FUE�1������
���D5�%U�c5u�:�pZ?�w9�O&1��p9�L ��X"m��z���Ea|
"��������X�����Xm��G�ip�O��&{�3�r���GG�����r�T>�*�G�n9�pg����l���������'mEIl�{Z�����S�	sx�����:��U��W���iA��hGK5��qN���02��-���_�A�9_O��T�PvQ�,Y\R-Z]��,o/::�����}X��_��^���-��P��z�h��v+���������]�L=��e{Q�#�`��R�����wdI��@9!Ps���g�v�-�}���}zY��#��+�A���x�>p���N��v���.������e1�~����q���~l�����������y;g�u�����l4�B�n�Z)L���
�Y���X����h-�rY������[>�J���e�iy�t�]��n;y��������������X�_O���,4k������EE����eJa�Ub�O�a�
3k�SKV�h����r\������;z:<�_������W�#��{�Fz>�p�G)��'w	�P���]T�tW��������!1�z�,�r4�*�8�
TA!��,,��P�T�U�w��	�`��_<����@q���3���`��	��������I����e���%U����s���CIGv�����QwT����7�����u}i��'��!oG7�_w>tG� 0���,��?�Y^����=>�{��>Y�B(�
.2��z2�S���N-��
���n57V�*�	���@3��d�vv�0�������r����D���w9����!_����9���7E����l�!������!r�I�vV��������5���0�'�^HB�t0�9UW>�}�C�b��A�S�%+��J8��'���(yp����*a�h��Yx�M������}����#o���t��]��|�0u�d��i�����,K�%�9�7��+$_�<�6:���z�}��{|X��<��\\������k�#��]���Z�Z��I��@'�mH�Aa�pbH^�K*~.�w�O����Q��n��0����G�i�;�Z1d���:��H5��B��zR�m�/lf���4:Ld������G��n2W�="N�7u�l����Vg8�l���������r����r�}w��w����K�2<��u�	s���[�l���o�#q��o=�����W�����p��|���%�,S`�U�y=���M'�.�w��W�����^�����?O1�C��"rj��{�G��J�W*?�F{����)"����W�x������)n=�7���%���tcHm�����!��"}����!02����B�f �l{r�l����>>�#�����Y��Nn�%�e��Fa]�N�W����������+�s:�2�kvFs]?_=��v/��� ������f����L��D��e����7(k�����n������G��Q���[����E-����1������>p��Ke��T��������~P�/�fQ�1���a{����Cs����s3�t?vn`���dr���qQJ1���I`�?V��H�~�����k�^;zz�.����F����
�z?l>F7��(@-t���D�!��q5�m������3w���i�0W�#
~��u�(����������W��%�����mnl}���MJ��m����y��|���mW*�����O�GO��<�R`�j�6'%LE+a<�����R��H	�#Ii]X/7��&I)]t���~K��)��B�|��';��T��V,����V�K���C���uRq��*��}8�I��0o���tJ����_���:!]1�AqA*���:U<�Q}��n�����^�:����x�%��K�d�IU������II���P�V��vz��4!7+��+�DA�j���ac�������a��j�F����In�'C�;�P��g;��r#nQ~S���Q��SA31���J�������������>����g+����6ZJy�"���~_���y��qt����*m�O��P�2�#�A:]{l %F�YG���h&��E<a�F:R��n\���C9j2��64x�F�~���&��V���t��|g��l
�x��>p�IB*##�HG���A�l��E+���w����[������l~�B�t���	�T���|�[����UZZ��[���y�?�Y�6"|������1��@��rs����%(���:��
�=ip����[��J��no�0z��`X�P��*A)����B��
Z2����4a#�i���U�I#
o!�"�����+i�N�V�8J���>�!.m�8��o��Rp�mjD:FZ�li��;����.~��W�����$	��SW��W�T��E�9�0�C�4������\�������rp��"��S�Z
�/j2��Wah���$+��QN������{���>�V���cuC���pL�������c6�~��B^�!�bNe.a�tg'������lb��6F�=����s6����`����R��TY_�����}�������}�������}�������}�������x���X
#186vignesh C
vignesh21@gmail.com
In reply to: Yugo NAGATA (#185)
Re: Implementing Incremental View Maintenance

On Mon, May 17, 2021 at 10:08 AM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Fri, 7 May 2021 14:14:16 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Mon, 26 Apr 2021 16:03:48 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Mon, 26 Apr 2021 15:46:21 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Tue, 20 Apr 2021 09:51:34 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Mon, 19 Apr 2021 17:40:31 -0400
Tom Lane <tgl@sss.pgh.pa.us> wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

This patch (v22c) just crashed for me with an assertion failure on
Fedora 31. Here's the stack trace:

#2 0x000000000094a54a in ExceptionalCondition
(conditionName=conditionName@entry=0xa91dae "queryDesc->sourceText !=
NULL", errorType=errorType@entry=0x99b468 "FailedAssertion",
fileName=fileName@entry=0xa91468
"/home/andrew/pgl/pg_head/src/backend/executor/execMain.c",
lineNumber=lineNumber@entry=199) at
/home/andrew/pgl/pg_head/src/backend/utils/error/assert.c:69

That assert just got added a few days ago, so that's why the patch
seemed OK before.

Thank you for letting me know. I'll fix it.

Attached is the fixed patch.

queryDesc->sourceText cannot be NULL after commit 1111b2668d8,
so now we pass an empty string "" for refresh_matview_datafill() instead NULL
when maintaining views incrementally.

I am sorry, I forgot to include a fix for 8aba9322511.
Attached is the fixed version.

Attached is the rebased patch (for 6b8d29419d).

I attached a rebased patch.

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

Regards,
Vignesh

#187Yugo NAGATA
nagata@sraoss.co.jp
In reply to: vignesh C (#186)
Re: Implementing Incremental View Maintenance

On Wed, 14 Jul 2021 21:22:37 +0530
vignesh C <vignesh21@gmail.com> wrote:

On Mon, May 17, 2021 at 10:08 AM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Fri, 7 May 2021 14:14:16 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Mon, 26 Apr 2021 16:03:48 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Mon, 26 Apr 2021 15:46:21 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Tue, 20 Apr 2021 09:51:34 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Mon, 19 Apr 2021 17:40:31 -0400
Tom Lane <tgl@sss.pgh.pa.us> wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

This patch (v22c) just crashed for me with an assertion failure on
Fedora 31. Here's the stack trace:

#2 0x000000000094a54a in ExceptionalCondition
(conditionName=conditionName@entry=0xa91dae "queryDesc->sourceText !=
NULL", errorType=errorType@entry=0x99b468 "FailedAssertion",
fileName=fileName@entry=0xa91468
"/home/andrew/pgl/pg_head/src/backend/executor/execMain.c",
lineNumber=lineNumber@entry=199) at
/home/andrew/pgl/pg_head/src/backend/utils/error/assert.c:69

That assert just got added a few days ago, so that's why the patch
seemed OK before.

Thank you for letting me know. I'll fix it.

Attached is the fixed patch.

queryDesc->sourceText cannot be NULL after commit 1111b2668d8,
so now we pass an empty string "" for refresh_matview_datafill() instead NULL
when maintaining views incrementally.

I am sorry, I forgot to include a fix for 8aba9322511.
Attached is the fixed version.

Attached is the rebased patch (for 6b8d29419d).

I attached a rebased patch.

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

Ok. I'll update the patch in a few days.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#188Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#187)
11 attachment(s)
Re: Implementing Incremental View Maintenance

Hi hackers,

On Mon, 19 Jul 2021 09:24:30 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Wed, 14 Jul 2021 21:22:37 +0530
vignesh C <vignesh21@gmail.com> wrote:

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

Ok. I'll update the patch in a few days.

Attached is the latest patch set to add support for Incremental
Materialized View Maintenance (IVM)

The patches are rebased to the master and also revised with some
code cleaning.

IVM is a way to make materialized views up-to-date in which only
incremental changes are computed and applied on views rather than
recomputing the contents from scratch as REFRESH MATERIALIZED VIEW
does. IVM can update materialized views more efficiently
than recomputation when only small part of the view need updates.

The patch set implements a feature so that materialized views could be
updated automatically and immediately when a base table is modified.

Currently, our IVM implementation supports views which could contain
tuple duplicates whose definition includes:

- inner and outer joins including self-join
- DISTINCT
- some built-in aggregate functions (count, sum, agv, min, and max)
- a part of subqueries
-- simple subqueries in FROM clause
-- EXISTS subqueries in WHERE clause
- CTEs

We hope the IVM feature would be adopted into pg15. However, the size of
patch set has grown too large through supporting above features. Therefore,
I think it is better to consider only a part of these features for the first
release. Especially, I would like propose the following features for pg15.

- inner joins including self-join
- DISTINCT and views with tuple duplicates
- some built-in aggregate functions (count, sum, agv, min, and max)

By omitting outer-join, sub-queries, and CTE features, the patch size becomes
less than half. I hope this will make a bit easer to review the IVM patch set.

Here is a list of separated patches.

- 0001: Add a new syntax:
CREATE INCREMENTAL MATERIALIZED VIEW
- 0002: Add a new column relisivm to pg_class
- 0003: Add new deptype option 'm' in pg_depend
- 0004: Change trigger.c to allow to prolong life span of tupestores
containing Transition Tables generated via AFTER trigger
- 0005: Add IVM supprot for pg_dump
- 0006: Add IVM support for psql
- 0007: Add the basic IVM future:
This supports inner joins, DISTINCT, and tuple duplicates.
- 0008: Add aggregates (count, sum, avg, min, max) support for IVM
- 0009: Add regression tests for IVM
- 0010: Add documentation for IVM

We could split the patch furthermore if this would make reviews much easer.
For example, I think 0007 could be split into the more basic part and the part
for handling tuple duplicates. Moreover, 0008 could be split into "min/max"
and other aggregates because handling min/max is a bit more complicated than
others.

I also attached IVM_extra.tar.gz that contains patches for sub-quereis,
outer-join, CTE support, just for your information.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_extra.tar.gzapplication/gzip; name=IVM_extra.tar.gzDownload
���a�<�s�H��U���z9��F`�I.�%6�r��YL����R�b��+	'����������������\	�����������K�x{O��
=|wzm�]�=q�hl����'m��t{OX��������K{r���[��~w=��k��t�- ���f�tu���'O��r'Y+�Z�yw�e����@��������w���n�I�����q�\��?N�7I�`�C���/�����_t�w������9��E{������a���#v���9`��s��:@��y��8b����y�����&^���~����U����s6�Z��3`]�0�����|��y�����v�|u�+����������&s�=��O��r�d�9Y1`�Z�(N�$�^�4�.����|z����a|�[�
#�D��Eh��4�0?�V)gA��9	��G,���p:�Ojf�7��w����h���[&�����^���5�,�
�Z\s���cQ�,����.�il��`6���8\-��=
n �A���������Ok�
:�pE���Y0W;��hdP��zY���,��
{��Ky-�.`9�3������,��6-�����W����#�Z-g�� J3��X<g�f�0<�p�����br�:E�}<@D�H	O�$�D���Z���L80g�U$�qdg�@@�%��x�>����.��\�E��b�5l"q�q�
���c6��L�{�Uci��]x�5�f{~�X��=?��P�����~���6�[x�M�?�0��J�v�`$� �������B8�u�N�u�<a�>��%x��F��pY�i@������V�Vf��V�2���w�V/��,4�_X�����/vw����;���W�<��}���s�`�����Y�p�����f�=�]��3��������^c�E������s/j;�w%��1����G�^�,P
 ������c�_�r�{��������/I|e��C~���]-_��W!�.��!.�4�t�Qc0�*� �5�d'�h:<���Hr6�	?�BO��"
L��8����.��8�i�=���g(��e��6+�8�k��j�k��B��"B�/\
�������3�����
���.
�Q�P�%U��D���Xc�
}��&���$������[/5�V�zS�X{�����M��$|���FI$�lnW�\��cq\�o�n(7�;{5��`�����o�u]>$�6CVcMX����iC���%��;v����`��c�%�ly{F��N����`���%��2q�A@�����o��c�|t2:������h�L�2/���\ H`D�h�l�`��_�$2�����(��E�����zs�3r\���~��$�G<Y3�Q6�	���E8�2D
Xj�V&Q�kC��5�KLX�w�"�Q�R��]V2���4�(�Xo3�
��&��U}�I�o@dlX`Ab����N���,Z-���x���y����''��hgG.����z	�
��$�`9
9P��9I���?�hA0�BZ%�0F,���r�[��������
z������������S���������T��}��������:���X��`��N���6k96�Pu��,���`,@B���^���8���
a�^G�q���Y�U������P(&���aNq`?���:�s�����U	J3�|/���]pCN6����+���E '�I�Z�k~�.�����&H!i7P�e�@��HZP��N�s�uP}������p|������Iz����c
&�_��
���x�6"2��='f��&�8�,<\,�+���_\��.��"��R���Y
�Ng�)��q��_��t��x�������A%�Xr�Z�^���I�Y���zQ2�)����!G��4�-&r���7��k�?b���&�F{��r���M�i�{���K����T��'OQ�Tm�N�Z���g�����V���sz7��lY[=:��_��IL��66�f����	����,n����+�I��W_%*��#�#.;�?@�V8&�o_{S�%�����z�VI$��;h���B��D��$�Q��,���#;��E��K]�������"txzQ�I���F�[<��>�L�&6�XVL�]����x��
�&#��l
"�����qC�f�{�^����^1x��=�|��B��u��j-������o>m����J�_�����%�7���� el�
�
:�>j.�9<����`A	1!����!�pA��"�Mt���6{����l�RQ����l����i�l����h���4>=��<I��	��\KC|%��R�������*��X���,�n����V����Cm0~�������v���{����N
&_��g�P
���J*�A��y��yl�S�Q!�(�����+���A���8@�N��9����4����������g�:�{zo�}+����az*���JuC��|�
��K+0��r�a�$��zn�	� B�,�\MD����������^h2��<���==�M��oG�t��d��t�i&��b&�]*3weU]��I���Z���%��g��2,g�o�o��@ZMx�����T��);����9�������f�<M����6�?�I/�2�K�)�N��N�����S��]2�{^��cC�S������C_�B��+��y�i���
$S1�.>��|�|�����.�,������?�ipv�A��n(%#�&�������2�+��14E�����<dW^F>��I�g�vg�B
E_���:K��������}�Vb�������m��n;�>LG�����w�:
b��D��s3+��F �dJ�7/����P��)����)}
��	�&!9&��x�E7S��YI�����^���\��l���p<XH4���m��b<Cd���aa������~�j�,�m��a5Up,�JxF��Nd9���vI���

����o�<����E��}��G�LK�2D�7nz4����c-��Y<MQ����6=��RX�"GE���'��MM�M9���;����H���b����-�9=Ju����M�
w��k~��EO�y/��K��j~�*RO8:���Q��q4�Y��������'<<�����Rut(<J,	n�|.�aUj/�<x��evU��^^X�I%*7�il<H��U��]���3&B1,]�:��.,��F�)��������j&��@�wh��(��[�A���J�Cl������4��D��:�����9 �}H��*ff�p7�9r����k{�%>�8'�c{I)62�%��Xy�n��%���0�v�D�s��&���t|vJ{���c*����|�MH�=PB�1q����
�`�wW��E2
@tYor���z�;��u��`��;��7�m)7�;�w�)CW2�K����<m�c��:c�8������p*��E�N'B2��(�@�L��J�����c��1���3�p���@���Tu�������]�U( ���!B����h��7&jj�r��z7��c<*t��[���k��,����Do� �m�B��D��;�C������.
\?��-J�\:���rR��@�M/J=2�����r$�
���������#��|�fV��GW��Kx��@a���F��?�wc@�x,+��(q#o��$��9 j|�nRA`=�B�J<"��� ��S�tP_��D�O��L�{�`��U��
!�j
c��GH�*aj�I�'(�-��6r�:�p�\��OK�T� �&e�X���
h!�k�P�qm����|V����p��6��74��*�A��1STe���y@���S���l�@�N9��l� ��(��4��8������$��{�"2�AQ!@��JR����]�W2T=J��L�
4-�T�������}���jC�X�Y��H�G^��������G������s�f2�n([G���0:(���D^����6U=�)<f1��Q��##eQh���l���w|'�G"\:�3�i\���j���81�t�7�@4�1y����,g#/��W��m��{p}������l�tR���wv�%D�I6�+�&�m�uo���gaS�S���s�,�n���l�R��)!�|���r�o���c��l��U�X���{`wz�1�}U�-������*��x&�����x8o�����%�Q�Z,�!�Q���X}�b&*?�(j���
����h*QL��u��G�Y���m�����g�g�3
r�'�U�B6�<)&K.�C�*FQw)rJ�g�8OV3���S�W ��gR���������D'R�P,��
����0P-��DB��O�@8<��\�W�O:����]��0^L�r�sy��<nU!8��P;W����9}�[,]�O������U�(U+�LV+�k�����F����d������*�R,�s�-E}!��
��J��t�y��8���������S��K�2|hC��n���l/����L���8=3�%f�jGhPQ��0��)��k�z�I-{��������]��+@�4���.i+ju��s`;=J���������.��������S�������
5jjk)j�v<�d��R��`P��x�*��V%�����5���::_����_Y�F�}W�����
N��O���q�"������7:U��*�e�����m$\z�'���`�O�
�;w{������k���ofI����kV����TER��	o
�F6����P�{��mi���b��7��*�"�C1�^���r�/5�^K�u`�<�/�����`t+[����57�Z��$��	_����s�xD�=�������QA[cA�J�G�3w���=m�U�.=kzsY�RQ=TIx��I����O	�^��z�.x}�L� ��X�$�z��m�|;�d�x�.�S�������@���NjW�;�E�Hs�tF����D2���W����z��Z%~s2&2
���	�m�������#��W����{���d�K&XX�7�[�x6��T��r����vw������n��������sF�n"^?�Ci4��#�>^lVt?j�B��
�%OP��������;���"di���5E���6!����
�A\���j���U��n����t��Zt�PWoRY��)��B�]4����y��������g�~uo�&,p��n���GJu�����z�����S�����7���F���;���d|4��G��$��<j��s�nT +�DO���v><���W�d�����5���;�����J�������;�8n�|�R�Qs�fREJ���(�"����#&.lI�V��e$5�Gw��`m��i�����k��[����5$�c��V�����;�)��@�^a]%���2C�%�����yk��yB�K
W�7�p����\@�j��J���X�/�����4U��
s��RN���W�S�fxz�r<y���y�0��TRZ���/K������ Kt��ox"DUU����'O
�2�6�IlPY�Q&n�'E�x���gg'�V)
���y�����g�^�Z�Q6t�}[Ke+��Bg���
kBT#������Iu�2-��3K��&�����DJ)Y��$u��;�3Lf5�{�&�U�������m�?N��F����dt��F���Wu�j_W���u�&l��������Y�-��X��)���<^�g�T��)K�_��IA��O�1�&�i���?�\�z\(�xY�������X��x���U��	<�J��T��_�~��ui4"!$k6���M�NM�u����L������z��;�W�� J��.QnD//����B�:�
LYV���U��S��:3�")-�k��u���Y�-�R<�[����.Dw���� s��a��N	�B� jF�_H::�p:]�[��������Q����������`$�� F>q��CZ�=KD��S�8+$"�M��������Wd*���S8��m�)�����/��m��*�OP�������S���j����C���a�H%�%u��:%0$=t����Hd��4%E��P?a�������4�d��*~E^��6X��@hq�s�6��Y�����w�=�
�l���~3��_D�R��@�!�8���Kdd�	�p^�����w-����k�&��t������y1(C��F��)��������[q��g-��K	���VN�1ed�yMv�F�
�2�G��g�n�( ���Y#�8K�G(�
�
��s9y�<�4�z���$R>w�����j+���~;��g�����x�d�����<em�����Z;�B�������@

�%��nizEdI���K�H(s�Ir 6���B�!��Z��(��w]Ft���\j��*m�Re�.�6�����_`H���R��g��
Z�^$	� ���\S=��I��Ge���������	���M�e��������$G�Fa�QQ��gX������(�i�N�l�,�����b<q�`*���D W\��Q	d �%@Ut��
�Z�����s�R0����1�,��ux���D�
��N�\l����Jk�h{�Ut&���z����c���8x^�L�VINdF��D���-z;u.�D��Y�vY��
d��(�!��mY"����,�x*��SQ��%<��c&����?�n"r�����8���|�GA�1�QG9�P��������
�d�U�5+!P�A+0��a�U�:�������]C�m��I*v4`(������@���yp��N��AZ,aU����cl4/������5Z����A�\1���0��
5���g���������^,�W��!����;�<��P
n��Exj����_5ye��d��.S����[���	�k�A���w��=lM�-����e)h��W�(�A���Grwv~p���'aqz#����"�A�H�������������� �Z6�]��V���{�m��R�.�h������$x�cWa���JA�x������!�6�1�9�EtC�*�������K������P���S�CH�r��X���=��y���Xu/�Sz%*V���X��3����~>���M�����x�P�S���H u��K`�&,=��� (����g�4I��$�'��< ���5����z��r�j����q��&��h(H���H�K�p���r����}gO~�����]u|�7I���^�LP�Lz_(^�g>GC�_�|kf�3,�x�+�=���tM��tr�m����l�o��o��G��B##�X������P�A�xwz��Y��_)���rV����=�=k�D����Op�4���\�8�R��h
��������=j��O�F�R�B�?X�jv��&>t���Q��9n8���}x\=���V�Sm������8��������d�TN�������9"'9*x�rK4hk��_�4s���r���9�XKi��b����k�Y�����9�j-r��8jG���������j�����Z��w����;��y�����K{�w��9��a��X�r�}��wj��#zk;`���GG�YtT	�����/w���@9X=�]^�i^����\H>����cy��Z(6�]]���|�����`�H�r�!�����A
������}�<�����k^���#i^���������G��c��r�v����Q
O�j� �r���W<]5k��x�����wqys��y������P��A�� k��&E�?�]fO'���h����zvq����I6�H�����!uO-,����"Y����j����4X�"�P`.��G�{	������O�Z����Y1�*<a�Mk�5<�k�u�Y��D�.��������
:(.5��e����_(����k�)��W�� ��9@N��C����)x�[�9z8{A����c�S��OY��5��<�x��RK��l��[Xo6f"�Q�P��A����
CQ��#{�@B��v�`��G?��j���v�2���*��$5������KG�7�p��!��/o�-~D���1����P�x��c�6�a#XCM�]��j- ����K��������1�����SXt%�����������um=T���]�\W�-�u���]h�������t�U�SV@,��1�zu��k��=�F5P��>@�}�����K�%�cU���>���S�5���)�����{��,�����`��Z���Osk������:g����3_}��>���l�>pU�4�XeRw��t�_B:��z�\�I��]�G�>�����
4���Cf��h-�s��gb��H��
�������h�h����������8<B
P!�z�kW���UE����y����}�>�x�w6�W�Y��7�^�8����vF�Y����P("�Yv���pu��=LB`���FU�DUk��6���<kb�xQ�5�`[��`&����#}���I"�^'� d?��g��[Uw��]�X���~*
_������i��I�1�aak_p��:�b`=.D2i�G�WN{;X�� ��MS�c�M��v���d8�m�HJ���8�O�]�����3�y��cN 
R���dX�T�������M�|yBh�]	�a<}*b;�����c�y���bL��
A�����q��U���J����!�N�U�����!Y�M�)���eh���}�&�/��C�����=`�B�&(XIX�r��
8��=�Z"=�{�SQ� �Z�����	�1�v��W�6�����|���w1s���'��Ch��C��LPV�]M�-�MV�J�4d�V�� m��x�#���%C�F0$���
����y��}A_�����{9�YA	��vSm�_��!Z$p������:�V����.A��]�����k-�Z�,��wK�/���0�����S�����Kb�����W��y���E��=b����n�o���,�������E�M_�����f����<�h�<���'�q_=n�E�
�U�"Q��-�GZf�2�����]^J���m������b�>?�j����eIJ`��7_@C/{&���u��"����U�
^y��������9<����[�~�Fh������A������o���y��e�~���g�a@3���Cy�<��DL�����������^��p��������G�c\����������>����BjB�G[���@����)��a���������!�����y��0=�46�U������~�Z�	>���f��<% J���%8}|�Y�ub���IXk��w����O�T#WH��ho���j�E����kqW��Z���o���6��%�H�8]Z?��b2\����`_�3D����!D�\����3V
������J��*Wd��1�Z�h��R�&e���|�B���V����^� ���+��L����D��WV�jeSx<	��G`��9����j���8�.��k��
�Fl![���{��B��8
�����������	�+��3�8�^�SC�����m��l;�rPL
��`���6��VTL ���oaC�kE��P�s��@���PT�l���6�����3x���xZe�xM��u�[|3p]r^]��5K�{����������U��U����p1��P?��G�kK1=cGl������-��nE��UIO[i�]J�!�O����u9����?��lu��[k]�BW�k��O�Z��]�{tK�������2�
BO���]H����m����SX�b��|�v���z�����
/��nm�|%����lW��y���,Fu-p��W�2�+��}�( �H������M�������)2���F�+g����W�a:��'��i}T����F�f-�l�!xl�H������d�h	]�6�]YB#WbN)�������D���b�����m�)������-tM�[�14*D���E�$�>�I�f5k���H�{Kc�D-{��3�%k�+�X��D��AnmB�VX�����j�������'��W��t�<��ne�������m���k[���?�%m�������mb�:�uX��ylx��m�J3����Z������������]6������j�=��G��O���*R�dl5k�-�&��dv�EBn�����X����T,���zl`f�d�X����Z=a�I"�?�5����n�Z��id��`�c��J���j� ��v�;a��"@fyp[��n��3�X�o�l����m�j����6go?a���r���:�5�r����s�v��(��1lT��������Rk�P���l�3�f�;����GB�'���|��X97^�k��.���6�=U+���2f��U�%3N��"]��13<~7���!��eT�\Q�'a|��tif�$��6��$lokC�'aE������Ux���&�Zm�j���Ls�/Ye�5�q������>oo�T������|����W]Z[z��Ya��l%�~&�f���`��l���R_�'`{�2K��'d�Y.`a�s�"����?F���5�V���g��6e�|����|V�h����$�n��&�U��'c���e���4�\�����?e���NT���`]%I�j	X[-�,����V={��j�d�w6���c�1�����Y��Mncz�
��g�$����e?�Z����S.���+�&�e�+��Lk�$�1�)�+�7u��L!Pf�e=Yx���m��u���1L���%����������`��*��7�f��u�G���>��f�4����`�y�nUJ:K	�u����Vm����^�1v�5����"A���c��7VfhM����6���bd���<��d���l�{�%��QP~G�o�:�������24n.^�I�h]�43[W�VW�m�2�c��6_i��)���N�=NI��������,7�uemk��&���,^�J���km��j��������$-������
r��`�
�vz�_�j+f��F��&���?m�\����N>+��1����?�7^d��	��7��;�I�t�V5h[�5�{�����b��4���l(��qd�o=���i�,���m��b]U�6l�X�4����f�&�dx��Ya}5���I�=�4�9��,M+��4���i�Z����2��������a�k�%�����c�n��o�����?O�����7B�k���T[���g��\;�e�X�
�]�i�/p5���WI�oE��$�pO���]�W�`�Y�������F�E,��m�a';���8�neb��e�z�F��qz��@��{�j���Y2{�:�)�/i3�.���Y'�'�)����
(�2o���+�e[��6�^zr&i�YWR��#0=l%��Y!���m�@��I�cz�7��5QY8�w��Y��71��6���jV�eiS�f��N���;}{�;��A���x�=�i���0��+Vy�����d!�����diu7��i����!�\���78Y�M��7���dVEB��"WJ�w��{����Hb��UeU�t��$�lM��j�Y���Y���"~�����K�\�:����Nn+X��<=�z?���[�����|�&~&q#�[��<�%���S���V�#)Sr00T	8��?7��]�_!����|�w��fR���P,1��[z��U,p�C���~O�����E�Y1�g�� =f���!��9�BA��I��/4��R���@l9�@��L�:��
��<�7n���m��
�t��`������������V�)<��R��Bi�L������Z���d��R�������S<(�����B�%�@%4$������Td�R��1)�P����W������,'��Ui:��������%��O��+850�}�Mf��l8��f��VVC3�����y�L5�G����`���Cg4���n���������YJ�Z��.3\��P3�2�p[N����Q��3U��y���>@������q$�Tr�j�|��8�x��,��~�����xM�v���5��|�_��rwT��_���8�_���
%8�#|E��!6�y��E��)&�4<Jt������Ki�hI F��Z]��$���JxG3$��N��P[�5����85LV
��Ce	� �����G����)2Q"%"n!���`��k�{��o�#{�����T�������$��|����{�Y�zA`�|��6��{��5�rw�o��*�uz�v�����1!E���R�:*Vkl���:(�\C�5��������Y2�1\����v�nj�)o�Mr�q��������l�����fm�o���6��R����
(����lR
�"��r�����k���3D�����>��f�B�O����.�@`#�
<L��w�8�p(��=C���u&}�����{���}���<���w4�S�s��Y�=	�������}���)�>/�u�8�MZ����������p
D3���p�cITM�
���	�M����Q��>�W�B�`�`��������X�q��>�n"y�`�^U�
~���9\�����oR&.
���8��P�v������+ag��
���9b��X��O��q@(������H�N�>���m��ezHjZ��~�s>:�� ��=�j��IO�V'1��~bg��]k��y�B�4�g|�/�e����}{�9��0G$�������N�OK���*�Z+p�
yo�F�������O�6�k F^���5{��������Y53������O���
r!EJT��~���g?���0�R'��*U����M�&8A��36zz��H��z]Xk��-4�z�NN&�*���?�6��F����������`7���A���i���3eDK�J�o"�/��	��.�-~����B�O���XA3o$S���4>&ffQ�d�����	�a ����d�:�P<;k14:�+�Or�mT���iO�bC+c��J����4_��Q=uC�k5L�qP<F�s��B�#��i���u�����4T����*�D�|��v��A�']~���?/�7m�q�(�e�+��fyo��l��J
��m�p�������y��� �����F�)0{��Daf����&#u�/�
"���E��XG<�����Z���@��-��S.��CX+����y�\�A�prc�HZx+bGV�X�Q�5�fv���zZJ��|R������Mv������������V�*
>��*�5!x�	�U]
z^d���iuA����
X}>H+��/���X��6����|H-�Q&��84)o,�R�>��t.��g��|^n��X-,.����J
�(�J��b���n���N`
�y�`�9��vz~<V^h�F��i^]�����_7���]�2�V���������p�����&������H<k�d��.���`<�*L
�l
!A�p���w"�NG-)��?lN���C���=��	Yl.���t�M-2�U�����0g�$�6#�m]�!7
�C�&w�I�7�x����|��z�J��R3��h:��Z��+�1��M����B�V��@����a���s|��/fu�t8w�E�������v�����:>I��k���B	�	���b�_�3�W6^�	UF=6J
��Sm�t��K)��������c{22G#���v~�2T������D������#�Q�b`����Ex��;�s��0����s����t��;�g)<��5��g�������������U�vYw��I;_`�E�r�c�������na7���������]�;x������Ne���=����j�9n7z�r����wk���1�V*�z����"�����H�xU�H��O�r��K��@f-S5K�����2Ce��<�]c���G:(������Z?���
�5W�yeGp�=g$`%����i�Y�1T�(�c�j3?��,��V��?s kG[���OU�?eA[���+��H���~���?���J��v�`�)W��&=�oW,�ik��7h��w1�%f�5_�nkf���. {+���;�e�^��-��0��:����C$.0-��4�m��C�������b�Wg�R�H/�H/i)|"1�*��Ib1�w�{�X�<^���m��Ai��Z����]Y�u!�,G� g��%g���� g#�U��@�q����$�(�U���"�eQ�Z���6�q.Jc����`��P�E.�-2#)���*l'��6Z���Z���4����q�H=)/1vaM��c f���!�o���(���3W��f��=�3m�	m���x����R�����{	�|
�`����*C&�����������qs�_�y�^�7���5�l���O�h����.��3������@W���U�v��������QK#�D���������17}5����D���%X����?q�����E�
�U�"�'1�����/@�=�`Ys@`�5�a��+�Y^��^�<��.��k��|�%
�L���O��1Z�%I���]���3�	����[�.�A����x�`����9���{ YUh@9������hk��a�������}�A���+;?�x���m����[������gm���>�������9����q�E���U�iv��\�P��5�-W�c�'7�u)���sSMi�s���CN�K5���v�rR�:�-��U���`���,��<E,�T�<�k��:%kY0-y.U��,y2T���.a����Xi�_�U�m���2��g,8c��Xp��M�wz���r�������B�?���Xl�b3�����~�,�so����^���u��l�Ae\6�����l�e3.krYw8t����Z���p�4���l�e3.�q���f\VpY�?FPT���AQ�S�b3�����f,6c�&���Gv.;{��*������f,6c���X��b7������#��Ae\6�����l�e3.kr��{d�2�����dP���l�e3.�q���
.KJ�c�d�Z�V�dW2���f\6�����l�eC\v�^�����+T�e3.�q���f\6��!.�y��|������*c���m�h3F�1����ov>K{��jF�1���f�6c����`��`�;�W[��]��2.�q���f\6���
q��{g�3�G���dP���l�e3.�q����������9�cxgW3���f�6c����w�h5KG���u���wc��ll��|����r��v�[\!;\��[��gXGX'�X�_�_y��~O��P�j�yV����T�c�OK2��E	��zIO�h�����5��YM�����hy2�d�sv��@Yp�L���7!f0�O��3��lb������[�g��r�k����R"�?�n~��<[���^��y������-�������������VHt�L�4�Y{+:��w4.���/�����X!9����a�=p�gX���@{������F��y8
��|�X_��Y�������{�������[����N�:]6�:vr������9����d%��V���j�rX�5\O�5��/�,���J���l8�H��ts���m��|�cQ��A9�Z����P^m�6�����h�cz
}.=�%@y���#JD!��.�#��#�i��'�KFC������rG����]d�K��m�g�		=7���
������2`]��������}����w1��(�:�WW�hd��xng�z�a�!�!�+��'��U+���'X�R��A��!�>�n"�\�O�*��TY�.VX���R�	CVl���_QZC���1ucO+����Y�T��o�,�����T?�������,��~��9@A �c���{��G���B1�O���]�^�+m&�K���������|"��$���-������������������e[����c�x_W;�Ea�O�N�Q��������k5�jh��m)&�������f�����g?���0�R�{p���6dI_��9�6i�M�!�gC��d����E;�����d�|!O��������v}g��cO��NJ������q��gRJ��$D^_l��4����|*�p�4�,����	�q��,�����p1��N��)���]E��c�I�%����C������8�/P���\79>.6P59�Q3YJ�s�iA��Y�vk�����x!,C�����M�y!��}�G=���� ��T?�� !���;H�����+���:��6���/�~�m����|�d1P�b2��g��A�pr�'|���5�1�s����N����`��M�h$���XQ������������Mv�������AEA��������z/��3��E���VXWp�7 C��/�I�����@�a���	�@����r�?8l4���F[H������K�����lF`e�Z��
hyT+N�5��3�pq�1j���N8Xk����������-�7j�5>�GQ�kQ�\=,]�yn���::�������x�U�b�d1A��}�z��o���}#b'������@����^����1{������a2�g���O�<QP�������=A��&��Z8�gBC�<���K��:��p�j����:��+E��i����sT����>����l
aW_r�y-�@�������k�5_-�W\��X1K�����qmZ�Q��a/����;�����u�
'd���c����V�y����f���*����pB}�s;w����U����g���z��1r0|��s���1���>�
 �Y��a����%�|`-�������Z�R��J��n��M�$�z������ZK�Q�O�Q��������6��_��*~;l��X�J��VY�\c?S�������vT&?7�����������^�z��zN��4�����J�aU{���Z��a�@��v�$�J���c�X�6s��	0b6H�6�iH�������;�������N������c�_�>�nUY���:8���X9�Tr��6��O���?�y�+�d��~�� '�e
�f�xr����|A�#��,�����K	^\������;p���ak$�&��]B��:~9������5�|��v�t�(��q`����C�B[���9�}|���0�A�x���o����������.�_`��;��9C�����VgB�+���9���o���}��,w�gh��B`�n��v������c���o��O��~g4����wp �
���������b?��v��4�����4_�1�3Vc�yw��O�����V�����&������<v����|X���{�":���P*�r]���z�� �?@�yO�@�u�@/��N�s|P+�zv���z�c�\i��d���Wf��?������?\#{M���7�{{�x���g�n����r��������x_��b�u��!�[d�n�����Ev����N����t�v�E���f��K&�-0l
D3|��������I3x1�(ixy>�uRz���?0V}���w�~���u�����;w�~������pD�����������o~{�9�5�P�z��{�Z~�#;�9���g�i^�.��"#x���f@1�Z�g���I������O2l�Z���.l�Ha#�;���dp�`���}��l�?9^^��������R��V�����x���7��3�'��C|�6�zC�x����Ix�C�'����������8��T0�����*��;;yh�����/�4[����������i]x�������������n\P
������<��bD�a}���}�W]b#��2�����S�����>�������_��)����b�Z�2/20�m�� �4��7�
�Z���\|D���j�W��{GP��:�-:���t=�Cl��r���P�I���t�)�Iz	�A�����Q�Ejd^�ZF��"�U'��B�#�U; +�U�q+��p+a/,�t�:�|��if[x	������������jH_,�����t 	�[��� �N�
j�:����U�*k]G}j���%LT09;������[B<����/D��b�>p���������?��~��F�@���.��)�|!������=��3Q#$���G N�#����J%��RN���X�B�&�~ �r�=���[�%?I�gq��f��p��`��#w�Az@1_4i�-n?����{�����>9_[�d2���7�h&y][)2���b
 ��Z������UbB&/2���/)�����/�E#��������:��]^ ���p���q���dY��B�=��C�;�F��&�r��.�.������#�{�����Nu�SI����l��H����-�UH���0������a �����3�zV���yn{��\A2������x2�S�eX+'�o�w&>_-�*7�������
K��m��|neW�����SX	�G���6��Q�}x��uk������z��Ha#�a"�[�@l��=���N�-Z#�8�t���"�m�V��/�������3�	nX���/"k�H8j�����������+��9h�~^���H�����d{%y��?���5����� �����*��-��r�(��������j���"�C�i!P\��N�jt�5�����t
�Z����i+��
�ON�P�o.��_��^�am�^��x��������.�����usz��y�"�����S>(hS��whW���
����T�+���4��(���0b��1���1�H!h\�h���A�;�k�B1��8�����4�.�4-L�M��/���^���I�`U�
F#:�z�IC�
#h!������j��!n�k�����38�hh������V&����?g4�0A��D��oC�K�;�P�i ���.@tZ�&��5�p7�-�uC.��'>t����������M�+����Y�C+:���d�B��1{��=�����v��m5V���t����Q�����m�}�s\w�q�C
��p&�xC�'�Y�����0���O�F&�ywxo��&���'�~�����k,��
�9Y���������[j��!R����?�v_��B� P���-��L��l�F��@��\��0jq��ZP	D�m�*t%��Z�0��_�4�������U2��&>��������;>�v�G�����Q�V?����C�j��j��������xn�U-� ���'�����,�����r29I������H@�A�����r������@�����Kj-R��	���7�oh�'b�[����^��������h��_�::;����is$s��^���v��$���z��k6�,�	,�0��DN�Z�:f{������������p����7�*d�����������d�)J�!E>��O���n4x�.��@X�_�<r�\cv{��1V*&P�G{(I��Xd��m�
K���J�������]�������L�\*��0zcdbo��A�J�=:'���Q�,�8�W�
}��U9M������v��hVi����-������������4�����&���f�S��z��H�'r�T*�)jLn?2;�n�?�k+���zZJaO�z(11?��L&|���q"�Z�G_��i��Z��i���Ti�r=.cF�V�����+���[�t�
&� TZ	X����ZPa�p/�N�\B1��<�9W;�������pv({��Fr���]%��6� -�+7!�l^�\��8�L5�H��0�i=��JR��+1�A>{!��/������1���s����}"jWM�%�B)��R�����a0-�4zL���2a�4u�56�?��O��M�@jJ��O����_G��{�+��= ��������]
�G
@�r��w�j��}u����e>tB�H��������=�H��<4�r4�/
���%��GHJY�cE�������h�Y(�>��v����teVBv_9����_������{�t���^���[���^`mS���CR�����(�I�@
�����o���T���p���:Jm��R$������,jS����!������=ia
�k\�Z=�uR��E�AVQ���%*�Wa�K����|q)K��Hme5���q�DN$@��,�K�
�5��N9>����s�(����v���U�f�S���&�]�zq\�|�=��8F��;pN�
u�([�Rc�R9�6�p��c?����k�D�af�,-���<�+����>w��}_O=,���Zd�����K��dw�����V1�i_�B���� 8���V��M�G�}�c��${<�i��@]5z����
R��!��H�����e��`d��=oo���	_}�3��R��_8�`e�u9�+�N	Ce�g��_�a8�����G���pI������En�����:�\k�41���0R�2�A�.p@�h�A}|�K�=��(����:�T���b�o.���4
�f)��e��&+��������5���A��;�t���wC�.�S���#���?��'�����4��r�	i���Z����s`Z��\���#E��|�Q����.�d:�+�0�d������\xu@�yE=sW����h��f{��F�����NA.-����5��4����'4c�����(h�|���8�	n�WX$'����2b�`�/�!�FU02!E���N����(�8%�Mx:��m
>����s�1x�clf��C�M��/���V3�3�j<8,ZHe�*�g�tbYLc%��.Iv}��S��f{J��P��
n����vy9�[��%�$P�����Y�� ���k����D��b���{��D6`H� ��n�L��c ��oR1y�MIv��:t�]�u��f���kGaBbH���9���n1L��gI�����J�q�}!=`���P����Q:�^:�x��S�c�W��S.��
��b�B��[�"�X��P^!E�)���1����5koB��5�0�zI�7">�\c����	�������p��2V,2T���K��iN��M�]&��&>A\���2M!g�w�UpK$��p���s{��b����l�������D:�E=���Ao�vX|H���,lK%�j��C�M!�����G��yD�x��������D������S����Y=�6���M|(���8��a���;���j�����cv*�����P������������	���c���9#e@���*��z�Sh��r{!m}��]�?�%Qu:S��Lvlz%G�|1�A��z�g�8���Y:�S���a�[s\��#���
�X��9
�`�;�4K�����M�!Y����qT���X�CSASlW�A`�y�]pP��������MsyX��������(^JftJXw�6�����H���+DA����s������w����NQ��s�@y�4�/�vS���������-�p`5�Q��r��
;�M�4���z��	jl��z�<�z�O�+w6�l�2�a���+������oG�����A��ap-u��hK]�kx//TF$�G�_�A�`x�e^mf�����+|���1R� 'Z� ~�=��8�����h���m�O���Z�Z��vE���*�kJK6{�� �;�yXJ�����!o�+&����&�����:��e��-���������u��6������������"�t��B�k.�
=�����=�p���9/Ue��j�����+�ovtx�P�XO���UV+��H���&
�V:�^���S;.�����*�{T�z��/?O��:=���"�������2\XE�����|�?%�_]t���1/�H�����K���*!B�Q����y������^�5�,^m�B���dgG���]���Q��Z��#^1B������+.%4O���}.����J�\9}.����&~v'{�;�~��g�%�%��"~K������1�i0��J��$l�<���[�!��W��.
|T�y�����.�L�zN���wU��$b��N���|@����|��RJ�o���^�Q�*Q�J��8;/���������j��
s	��Q�G
���B�m
OpZS`������^���QCE�������U*��M	�4������3���,���jk��p�N���U��G���:�����
�A���x�Z%.�h�b&6�d�S�j�)����3��8�i�������HN(8Qa�L���n�F���^Q��Q���=��E�U����a�.�6P;�����X�NR���u��O���[,y�-������m6?U7a����`����E���y��1B�����
 �A��������Gc�����33Tc|��d�;�{�p�,u�8eo4�rs�m4�5�"Gw��h'h��h�yw�O��>K�4!#��^a��"��kX4����,\��viT�ze[��-���#����	�E�$��OW���
rG������V�Z.j�������:�[����z��F�!�1*v����<�^-���3������;�2q��p�,�-
���@JZ��s�k,���3��d�X�oH
H-e	�W��|C{b�:�{r;,V!��jXg�Q/Z�E�h������EF�?e$���/�_
Nu���M
Zl%L�n���@N<�n]~�i^��n�{��P�-U"��j���	�h�<�{��l7G
�*&_�����T��%(3��#��������������P@�P�w�D=lYds��`
H>���:%��  �{_��.:������V���:r���������C�%�o�X�A"k�*1�"�/"p3�
Qks`�0�=��Y��o��O���6�a9G�7���b��&�)B�b�2��=�S����8|������{yAnL���2����Jwz�X4�mno��7���������<!��b9��}�7����U*��._��t;;�,�Y�~�?\�=Q.�o�S��p����fm@N<�d�c��$R�P�D�������o�K��q��L~O^�h���=����Y	5u��1E������A��Oh �t�%OP�h
�z>��^�������7Z`j��p:h
�A�mIA��p?�H=��	��v����+	�����
����Hw��"��T@}]���h��n�D��u`��`�A/�Eft�nh����Tc|�'X�I"U�r\��G�a��*���D�6�����|t5r��P���.��h�i�����W���8�����]��	?6�w�c�"�8��I���_�~)W�3����g�"����oJ�C$��"�5[?\}:�`L���U5�MDc���]�I
�_~�?Eq��"������M�0L�v0a1�����u�p�4�@�+���S0��}S���fy:TVh��A�$m|����lP�����Br�S:��N�
��
��JYS��hD'�����%�� I���M��������;_3����4�4wF�+I=���m�=�����(���������J/#�K4>�@�q�����~����r��
�{������C��_���������"��m�W�p1q
,�>�9z����"����p�Ma*��o��'?as�W��v���:�-�)y�<���x�������� �aK�1?�Xl�s����C���T%o2���dx�t��9f��3v�D�������W)���|�5-��
����'�{N�i�����{�i��pg���	?�	�8
h�]o4�Z�!�DO��$��*#�IHK�
�P|�{<����y�O��
�h=f5�s:EG=aU��	�.�0
��QbO@�8�!���i���_[���'e�������>'��$TGoE'����d���eQJeh���b<���p��l^�����i�����>�������3�Qc~����=�3���z������BD��\��l���%�^��s����w�O��Si�������YX�;+�<
RA�
��y|,�Y���z�B�$��J[��Y'L��3�w�9������s�]y�h0���SD��h����^T l�|��\�#�����$:����A������h�����3�.���V�M=:���c�Cp�><���-����S�����h��6�����i�n�:CE
�_�^C=�����d�w2-�r]q�8{��ep��_I����6�p7L�
U�g�P�)�";5�'��l��f��3I�IG�8_� ��ZR��8:���E����� �rme����5��~7������r�3ft�J=�TDG���
Z���{(�G��@�������_>\��9��h�^��F�����B��|��$"UK�%�����%�����h:���A�-����l�^M`�8w��h�������r9v����k�\4L��Q������������	aO
�
1���]*1(��<"�h`�1��O����,Lq<J-�����'�\?�g��0��$]G���A'�Qf�����Wg7�l]5�o��^�4��.�7�N�:v�^B�>��qz���^c��9��H�U�-�F����H����FEq%�BQ�����<v4.��\�?��L/(=i:@��3�j��)UA����>f<O��!F�P.x�)�k'�
	R8!�2/4��EO���w5��[d'~��S,������{@V5!g������@��y}��{�U������p�v� ��c���q�|���&��K�F���&}f�_FE��|�G�X�,�����c�4&�����������ep��#��`?E_(eFywO}�=�X!%��>�J�)7���z�4�@���F��
_�!�4KT<�(��H��a�NvEg�C��{���f�d���fzC�BS���hx��e.P����^|#L2�
M�G]b��CN����r&���
���MFl<�'%��L(b�z[$50{�
�1�<��.SQE2����n�G�-u��T#��T��QDEM�S�/qZa�z��i�oQ$d'L;+����kL�������Q�`|t��Q<5���h����%X��C���t|��&��O����u�#�.�	�8��@l�;���u&.�g6/�D�,o�}km��5C
�����G�p�3t�`�+�O����/v���"u}rBb��LuW6c�:�I���&
:������/n�5��+��x��S*�=)w��|��Op���
������PZ_A�5WW��(V�]��2�Siz��*dK)��{{� u��q��>\��d���lfMzr�������	��4tA[��#_`�{�Sk���g�g������O:w\6e�`G��#.�T�%"y��E���\yt��Y���J�����N�������8	C�5��^��Z�|%g9^v<�Wz%H�Ls�Hb,_�<�)^w:���x���I�j���:�.���w@|��N�+�������e�����p>�L�y��m���n�4]nz�6P��*�c#������aiz�Zw��\��vR#>�_���`I�tl'i���{c��C�n����P��Y�����A��n��X(��0kAB����2I0a=-~�(
Q<�H�_E�yTw���u#�vI�fT��f4��%m�]�G����4������k��0B��}+���L�y�{?/�l�q�P��G���V�7-�M��B�����S��n��I�������;�<�f
�|8��O���&^�VN�S4?�?'��F�.�Te��7���/�Y������t7~������=#��`�"���?�+����*-����Nl�a���h;��=���y;����������oY=B2������B`z�V�d�I���ZhA;zTEd�/j��#����� ���L>;�0T�{���rM�<�q�#��)���Z5�� K���0�
�^yA�������o'�Qf
 C�������=��	Q%j)���STi[�Q�yMk��A(�0�B�\k�}3�b�� G��)bc���,���������QL�*Ho�|>}���A&�$�d�<�gbq�!��\�"t�7f�y.�6��9txHF5([$
�c�����r��������� W��)���&"����j�����	���3�X�>������
�I8�QHV#6�6�S��c�/������bw(c���Y����G����5����I;�*���Y���������Q�W�V���AB���r��i���-��_����������Z1|��3�?���2�=����e,��B�4":]��%���#��(
2Q�#�D��
��K�
<K&95�j)�mX��q�W�`�k/����%i�XK��(@9�v`�;�,����`|0V�cC�Fg�[	Q$��Z(-.4K�h���]����>�����BWB-�b�!5#����[y&��Q���G������ .YI�3ONr�SaY!>�S��Y$������G���kf�2�8A�Y�y�Z!B�~����T�(*/*O�)�����O��@-�(P�P���N�&+����n LqP!Jq�f>N��b)�
��M�=�s�EzNb�)0�Y��K��\
�[D���QIR������V^
��i�F"�����)��&�a�����?5�=F�
�vW$�V8��0�C�H�j��\�L���$���9�E�lQE,^9����$����|P����D�h�>Eee��d��������f&�
Z�x^�N+o,Z�j��t�(I��%���)� �@���s&��<�"�
&Z�<�����]�i�_���	�����4xQ '>'��P�I������|��J��|q9`�R	�.b�_2#`]S,1��b�Uk�;�7�1b�e������\������/���P��!�#��y�[\x��mD1���3�R5QJl�Q�zR-v\��&�J|(���e�b~���g����(�^�V�����R�$f�l�-�d[	�Pd�xZ((���
2��9>�hJ���p"�L��>y�enw��*���c(�;��<�,�������Zf 2��O�����HB?�]�`	��$$�<
.+�F��Q�b�^��XO���%��1�
�B��d���
,E*.��{�?���>j� �Q�.%����'B$�����������FCWgoM��7��_��z��'��N���7���V$���)&^��
n�����I�C\��Y��������vH"q��u����y�C}c��]G�W�JL�S���������W�����e���]�K<�=��=�x����������Wq����F�*A8a��nFCe|1�D���}v���kEz���q
�����K<������;��uR3�"s�y��6�����L}%�X�,���\sd}y=�cj	���7�^��q'Z�fo��9�P
���7��h����`�-��M�lN�TD�����E
���$��PC/��5q8�\��sp!GA+���G��RE:6�e��!�>����r��������-	R�e%�������ez�R@��u�O��-�����W������:�;GS������1U\P+X���&�?�_��K��\����	3��U��N�hf��Rr�*H�T�� �<�G�\���mi#��l*0�H�#��H:o���K�����O�����\���Q���4�hh���x+�Z�+��N��iz������2�W��c������+*�!gh��j���(!��V,JX1MXipB���|D 
Q�E�U�"�����Op,��V>������W��)���&�],�j�m`�n{��H����I6R��
h�u�)B$@=��Z�`�)��%�x����LT���5����+��>�3�*[�/1j���*�M�������im�P:������O%�}����3���P�)(!mP�|xy����TWV�}qf������STR�<�@���n�;�7e����3����u�w�n9���������v������9�2�g2o]�m>����wH�)��*yK���e�&^��>���;��H�������f�W�]F���H��� %O�9�8a�Z�qW�0����X�n�tF�pEtp�����_p�F�
�#�_�PG�����x���(��E�\�3r�a���M����
l���j!�a��8|�� o��C�M�:-�?�������^v�����E�c��������T|>��$}
��Lg�O��,����\��E\uQ�G�^��H�`<���KR'US����S�mT{CR��)�3%�l�x���EX4"
��T����l����Ef9��9�z����P4)��kv\�1�Kad���(B���y�������^���^��^V��_oXze���
��u?@e2	gR����|S$�\�W��I��t����B���pIg?��LJX���)�p���3��(h?�U��f��F,�<~������*��9_�kL�����7��+Q��h���v��\���A����!�*j�n�8��3��b�K�t��{gg����N�s��������������*��\>��=_�um,�R��@H46,>��pt����I�>����k<y'Tu;/�H��6.DKb�z�'Y�A���x(��<8^��d<A,������������/��a�^��5l�������S�i���N2z"���V��c��LD���
�l
&o�L.��	AP�:inJnP��������P�1�$i�&(
���jDV����;}_�y)vqSG�x����~i��]z z�*�������
H��{���=�H���;PyQ�����Q:�9i/2$�hu���j��7�����A��a ��}��0��G����#JZ��_��?�1C!I��&�������n�����,>�$���:#����lF��`��[h�jK�q'�1�
��F�c��{;�<�3����=���I#�	�����H4��8b{L�a��������Z-�d�������ZLEp�h�z5Y�
����H(O	g#N�#�8�-+�Qt�,4Rb�<,8�Q��	y\��04;T���]�1���v�����=�P�_7����0���z��!���>X0Z�:cm+�;���JzOp@B�6c�C�4��TE��,)D����xV�<	�0C>���N�4�����G\�I�3�4�/��OI�a�!�F'�gh&��^Mn�l�Q�E8�E3!$MSx��I��<����,`a�fF��2�f����G\���N�*���J�"���<qo����9h����������m�mUdU�SIU����:�Q#�<L]�+tN��R����R\�4� s�
�-k�������j��X�Z�V�UD��U�\f4����B���,T-� r���lE
kar���5[d�*�yL�Ph������;l��g%�8O�������w�z{+��j]	ss��J1Zd�h��b�PQBC*SRF�����M,�?�Q�-�dA&��K,�U/�U�/O�^���_���KK5~�d��k0A�D,�M/���"e���kV�7�!��@���b����Z%�������*�/��Hy��,nQ����+�Z�j���U�������J���~���6�����f~_j<�����~�h�
��e���m�����*C�{�����i�^��6��A��,�^�# m�1Mx�)S-��'���S '��mP�)�Z+�q!�h�������+/B��b�CH���PPzx������tD�6F������+�F�o���
���3�%�t5��������e�b����?b�F�Y'��%DXQ����u0�?�J.�M��&H:��ZA"�*��*H==�o/��8�������
���������v� #H�T�B
�.�D����'�����g6%�6��,���@��j��J����*]����e���}A����}M�<UE���9��[���B�T�htj;��(�p${�Gw�
sy]E����sBj��8TYh�#�\��g�A����G,c��y�<���Q����N�p(��<�1���b����s�>� PQ��
C�F�I3��*h��<(��Bo��!O���-$���6)�b�d�.�����Op3=�N��Pf��iy!Rt�u��.?��O]y�Bc�h�HR���E�"������X�SN� a"<�(Y�����Ex�C�����k�|����������/�u|=��p8t2@��F0
�s)�������&��������"��O��]DvTA���c<�P���7�����[.��P����UQRz5�}Q�J��U���I:0����������jI44,Ar�y�I��k��b]A���zN�8|U�0���*����1�y�zPq����n%aQ���������
����t@n �a���F /��x������<l�]��*?�G��Z�jO����AFDYUP���2�21��Pzj�%9],��56��aq2.T)���p��Aw@�$5���M�O7I���<a�wh��(��d�C�E~������ �4p�P]��.3�?��G������]\��x�$��O�%�]^�(�d����a�U�����T�,]�����N���{vq���ag7�8����|�q[N� J��)8@��vv��� Fg�[�O���T���U�
���I[�O~A/������3�BA��w;����x$|�D�=N���Bo��K���=R0]j��Jt�z��*5�}b��*�)�	���K�m�B��C�r�������0k����)���zx���-��|{l\��z6p��	��cFS2�D
�7\*�]��t��N���r�h�=p��%���3�n��S+�da��|E����:�`#�����������	����6�Bk�����4�� J�$p)�s����iHi���E,
����"RZ�)�v��,v���c��t1Q	����i*[��$��i*���7 �4Cf ^but!X��l|v��I)�_@���.��c�j���
B��4��"�������!���%C�c}��)�X�*W��\�/�'�d���}�O��>�'�d���}�O��>�'�d���}�O��>�'�d���}�O��>�'�|g��Ta���
v23-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patchtext/x-diff; name=v23-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patchDownload
From 058ac1e8c9ff85ee1100e7996ddd71ee8086d0d1 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:05:02 +0900
Subject: [PATCH v23 01/15] Add a syntax to create Incrementally Maintainable
 Materialized Views

Allow to create Incrementally Maintainable Materialized View (IMMV)
by using INCREMENTAL option in CREATE MATERIALIZED VIEW command
as follow:

     CREATE [INCREMANTAL] MATERIALIZED VIEW xxxxx AS SELECT ....;
---
 src/backend/parser/gram.y     | 32 +++++++++++++++++++++-----------
 src/include/nodes/primnodes.h |  1 +
 src/include/parser/kwlist.h   |  1 +
 3 files changed, 23 insertions(+), 11 deletions(-)

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 10da5c5c51..9c2382fed7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -440,6 +440,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target create_mv_target
+%type <boolean>	incremental
 
 %type <defelt>	createfunc_opt_item common_func_opt_item dostmt_opt_item
 %type <fun_param> func_arg func_arg_with_default table_func_column aggr_arg
@@ -669,7 +670,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
 	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
-	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+	INCLUDING INCREMENT INCREMENTAL INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
 	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
 	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -4257,30 +4258,32 @@ opt_with_data:
  *****************************************************************************/
 
 CreateMatViewStmt:
-		CREATE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
+		CREATE OptNoLog incremental MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $7;
-					ctas->into = $5;
+					ctas->query = $8;
+					ctas->into = $6;
 					ctas->objtype = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = false;
 					/* cram additional flags into the IntoClause */
-					$5->rel->relpersistence = $2;
-					$5->skipData = !($8);
+					$6->rel->relpersistence = $2;
+					$6->skipData = !($9);
+					$6->ivm = $3;
 					$$ = (Node *) ctas;
 				}
-		| CREATE OptNoLog MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
+		| CREATE OptNoLog incremental MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $10;
-					ctas->into = $8;
+					ctas->query = $11;
+					ctas->into = $9;
 					ctas->objtype = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = true;
 					/* cram additional flags into the IntoClause */
-					$8->rel->relpersistence = $2;
-					$8->skipData = !($11);
+					$9->rel->relpersistence = $2;
+					$9->skipData = !($12);
+					$9->ivm = $3;
 					$$ = (Node *) ctas;
 				}
 		;
@@ -4297,9 +4300,14 @@ create_mv_target:
 					$$->tableSpaceName = $5;
 					$$->viewQuery = NULL;		/* filled at analysis time */
 					$$->skipData = false;		/* might get changed later */
+					$$->ivm = false;
 				}
 		;
 
+incremental:	INCREMENTAL				{ $$ = true; }
+				| /*EMPTY*/				{ $$ = false; }
+		;
+
 OptNoLog:	UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
 		;
@@ -15584,6 +15592,7 @@ unreserved_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
@@ -16136,6 +16145,7 @@ bare_label_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index c04282f91f..a0af94eb49 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -117,6 +117,7 @@ typedef struct IntoClause
 	char	   *tableSpaceName; /* table space to use, or NULL */
 	Node	   *viewQuery;		/* materialized view's SELECT query */
 	bool		skipData;		/* true for WITH NO DATA */
+	bool		ivm;			/* true for WITH IVM */
 } IntoClause;
 
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf876..2cafb4e7fe 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -205,6 +205,7 @@ PG_KEYWORD("in", IN_P, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("incremental", INCREMENTAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD, BARE_LABEL)
-- 
2.17.1

v23-0002-Add-relisivm-column-to-pg_class-system-catalog.patchtext/x-diff; name=v23-0002-Add-relisivm-column-to-pg_class-system-catalog.patchDownload
From 906915d5215de7d3271e69d81408e875d5713bf8 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:07:23 +0900
Subject: [PATCH v23 02/15] Add relisivm column to pg_class system catalog

If this boolean column is true, a relations is Incrementally Maintainable
Materialized View (IMMV). This is set when IMMV is created.
---
 src/backend/catalog/heap.c          |  1 +
 src/backend/catalog/index.c         |  1 +
 src/backend/utils/cache/lsyscache.c | 24 ++++++++++++++++++++++++
 src/backend/utils/cache/relcache.c  |  2 ++
 src/include/catalog/pg_class.h      |  3 +++
 src/include/utils/lsyscache.h       |  1 +
 src/include/utils/rel.h             |  2 ++
 7 files changed, 34 insertions(+)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 83746d3fd9..ade3586779 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -963,6 +963,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
+	values[Anum_pg_class_relisivm - 1] = BoolGetDatum(rd_rel->relisivm);
 	if (relacl != (Datum) 0)
 		values[Anum_pg_class_relacl - 1] = relacl;
 	else
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 26bfa74ce7..763f442a1d 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -961,6 +961,7 @@ index_create(Relation heapRelation,
 	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
 	indexRelation->rd_rel->relam = accessMethodObjectId;
 	indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid);
+	indexRelation->rd_rel->relisivm = false;
 
 	/*
 	 * store index's pg_class entry
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 6bba5f8ec4..8d5214fb90 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2013,6 +2013,30 @@ get_rel_relispartition(Oid relid)
 		return false;
 }
 
+/*
+ * get_rel_relisivm
+ *
+ *		Returns the relisivm flag associated with a given relation.
+ */
+bool
+get_rel_relisivm(Oid relid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp);
+		bool		result;
+
+		result = reltup->relisivm;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return false;
+}
+
 /*
  * get_rel_tablespace
  *
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..5b130f0ed2 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1865,6 +1865,8 @@ formrdesc(const char *relationName, Oid relationReltype,
 
 	/* ... and they're always populated, too */
 	relation->rd_rel->relispopulated = true;
+	/* ... and they're always no ivm, too */
+	relation->rd_rel->relisivm = false;
 
 	relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
 	relation->rd_rel->relpages = 0;
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fef9945ed8..2058978b89 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -119,6 +119,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
 	/* is relation a partition? */
 	bool		relispartition BKI_DEFAULT(f);
 
+	/* is relation a matview with ivm? */
+	bool		relisivm BKI_DEFAULT(f);
+
 	/* link to original rel during table rewrite; otherwise 0 */
 	Oid			relrewrite BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_class);
 
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 77871aaefc..09346aaa17 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -137,6 +137,7 @@ extern Oid	get_rel_namespace(Oid relid);
 extern Oid	get_rel_type_id(Oid relid);
 extern char get_rel_relkind(Oid relid);
 extern bool get_rel_relispartition(Oid relid);
+extern bool get_rel_relisivm(Oid relid);
 extern Oid	get_rel_tablespace(Oid relid);
 extern char get_rel_persistence(Oid relid);
 extern Oid	get_transform_fromsql(Oid typid, Oid langid, List *trftypes);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b4faa1c123..b5028f3449 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -649,6 +649,8 @@ RelationGetSmgr(Relation rel)
  */
 #define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated)
 
+#define RelationIsIVM(relation) ((relation)->rd_rel->relisivm)
+
 /*
  * RelationIsAccessibleInLogicalDecoding
  *		True if we need to log enough information to have access via
-- 
2.17.1

v23-0003-Add-new-deptype-option-m-in-pg_depend-system-cat.patchtext/x-diff; name=v23-0003-Add-new-deptype-option-m-in-pg_depend-system-cat.patchDownload
From 245223e82c4d0d6fa611bfd1a59536f9608585b1 Mon Sep 17 00:00:00 2001
From: Takuma Hoshiai <takuma.hoshiai@gmail.com>
Date: Fri, 17 Jan 2020 16:04:14 +0900
Subject: [PATCH v23 03/15] Add new deptype option 'm' in pg_depend system
 catalog

The deptype option 'm' mean specific database obejects referenced Incrementally
Maintainable Materialized View(IMMV). If set NO DATA flag to IMVM, these
database objects must be dropped.
---
 src/backend/catalog/dependency.c | 2 ++
 src/include/catalog/dependency.h | 3 ++-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 76b65e39c4..288ad44d4e 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -596,6 +596,7 @@ findDependentObjects(const ObjectAddress *object,
 			case DEPENDENCY_NORMAL:
 			case DEPENDENCY_AUTO:
 			case DEPENDENCY_AUTO_EXTENSION:
+			case DEPENDENCY_IMMV:
 				/* no problem */
 				break;
 
@@ -913,6 +914,7 @@ findDependentObjects(const ObjectAddress *object,
 				subflags = DEPFLAG_AUTO;
 				break;
 			case DEPENDENCY_INTERNAL:
+			case DEPENDENCY_IMMV:
 				subflags = DEPFLAG_INTERNAL;
 				break;
 			case DEPENDENCY_PARTITION_PRI:
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..d89b5dd774 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -36,7 +36,8 @@ typedef enum DependencyType
 	DEPENDENCY_PARTITION_PRI = 'P',
 	DEPENDENCY_PARTITION_SEC = 'S',
 	DEPENDENCY_EXTENSION = 'e',
-	DEPENDENCY_AUTO_EXTENSION = 'x'
+	DEPENDENCY_AUTO_EXTENSION = 'x',
+	DEPENDENCY_IMMV = 'm'
 } DependencyType;
 
 /*
-- 
2.17.1

v23-0004-Allow-to-prolong-life-span-of-transition-tables-.patchtext/x-diff; name=v23-0004-Allow-to-prolong-life-span-of-transition-tables-.patchDownload
From 3a5edb458d8c79d3462e1c0ca9d438489b7baa00 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:09:45 +0900
Subject: [PATCH v23 04/15] Allow to prolong life span of transition tables
 until transaction end

Originally, tuplestores of AFTER trigger's transition tables were
freed for each query depth. For our IVM implementation, we would like
to prolong life of the tuplestores because we have to preserve them
for a whole query assuming that some base tables might be changed
in some trigger functions.
---
 src/backend/commands/trigger.c | 80 +++++++++++++++++++++++++++++++++-
 src/include/commands/trigger.h |  2 +
 2 files changed, 80 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index fc0a4b2fa7..049007a484 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3570,6 +3570,10 @@ typedef struct AfterTriggerEventList
  * end of the list, so it is relatively easy to discard them.  The event
  * list chunks themselves are stored in event_cxt.
  *
+ * prolonged_tuplestored is a list of transition table tuplestores whose
+ * life are prolonged to the end of the outmost query instead of each nested
+ * query.
+ *
  * query_depth is the current depth of nested AfterTriggerBeginQuery calls
  * (-1 when the stack is empty).
  *
@@ -3635,6 +3639,7 @@ typedef struct AfterTriggersData
 	SetConstraintState state;	/* the active S C state */
 	AfterTriggerEventList events;	/* deferred-event list */
 	MemoryContext event_cxt;	/* memory context for events, if any */
+	List   *prolonged_tuplestores;	/* list of prolonged tuplestores */
 
 	/* per-query-level data: */
 	AfterTriggersQueryData *query_stack;	/* array of structs shown below */
@@ -3670,6 +3675,7 @@ struct AfterTriggersTableData
 	bool		closed;			/* true when no longer OK to add tuples */
 	bool		before_trig_done;	/* did we already queue BS triggers? */
 	bool		after_trig_done;	/* did we already queue AS triggers? */
+	bool		prolonged;			/* are transition tables prolonged? */
 	AfterTriggerEventList after_trig_events;	/* if so, saved list pointer */
 	Tuplestorestate *old_tuplestore;	/* "old" transition table, if any */
 	Tuplestorestate *new_tuplestore;	/* "new" transition table, if any */
@@ -4448,6 +4454,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
 }
 
 
+/*
+ * SetTransitionTablePreserved
+ *
+ * Prolong lifespan of transition tables corresponding specified relid and
+ * command type to the end of the outmost query instead of each nested query.
+ * This enables to use nested AFTER trigger's transition tables from outer
+ * query's triggers.  Currently, only immediate incremental view maintenance
+ * uses this.
+ */
+void
+SetTransitionTablePreserved(Oid relid, CmdType cmdType)
+{
+	AfterTriggersTableData *table;
+	AfterTriggersQueryData *qs;
+	bool		found = false;
+	ListCell   *lc;
+
+	/* Check state, like AfterTriggerSaveEvent. */
+	if (afterTriggers.query_depth < 0)
+		elog(ERROR, "SetTransitionTablePreserved() called outside of query");
+
+	qs = &afterTriggers.query_stack[afterTriggers.query_depth];
+
+	foreach(lc, qs->tables)
+	{
+		table = (AfterTriggersTableData *) lfirst(lc);
+		if (table->relid == relid && table->cmdType == cmdType &&
+			table->closed)
+		{
+			table->prolonged = true;
+			found = true;
+		}
+	}
+
+	if (!found)
+		elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType);
+}
+
+
 /*
  * GetAfterTriggersTableData
  *
@@ -4642,6 +4687,7 @@ AfterTriggerBeginXact(void)
 	 */
 	afterTriggers.firing_counter = (CommandId) 1;	/* mustn't be 0 */
 	afterTriggers.query_depth = -1;
+	afterTriggers.prolonged_tuplestores = NIL;
 
 	/*
 	 * Verify that there is no leftover state remaining.  If these assertions
@@ -4802,11 +4848,29 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
 		ts = table->old_tuplestore;
 		table->old_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+		{
+			if (table->prolonged && afterTriggers.query_depth > 0)
+			{
+				MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext);
+				afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts);
+				MemoryContextSwitchTo(oldcxt);
+			}
+			else
+				tuplestore_end(ts);
+		}
 		ts = table->new_tuplestore;
 		table->new_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+		{
+			if (table->prolonged && afterTriggers.query_depth > 0)
+			{
+				MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext);
+				afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts);
+				MemoryContextSwitchTo(oldcxt);
+			}
+			else
+				tuplestore_end(ts);
+		}
 		if (table->storeslot)
 			ExecDropSingleTupleTableSlot(table->storeslot);
 	}
@@ -4818,6 +4882,18 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
 	 */
 	qs->tables = NIL;
 	list_free_deep(tables);
+
+	/* Release prolonged tuplestores at the end of the outmost query */
+	if (afterTriggers.query_depth == 0)
+	{
+		foreach(lc, afterTriggers.prolonged_tuplestores)
+		{
+			ts = (Tuplestorestate *) lfirst(lc);
+			if (ts)
+				tuplestore_end(ts);
+		}
+		afterTriggers.prolonged_tuplestores = NIL;
+	}
 }
 
 
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 9ef7f6d768..0cfb0b3c5c 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -251,6 +251,8 @@ extern void AfterTriggerEndSubXact(bool isCommit);
 extern void AfterTriggerSetState(ConstraintsSetStmt *stmt);
 extern bool AfterTriggerPendingOnRel(Oid relid);
 
+extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType);
+
 
 /*
  * in utils/adt/ri_triggers.c
-- 
2.17.1

v23-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patchtext/x-diff; name=v23-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patchDownload
From ba59f8e8c1c1e19876eeb885bc2febe59356e602 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Wed, 11 Nov 2020 17:01:25 +0900
Subject: [PATCH v23 05/15] Add Incremental View Maintenance support to pg_dump

Support CREATE INCREMENTAL MATERIALIZED VIEW syntax.
---
 src/bin/pg_dump/pg_dump.c        | 42 ++++++++++++++++++++++++--------
 src/bin/pg_dump/pg_dump.h        |  1 +
 src/bin/pg_dump/t/002_pg_dump.pl | 15 ++++++++++++
 3 files changed, 48 insertions(+), 10 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 90ac445bcd..17e0a2193d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6268,6 +6268,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_ispartition;
 	int			i_partbound;
 	int			i_amname;
+	int			i_isivm;
 
 	/*
 	 * Find all the tables and table-like objects.
@@ -6299,6 +6300,7 @@ getTables(Archive *fout, int *numTables)
 		char	   *ispartition = "false";
 		char	   *partbound = "NULL";
 		char	   *relhasoids = "c.relhasoids";
+		char	   *isivm = "false";
 
 		PQExpBuffer acl_subquery = createPQExpBuffer();
 		PQExpBuffer racl_subquery = createPQExpBuffer();
@@ -6326,6 +6328,10 @@ getTables(Archive *fout, int *numTables)
 		if (fout->remoteVersion >= 120000)
 			relhasoids = "'f'::bool";
 
+		/* The information about incremental view maintenance */
+		if (fout->remoteVersion >= 150000)
+			isivm = "c.relisivm";
+
 		/*
 		 * Left join to pick up dependency info linking sequences to their
 		 * owning column, if any (note this dependency is AUTO as of 8.2)
@@ -6384,7 +6390,8 @@ getTables(Archive *fout, int *numTables)
 						  "AS changed_acl, "
 						  "%s AS partkeydef, "
 						  "%s AS ispartition, "
-						  "%s AS partbound "
+						  "%s AS partbound, "
+						  "%s AS isivm "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -6413,6 +6420,7 @@ getTables(Archive *fout, int *numTables)
 						  partkeydef,
 						  ispartition,
 						  partbound,
+						  isivm,
 						  RELKIND_SEQUENCE,
 						  RELKIND_PARTITIONED_TABLE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
@@ -6466,7 +6474,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
-						  "NULL AS partbound "
+						  "NULL AS partbound, "
+						  "false AS isivm "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -6519,7 +6528,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
-						  "NULL AS partbound "
+						  "NULL AS partbound, "
+						  "false AS isivm "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -6572,7 +6582,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
-						  "NULL AS partbound "
+						  "NULL AS partbound, "
+						  "false AS isivm "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -6623,7 +6634,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
-						  "NULL AS partbound "
+						  "NULL AS partbound, "
+						  "false AS isivm "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -6672,7 +6684,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
-						  "NULL AS partbound "
+						  "NULL AS partbound, "
+						  "false AS isivm "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -6720,7 +6733,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
-						  "NULL AS partbound "
+						  "NULL AS partbound, "
+						  "false AS isivm "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -6768,7 +6782,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
-						  "NULL AS partbound "
+						  "NULL AS partbound, "
+						  "false AS isivm "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -6815,7 +6830,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
-						  "NULL AS partbound "
+						  "NULL AS partbound, "
+						  "false AS isivm "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -6887,6 +6903,7 @@ getTables(Archive *fout, int *numTables)
 	i_ispartition = PQfnumber(res, "ispartition");
 	i_partbound = PQfnumber(res, "partbound");
 	i_amname = PQfnumber(res, "amname");
+	i_isivm = PQfnumber(res, "isivm");
 
 	if (dopt->lockWaitTimeout)
 	{
@@ -7000,6 +7017,9 @@ getTables(Archive *fout, int *numTables)
 		/* foreign server */
 		tblinfo[i].foreign_server = atooid(PQgetvalue(res, i, i_foreignserver));
 
+		/* Incremental view maintenance information */
+		tblinfo[i].isivm = (strcmp(PQgetvalue(res, i, i_isivm), "t") == 0);
+
 		/*
 		 * Read-lock target tables to make sure they aren't DROPPED or altered
 		 * in schema before we get around to dumping them.
@@ -15990,9 +16010,11 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
-		appendPQExpBuffer(q, "CREATE %s%s %s",
+		appendPQExpBuffer(q, "CREATE %s%s%s %s",
 						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
 						  "UNLOGGED " : "",
+						  tbinfo->relkind == RELKIND_MATVIEW && tbinfo->isivm ?
+						  "INCREMENTAL " : "",
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f5e170e0db..ae8f24bb78 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -298,6 +298,7 @@ typedef struct _tableInfo
 	bool		dummy_view;		/* view's real definition must be postponed */
 	bool		postponed_def;	/* matview must be postponed into post-data */
 	bool		ispartition;	/* is table a partition? */
+	bool		isivm;			/* is incrementally maintainable materialized view? */
 
 	/*
 	 * These fields are computed only if we decide the table is interesting
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c5d8915be8..9d1776a506 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2160,6 +2160,21 @@ my %tests = (
 		  { exclude_dump_test_schema => 1, no_toast_compression => 1, },
 	},
 
+	'CREATE MATERIALIZED VIEW matview_ivm' => {
+		create_order => 21,
+		create_sql   => 'CREATE INCREMENTAL MATERIALIZED VIEW dump_test.matview_ivm (col1) AS
+					   SELECT col1 FROM dump_test.test_table;',
+		regexp => qr/^
+			\QCREATE INCREMENTAL MATERIALIZED VIEW dump_test.matview_ivm AS\E
+			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QFROM dump_test.test_table\E
+			\n\s+\QWITH NO DATA;\E
+			/xm,
+		like =>
+		  { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, },
+	},
+
 	'CREATE POLICY p1 ON test_table' => {
 		create_order => 22,
 		create_sql   => 'CREATE POLICY p1 ON dump_test.test_table
-- 
2.17.1

v23-0006-Add-Incremental-View-Maintenance-support-to-psql.patchtext/x-diff; name=v23-0006-Add-Incremental-View-Maintenance-support-to-psql.patchDownload
From b100526d02a873fe3d4ee25ddd4d6e0b7346a99a Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:21:54 +0900
Subject: [PATCH v23 06/15] Add Incremental View Maintenance support to psql

Add tab completion and meta-command output for IVM.
---
 src/bin/psql/describe.c     | 32 +++++++++++++++++++++++++++++++-
 src/bin/psql/tab-complete.c | 14 +++++++++-----
 2 files changed, 40 insertions(+), 6 deletions(-)

diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ba658f731b..0518c88b5f 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1657,6 +1657,7 @@ describeOneTableDetails(const char *schemaname,
 		char		relpersistence;
 		char		relreplident;
 		char	   *relam;
+		bool		isivm;
 	}			tableinfo;
 	bool		show_column_details = false;
 
@@ -1669,7 +1670,26 @@ describeOneTableDetails(const char *schemaname,
 	initPQExpBuffer(&tmpbuf);
 
 	/* Get general table info */
-	if (pset.sversion >= 120000)
+	if (pset.sversion >= 150000)
+	{
+		printfPQExpBuffer(&buf,
+						  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+						  "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, "
+						  "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
+						  "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
+						  "c.relpersistence, c.relreplident, am.amname, "
+						  "c.relisivm\n"
+						  "FROM pg_catalog.pg_class c\n "
+						  "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+						  "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
+						  "WHERE c.oid = '%s';",
+						  (verbose ?
+						   "pg_catalog.array_to_string(c.reloptions || "
+						   "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+						   : "''"),
+						  oid);
+	}
+	else if (pset.sversion >= 120000)
 	{
 		printfPQExpBuffer(&buf,
 						  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
@@ -1853,6 +1873,10 @@ describeOneTableDetails(const char *schemaname,
 			(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
 	else
 		tableinfo.relam = NULL;
+	if (pset.sversion >= 150000)
+		tableinfo.isivm = strcmp(PQgetvalue(res, 0, 15), "t") == 0;
+	else
+		tableinfo.isivm = false;
 	PQclear(res);
 	res = NULL;
 
@@ -3630,6 +3654,12 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
 			printTableAddFooter(&cont, buf.data);
 		}
+
+		/* Incremental view maintance info */
+		if (verbose && tableinfo.relkind == RELKIND_MATVIEW && tableinfo.isivm)
+		{
+			printTableAddFooter(&cont, _("Incremental view maintenance: yes"));
+		}
 	}
 
 	/* reloptions, if verbose */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d6bf725971..9fb97596dc 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1055,6 +1055,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"INCREMENTAL MATERIALIZED VIEW", NULL, NULL, &Query_for_list_of_matviews, THING_NO_DROP | THING_NO_ALTER},
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2698,7 +2699,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
-		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
+		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW", "INCREMENTAL MATERIALIZED VIEW");
 	/* Complete PARTITION BY with RANGE ( or LIST ( or ... */
 	else if (TailMatches("PARTITION", "BY"))
 		COMPLETE_WITH("RANGE (", "LIST (", "HASH (");
@@ -2997,13 +2998,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches("CREATE", "MATERIALIZED"))
+	else if (Matches("CREATE", "MATERIALIZED") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED"))
 		COMPLETE_WITH("VIEW");
-	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	/* Complete CREATE MATERIALIZED VIEW <name> with AS  */
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny) ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH("SELECT");
 
 /* CREATE EVENT TRIGGER */
-- 
2.17.1

v23-0007-Add-Incremental-View-Maintenance-support.patchtext/x-diff; name=v23-0007-Add-Incremental-View-Maintenance-support.patchDownload
From 7117de103841d4a3f1065b2d1a8b3b86c3ca56eb Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Tue, 22 Dec 2020 18:40:18 +0900
Subject: [PATCH v23 07/15] Add Incremental View Maintenance support

In this implementation, AFTER triggers are used to collecting
tuplestores containing transition table contents. When multiple tables
are changed, multiple AFTER triggers are invoked, then the final AFTER
trigger performs actual update of the matview. In addition, BEFORE
triggers are also used to handle global information for view
maintenance. To calculate view deltas, we need both pre-state and
post-state of base tables. Post-update states are available in AFTER
trigger, and pre-update states can be calculated by filtering inserted
tuples using cmin/xmin system columns, and append deleted tuples which
are contained in a old transition table.

This patch also allows self-join, simultaneous updates of more than
one base tables, and multiple updates of the same base table.

DISTINCT and tuple duplicates in views are supported. When IMMV is
created with DISTINCT, multiplicity of tuples is counted and stored
in  "__ivm_count__" column, which is a hidden column of IMMV.
The value in__ivm_count__ is updated when IMMV is maintained
incrementally. A tuple in IMMV can be removed if and only if the
count becomes zero.
---
 src/backend/access/transam/xact.c   |    5 +
 src/backend/commands/createas.c     |  697 ++++++++++++
 src/backend/commands/indexcmds.c    |   40 +
 src/backend/commands/matview.c      | 1549 ++++++++++++++++++++++++++-
 src/backend/commands/tablecmds.c    |    9 +
 src/backend/nodes/copyfuncs.c       |    1 +
 src/backend/nodes/equalfuncs.c      |    1 +
 src/backend/nodes/outfuncs.c        |    1 +
 src/backend/nodes/readfuncs.c       |    1 +
 src/backend/parser/parse_relation.c |   18 +-
 src/backend/rewrite/rewriteDefine.c |    3 +-
 src/include/catalog/pg_proc.dat     |    8 +
 src/include/commands/createas.h     |    5 +
 src/include/commands/matview.h      |    8 +
 src/include/nodes/parsenodes.h      |    2 +
 15 files changed, 2308 insertions(+), 40 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 441445927e..d67a8bc906 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_enum.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/matview.h"
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "executor/spi.h"
@@ -2705,6 +2706,7 @@ AbortTransaction(void)
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
 	AtAbort_Twophase();
+	AtAbort_IVM();
 
 	/*
 	 * Advertise the fact that we aborted in pg_xact (assuming that we got as
@@ -4948,6 +4950,9 @@ AbortSubTransaction(void)
 	AbortBufferIO();
 	UnlockBuffers();
 
+	/* Clean up hash entries for incremental view maintenance */
+	AtAbort_IVM();
+
 	/* Reset WAL record construction state */
 	XLogResetInsertion();
 
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 0982851715..3fc2af1c4a 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -32,24 +32,41 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
 #include "catalog/toasting.h"
 #include "commands/createas.h"
+#include "commands/defrem.h"
 #include "commands/matview.h"
 #include "commands/prepare.h"
 #include "commands/tablecmds.h"
+#include "commands/tablespace.h"
+#include "commands/trigger.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "optimizer/clauses.h"
+#include "optimizer/optimizer.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_func.h"
+#include "parser/parse_type.h"
 #include "rewrite/rewriteHandler.h"
+#include "rewrite/rewriteManip.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
+#include "utils/syscache.h"
 
 typedef struct
 {
@@ -73,6 +90,12 @@ static bool intorel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void intorel_shutdown(DestReceiver *self);
 static void intorel_destroy(DestReceiver *self);
 
+static void CreateIvmTriggersOnBaseTables_recurse(Query *qry, Node *node, Oid matviewOid, Relids *relids, bool ex_lock);
+static void CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock);
+static void check_ivm_restriction(Node *node);
+static bool check_ivm_restriction_walker(Node *node, void *context);
+static void CreateIndexOnIMMV(Query *query, Relation matviewRel);
+static Bitmapset *get_primary_key_attnos_from_query(Query *qry, List **constraintList);
 
 /*
  * create_ctas_internal
@@ -108,6 +131,8 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	create->oncommit = into->onCommit;
 	create->tablespacename = into->tableSpaceName;
 	create->if_not_exists = false;
+	/* Using Materialized view only */
+	create->ivm = into->ivm;
 	create->accessMethod = into->accessMethod;
 
 	/*
@@ -238,6 +263,7 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 	List	   *rewritten;
 	PlannedStmt *plan;
 	QueryDesc  *queryDesc;
+	Query	   *query_immv = NULL;
 
 	/* Check if the relation exists or not */
 	if (CreateTableAsRelExists(stmt))
@@ -282,6 +308,22 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 		save_nestlevel = NewGUCNestLevel();
 	}
 
+	if (is_matview && into->ivm)
+	{
+		/* check if the query is supported in IMMV definition */
+		if (contain_mutable_functions((Node *) query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("mutable function is not supported on incrementally maintainable materialized view"),
+					 errhint("functions must be marked IMMUTABLE")));
+
+		check_ivm_restriction((Node *) query);
+
+		/* For IMMV, we need to rewrite matview query */
+		query = rewriteQueryForIMMV(query, into->colNames);
+		query_immv = copyObject(query);
+	}
+
 	if (into->skipData)
 	{
 		/*
@@ -358,11 +400,74 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 
 		/* Restore userid and security context */
 		SetUserIdAndSecContext(save_userid, save_sec_context);
+
+		if (into->ivm)
+		{
+			Oid matviewOid = address.objectId;
+			Relation matviewRel = table_open(matviewOid, NoLock);
+
+			/*
+			 * Mark relisivm field, if it's a matview and into->ivm is true.
+			 */
+			SetMatViewIVMState(matviewRel, true);
+
+			/* Create an index on incremental maintainable materialized view, if possible */
+			CreateIndexOnIMMV((Query *) into->viewQuery, matviewRel);
+
+			/* Create triggers on incremental maintainable materialized view */
+			if (!into->skipData)
+			{
+				Assert(query_immv != NULL);
+				CreateIvmTriggersOnBaseTables(query_immv, matviewOid, true);
+			}
+			table_close(matviewRel, NoLock);
+		}
 	}
 
 	return address;
 }
 
+/*
+ * rewriteQueryForIMMV -- rewrite view definition query for IMMV
+ *
+ * count(*) is added for counting distinct tuples in views.
+ */
+Query *
+rewriteQueryForIMMV(Query *query, List *colNames)
+{
+	Query *rewritten;
+
+	TargetEntry *tle;
+	Node *node;
+	ParseState *pstate = make_parsestate(NULL);
+	FuncCall *fn;
+
+	rewritten = copyObject(query);
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	/* Convert DISTINCT to GROUP BY.  count(*) will be added afterward. */
+	if (rewritten->distinctClause)
+		rewritten->groupClause = transformDistinctClause(NULL, &rewritten->targetList, rewritten->sortClause, false);
+
+	/* Add count(*) for counting distinct tuples in views */
+	if (rewritten->distinctClause)
+	{
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+		fn->agg_star = true;
+
+		node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+		tle = makeTargetEntry((Expr *) node,
+								list_length(rewritten->targetList) + 1,
+								pstrdup("__ivm_count__"),
+								false);
+		rewritten->targetList = lappend(rewritten->targetList, tle);
+		rewritten->hasAggs = true;
+	}
+
+	return rewritten;
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -623,3 +728,595 @@ intorel_destroy(DestReceiver *self)
 {
 	pfree(self);
 }
+
+/*
+ * CreateIvmTriggersOnBaseTables -- create IVM triggers on all base tables
+ */
+void
+CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create)
+{
+	Relids	relids = NULL;
+	bool	ex_lock = false;
+	Index	first_rtindex = is_create ? 1 : PRS2_NEW_VARNO + 1;
+	RangeTblEntry *rte;
+
+	/* Immediately return if we don't have any base tables. */
+	if (list_length(qry->rtable) < first_rtindex)
+		return;
+
+	/*
+	 * If the view has more than one base tables, we need an exclusive lock
+	 * on the view so that the view would be maintained serially to avoid
+	 * the inconsistency that occurs when two base tables are modified in
+	 * concurrent transactions. However, if the view has only one table,
+	 * we can use a weaker lock.
+	 *
+	 * The type of lock should be determined here, because if we check the
+	 * view definition at maintenance time, we need to acquire a weaker lock,
+	 * and upgrading the lock level after this increases probability of
+	 * deadlock.
+	 */
+
+	rte = list_nth(qry->rtable, first_rtindex - 1);
+	if (list_length(qry->rtable) > first_rtindex ||
+		rte->rtekind != RTE_RELATION)
+		ex_lock = true;
+
+	CreateIvmTriggersOnBaseTables_recurse(qry, (Node *)qry, matviewOid, &relids, ex_lock);
+
+	bms_free(relids);
+}
+
+static void
+CreateIvmTriggersOnBaseTables_recurse(Query *qry, Node *node, Oid matviewOid, Relids *relids, bool ex_lock)
+{
+	/* This can recurse, so check for excessive recursion */
+	check_stack_depth();
+
+	if (node == NULL)
+		return;
+
+	switch (nodeTag(node))
+	{
+		case T_Query:
+			{
+				Query *query = (Query *) node;
+
+				CreateIvmTriggersOnBaseTables_recurse(qry, (Node *)query->jointree, matviewOid, relids, ex_lock);
+			}
+			break;
+
+		case T_RangeTblRef:
+			{
+				int			rti = ((RangeTblRef *) node)->rtindex;
+				RangeTblEntry *rte = rt_fetch(rti, qry->rtable);
+
+				if (rte->rtekind == RTE_RELATION)
+				{
+					if (!bms_is_member(rte->relid, *relids))
+					{
+						CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_BEFORE, ex_lock);
+						CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_BEFORE, ex_lock);
+						CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_BEFORE, ex_lock);
+						CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_AFTER, ex_lock);
+						CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_AFTER, ex_lock);
+						CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_AFTER, ex_lock);
+
+						*relids = bms_add_member(*relids, rte->relid);
+					}
+				}
+			}
+			break;
+
+		case T_FromExpr:
+			{
+				FromExpr   *f = (FromExpr *) node;
+				ListCell   *l;
+
+				foreach(l, f->fromlist)
+					CreateIvmTriggersOnBaseTables_recurse(qry, lfirst(l), matviewOid, relids, ex_lock);
+			}
+			break;
+
+		case T_JoinExpr:
+			{
+				JoinExpr   *j = (JoinExpr *) node;
+
+				CreateIvmTriggersOnBaseTables_recurse(qry, j->larg, matviewOid, relids, ex_lock);
+				CreateIvmTriggersOnBaseTables_recurse(qry, j->rarg, matviewOid, relids, ex_lock);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
+	}
+}
+
+/*
+ * CreateIvmTrigger -- create IVM trigger on a base table
+ */
+static void
+CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock)
+{
+	ObjectAddress	refaddr;
+	ObjectAddress	address;
+	CreateTrigStmt *ivm_trigger;
+	List *transitionRels = NIL;
+
+	Assert(timing == TRIGGER_TYPE_BEFORE || timing == TRIGGER_TYPE_AFTER);
+
+	refaddr.classId = RelationRelationId;
+	refaddr.objectId = viewOid;
+	refaddr.objectSubId = 0;
+
+	ivm_trigger = makeNode(CreateTrigStmt);
+	ivm_trigger->relation = NULL;
+	ivm_trigger->row = false;
+
+	ivm_trigger->timing = timing;
+	ivm_trigger->events = type;
+
+	switch (type)
+	{
+		case TRIGGER_TYPE_INSERT:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_ins_before" : "IVM_trigger_ins_after");
+			break;
+		case TRIGGER_TYPE_DELETE:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_del_before" : "IVM_trigger_del_after");
+			break;
+		case TRIGGER_TYPE_UPDATE:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_upd_before" : "IVM_trigger_upd_after");
+			break;
+		default:
+			elog(ERROR, "unsupported trigger type");
+	}
+
+	if (timing == TRIGGER_TYPE_AFTER)
+	{
+		if (type == TRIGGER_TYPE_INSERT || type == TRIGGER_TYPE_UPDATE)
+		{
+			TriggerTransition *n = makeNode(TriggerTransition);
+			n->name = "__ivm_newtable";
+			n->isNew = true;
+			n->isTable = true;
+
+			transitionRels = lappend(transitionRels, n);
+		}
+		if (type == TRIGGER_TYPE_DELETE || type == TRIGGER_TYPE_UPDATE)
+		{
+			TriggerTransition *n = makeNode(TriggerTransition);
+			n->name = "__ivm_oldtable";
+			n->isNew = false;
+			n->isTable = true;
+
+			transitionRels = lappend(transitionRels, n);
+		}
+	}
+
+	ivm_trigger->funcname =
+		(timing == TRIGGER_TYPE_BEFORE ? SystemFuncName("IVM_immediate_before") : SystemFuncName("IVM_immediate_maintenance"));
+
+	ivm_trigger->columns = NIL;
+	ivm_trigger->transitionRels = transitionRels;
+	ivm_trigger->whenClause = NULL;
+	ivm_trigger->isconstraint = false;
+	ivm_trigger->deferrable = false;
+	ivm_trigger->initdeferred = false;
+	ivm_trigger->constrrel = NULL;
+	ivm_trigger->args = list_make2(
+		makeString(DatumGetPointer(DirectFunctionCall1(oidout, ObjectIdGetDatum(viewOid)))),
+		makeString(DatumGetPointer(DirectFunctionCall1(boolout, BoolGetDatum(ex_lock))))
+		);
+
+	address = CreateTrigger(ivm_trigger, NULL, relOid, InvalidOid, InvalidOid,
+						 InvalidOid, InvalidOid, InvalidOid, NULL, true, false);
+
+	recordDependencyOn(&address, &refaddr, DEPENDENCY_IMMV);
+
+	/* Make changes-so-far visible */
+	CommandCounterIncrement();
+}
+
+/*
+ * check_ivm_restriction --- look for specify nodes in the query tree
+ */
+static void
+check_ivm_restriction(Node *node)
+{
+	check_ivm_restriction_walker(node, NULL);
+}
+
+static bool
+check_ivm_restriction_walker(Node *node, void *context)
+{
+	/* This can recurse, so check for excessive recursion */
+	check_stack_depth();
+
+	if (node == NULL)
+		return false;
+
+	/*
+	 * We currently don't support Sub-Query.
+	 */
+	if (IsA(node, SubPlan) || IsA(node, SubLink))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("subquery is not supported on incrementally maintainable materialized view")));
+
+	switch (nodeTag(node))
+	{
+		case T_Query:
+			{
+				Query *qry = (Query *)node;
+				ListCell   *lc;
+				List       *vars;
+
+				/* if contained CTE, return error */
+				if (qry->cteList != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("CTE is not supported on incrementally maintainable materialized view")));
+				if (qry->havingQual != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg(" HAVING clause is not supported on incrementally maintainable materialized view")));
+				if (qry->sortClause != NIL)	/* There is a possibility that we don't need to return an error */
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("ORDER BY clause is not supported on incrementally maintainable materialized view")));
+				if (qry->limitOffset != NULL || qry->limitCount != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("LIMIT/OFFSET clause is not supported on incrementally maintainable materialized view")));
+				if (qry->hasDistinctOn)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DISTINCT ON is not supported on incrementally maintainable materialized view")));
+				if (qry->hasWindowFuncs)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("window functions are not supported on incrementally maintainable materialized view")));
+				if (qry->groupingSets != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("GROUPING SETS, ROLLUP, or CUBE clauses is not supported on incrementally maintainable materialized view")));
+				if (qry->setOperations != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("UNION/INTERSECT/EXCEPT statements are not supported on incrementally maintainable materialized view")));
+				if (list_length(qry->targetList) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("empty target list is not supported on incrementally maintainable materialized view")));
+				if (qry->rowMarks != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("FOR UPDATE/SHARE clause is not supported on incrementally maintainable materialized view")));
+
+				/* system column restrictions */
+				vars = pull_vars_of_level((Node *) qry, 0);
+				foreach(lc, vars)
+				{
+					if (IsA(lfirst(lc), Var))
+					{
+						Var *var = (Var *) lfirst(lc);
+						/* if system column, return error */
+						if (var->varattno < 0)
+							ereport(ERROR,
+									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									 errmsg("system column is not supported on incrementally maintainable materialized view")));
+					}
+				}
+
+				/* restrictions for rtable */
+				foreach(lc, qry->rtable)
+				{
+					RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+					if (rte->subquery)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("subquery is not supported on incrementally maintainable materialized view")));
+
+					if (rte->tablesample != NULL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("TABLESAMPLE clause is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_RELATION && find_inheritance_children(rte->relid, NoLock) != NIL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("inheritance parent is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_VIEW ||
+						rte->relkind == RELKIND_MATVIEW)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view")));
+
+					if (rte->rtekind == RTE_VALUES)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("VALUES is not supported on incrementally maintainable materialized view")));
+
+				}
+
+				query_tree_walker(qry, check_ivm_restriction_walker, NULL, QTW_IGNORE_RANGE_TABLE);
+
+				break;
+			}
+		case T_TargetEntry:
+			{
+				TargetEntry *tle = (TargetEntry *)node;
+				if (isIvmName(tle->resname))
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("column name %s is not supported on incrementally maintainable materialized view", tle->resname)));
+
+				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				break;
+			}
+		case T_JoinExpr:
+			{
+				JoinExpr *joinexpr = (JoinExpr *)node;
+
+				if (joinexpr->jointype > JOIN_INNER)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("OUTER JOIN is not supported on incrementally maintainable materialized view")));
+
+				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+			}
+			break;
+			expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+			break;
+	}
+	return false;
+}
+
+/*
+ * CreateindexOnIMMV
+ *
+ * Create a unique index on incremental maintainable materialized view.
+ * If the view definition query has a GROUP BY clause, the index is created
+ * on the columns of GROUP BY expressions. Otherwise, if the view contains
+ * all primary key attritubes of its base tables in the target list, the index
+ * is created on these attritubes. In other cases, no index is created.
+ */
+static void
+CreateIndexOnIMMV(Query *query, Relation matviewRel)
+{
+	Query *qry = (Query *) copyObject(query);
+	ListCell *lc;
+	IndexStmt  *index;
+	ObjectAddress address;
+	List *constraintList = NIL;
+	char		idxname[NAMEDATALEN];
+
+	snprintf(idxname, sizeof(idxname), "%s_index", RelationGetRelationName(matviewRel));
+
+	index = makeNode(IndexStmt);
+
+	index->unique = true;
+	index->primary = false;
+	index->isconstraint = false;
+	index->deferrable = false;
+	index->initdeferred = false;
+	index->idxname = idxname;
+	index->relation =
+		makeRangeVar(get_namespace_name(RelationGetNamespace(matviewRel)),
+					 pstrdup(RelationGetRelationName(matviewRel)),
+					 -1);
+	index->accessMethod = DEFAULT_INDEX_TYPE;
+	index->options = NIL;
+	index->tableSpace = get_tablespace_name(matviewRel->rd_rel->reltablespace);
+	index->whereClause = NULL;
+	index->indexParams = NIL;
+	index->indexIncludingParams = NIL;
+	index->excludeOpNames = NIL;
+	index->idxcomment = NULL;
+	index->indexOid = InvalidOid;
+	index->oldNode = InvalidOid;
+	index->oldCreateSubid = InvalidSubTransactionId;
+	index->oldFirstRelfilenodeSubid = InvalidSubTransactionId;
+	index->transformed = true;
+	index->concurrent = false;
+	index->if_not_exists = false;
+
+	if (qry->distinctClause)
+	{
+		/* create unique constraint on all columns */
+		index->isconstraint = true;
+		foreach(lc, qry->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+			IndexElem  *iparam;
+
+			iparam = makeNode(IndexElem);
+			iparam->name = pstrdup(NameStr(attr->attname));
+			iparam->expr = NULL;
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->opclassopts = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+		}
+	}
+	else
+	{
+		Bitmapset *key_attnos;
+
+		/* create index on the base tables' primary key columns */
+		key_attnos = get_primary_key_attnos_from_query(qry, &constraintList);
+		if (key_attnos)
+		{
+			foreach(lc, qry->targetList)
+			{
+				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+				Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+
+				if (bms_is_member(tle->resno - FirstLowInvalidHeapAttributeNumber, key_attnos))
+				{
+					IndexElem  *iparam;
+
+					iparam = makeNode(IndexElem);
+					iparam->name = pstrdup(NameStr(attr->attname));
+					iparam->expr = NULL;
+					iparam->indexcolname = NULL;
+					iparam->collation = NIL;
+					iparam->opclass = NIL;
+					iparam->opclassopts = NIL;
+					iparam->ordering = SORTBY_DEFAULT;
+					iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+					index->indexParams = lappend(index->indexParams, iparam);
+				}
+			}
+
+		}
+		else
+		{
+			/* create no index, just notice that an appropriate index is necessary for efficient, IVM */
+			ereport(NOTICE,
+					(errmsg("could not create an index on materialized view \"%s\" automatically",
+							RelationGetRelationName(matviewRel)),
+					errhint("Create an index on the materialized view for efficient incremental maintenance.")));
+			return;
+		}
+	}
+
+	address = DefineIndex(RelationGetRelid(matviewRel),
+						  index,
+						  InvalidOid,
+						  InvalidOid,
+						  InvalidOid,
+						  false, true, false, false, true);
+
+	ereport(NOTICE,
+			(errmsg("created index \"%s\" on materialized view \"%s\"",
+					idxname, RelationGetRelationName(matviewRel))));
+
+
+	/*
+	 * Make dependencies so that the index is dropped if any base tables's
+	 * primary key is dropped.
+	 */
+	foreach(lc, constraintList)
+	{
+		Oid constraintOid = lfirst_oid(lc);
+		ObjectAddress	refaddr;
+
+		refaddr.classId = ConstraintRelationId;
+		refaddr.objectId = constraintOid;
+		refaddr.objectSubId = 0;
+
+		recordDependencyOn(&address, &refaddr, DEPENDENCY_NORMAL);
+	}
+}
+
+
+/*
+ * get_primary_key_attnos_from_query
+ *
+ * Identify the columns in base tables' primary keys in the target list.
+ *
+ * Returns a Bitmapset of the column attnos of the primary key's columns of
+ * tables that used in the query.  The attnos are offset by
+ * FirstLowInvalidHeapAttributeNumber as same as get_primary_key_attnos.
+ *
+ * If any table has no primary key or any primary key's columns is not in
+ * the target list, return NULL.  We also return NULL if any pkey constraint
+ * is deferrable.
+ *
+ * constraintList is set to a list of the OIDs of the pkey constraints.
+ */
+static Bitmapset *
+get_primary_key_attnos_from_query(Query *query, List **constraintList)
+{
+	List *key_attnos_list = NIL;
+	ListCell *lc;
+	int i;
+	Bitmapset *keys = NULL;
+	Relids	rels_in_from;
+	PlannerInfo root;
+
+
+	/*
+	 * Collect primary key attributes from all tables used in query. The key attributes
+	 * sets for each table are stored in key_attnos_list in order by RTE index.
+	 */
+	i = 1;
+	foreach(lc, query->rtable)
+	{
+		RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+		Bitmapset *key_attnos;
+		bool	has_pkey = true;
+
+		/* for tables, call get_primary_key_attnos */
+		if (r->rtekind == RTE_RELATION)
+		{
+			Oid constraintOid;
+			key_attnos = get_primary_key_attnos(r->relid, false, &constraintOid);
+			*constraintList = lappend_oid(*constraintList, constraintOid);
+			has_pkey = (key_attnos != NULL);
+		}
+		/* for other RTEs, store NULL into key_attnos_list */
+		else
+			key_attnos = NULL;
+
+		/*
+		 * If any table or subquery has no primary key or its pkey constraint is deferrable,
+		 * we cannot get key attributes for this query, so return NULL.
+		 */
+		if (!has_pkey)
+			return NULL;
+
+		key_attnos_list = lappend(key_attnos_list, key_attnos);
+	}
+
+	/* Collect key attributes appearing in the target list */
+	i = 1;
+	foreach(lc, query->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) flatten_join_alias_vars(query, lfirst(lc));
+
+		if (IsA(tle->expr, Var))
+		{
+			Var *var = (Var*) tle->expr;
+			Bitmapset *attnos = list_nth(key_attnos_list, var->varno - 1);
+
+			/* check if this attribute is from a base table's primary key */
+			if (bms_is_member(var->varattno - FirstLowInvalidHeapAttributeNumber, attnos))
+			{
+				/*
+				 * Remove found key attributes from key_attnos_list, and add this
+				 * to the result list.
+				 */
+				bms_del_member(attnos, var->varattno - FirstLowInvalidHeapAttributeNumber);
+				keys = bms_add_member(keys, i - FirstLowInvalidHeapAttributeNumber);
+			}
+		}
+		i++;
+	}
+
+	/* Collect relations appearing in the FROM clause */
+	rels_in_from = pull_varnos_of_level(&root, (Node *)query->jointree, 0);
+
+	/*
+	 * Check if all key attributes of relations in FROM are appearing in the target
+	 * list.  If an attribute remains in key_attnos_list in spite of the table is used
+	 * in FROM clause, the target is missing this key attribute, so we return NULL.
+	 */
+	i = 1;
+	foreach(lc, key_attnos_list)
+	{
+		Bitmapset *bms = (Bitmapset *)lfirst(lc);
+		if (!bms_is_empty(bms) && bms_is_member(i, rels_in_from))
+			return NULL;
+		i++;
+	}
+
+	return keys;
+}
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5e..5581c21298 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -36,6 +36,7 @@
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
+#include "commands/matview.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
@@ -1054,6 +1055,45 @@ DefineIndex(Oid relationId,
 	safe_index = indexInfo->ii_Expressions == NIL &&
 		indexInfo->ii_Predicate == NIL;
 
+	/*
+	 * We disallow unique indexes on IVM columns of IMMVs.
+	 */
+	if (RelationIsIVM(rel) && stmt->unique)
+	{
+		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
+		{
+			AttrNumber	attno = indexInfo->ii_IndexAttrNumbers[i];
+			if (attno > 0)
+			{
+				char *name = NameStr(TupleDescAttr(rel->rd_att, attno - 1)->attname);
+				if (name && isIvmName(name))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique index creation on IVM columns is not supported")));
+			}
+		}
+
+		if (indexInfo->ii_Expressions)
+		{
+			Bitmapset  *indexattrs = NULL;
+			int			varno = -1;
+
+			pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
+
+			while ((varno = bms_next_member(indexattrs, varno)) >= 0)
+			{
+				int attno = varno + FirstLowInvalidHeapAttributeNumber;
+				char *name = NameStr(TupleDescAttr(rel->rd_att, attno - 1)->attname);
+				if (name && isIvmName(name))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique index creation on IVM columns is not supported")));
+			}
+
+		}
+	}
+
+
 	/*
 	 * Report index creation if appropriate (delay this till after most of the
 	 * error checks)
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 25bbd8a5c1..f1f46e2e14 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -25,26 +25,47 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_type.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "commands/cluster.h"
+#include "commands/defrem.h"
 #include "commands/matview.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
+#include "commands/createas.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "executor/tstoreReceiver.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_func.h"
 #include "parser/parse_relation.h"
+#include "parser/parse_type.h"
 #include "pgstat.h"
 #include "rewrite/rewriteHandler.h"
+#include "rewrite/rewriteManip.h"
+#include "rewrite/rowsecurity.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
 
 
 typedef struct
@@ -58,6 +79,50 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_transientrel;
 
+#define MV_INIT_QUERYHASHSIZE	16
+
+/*
+ * MV_TriggerHashEntry
+ *
+ * Hash entry for base tables on which IVM trigger is invoked
+ */
+typedef struct MV_TriggerHashEntry
+{
+	Oid	matview_id;			/* OID of the materialized view */
+	int	before_trig_count;	/* count of before triggers invoked */
+	int	after_trig_count;	/* count of after triggers invoked */
+
+	TransactionId	xid;	/* Transaction id before the first table is modified*/
+	CommandId		cid;	/* Command id before the first table is modified */
+
+	List   *tables;		/* List of MV_TriggerTable */
+	bool	has_old;	/* tuples are deleted from any table? */
+	bool	has_new;	/* tuples are inserted into any table? */
+} MV_TriggerHashEntry;
+
+/*
+ * MV_TriggerTable
+ *
+ * IVM related data for tables on which the trigger is invoked.
+ */
+typedef struct MV_TriggerTable
+{
+	Oid		table_id;			/* OID of the modified table */
+	List   *old_tuplestores;	/* tuplestores for deleted tuples */
+	List   *new_tuplestores;	/* tuplestores for inserted tuples */
+	List   *old_rtes;			/* RTEs of ENRs for old_tuplestores*/
+	List   *new_rtes;			/* RTEs of ENRs for new_tuplestores */
+
+	List   *rte_indexes;		/* List of RTE index of the modified table */
+	RangeTblEntry *original_rte;	/* the original RTE saved before rewriting query */
+} MV_TriggerTable;
+
+static HTAB *mv_trigger_info = NULL;
+
+/* ENR name for materialized view delta */
+#define NEW_DELTA_ENRNAME "new_delta"
+#define OLD_DELTA_ENRNAME "old_delta"
+
 static int	matview_maintenance_depth = 0;
 
 static void transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
@@ -65,7 +130,9 @@ static bool transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void transientrel_shutdown(DestReceiver *self);
 static void transientrel_destroy(DestReceiver *self);
 static uint64 refresh_matview_datafill(DestReceiver *dest, Query *query,
-									   const char *queryString);
+						 QueryEnvironment *queryEnv,
+						 TupleDesc *resultTupleDesc,
+						 const char *queryString);
 static char *make_temptable_name_n(char *tempname, int n);
 static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 								   int save_sec_context);
@@ -73,6 +140,45 @@ static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersist
 static bool is_usable_unique_index(Relation indexRel);
 static void OpenMatViewIncrementalMaintenance(void);
 static void CloseMatViewIncrementalMaintenance(void);
+static Query *get_matview_query(Relation matviewRel);
+
+static Query *rewrite_query_for_preupdate_state(Query *query, List *tables,
+								  TransactionId xid, CommandId cid,
+								  ParseState *pstate);
+static void register_delta_ENRs(ParseState *pstate, Query *query, List *tables);
+static char *make_delta_enr_name(const char *prefix, Oid relid, int count);
+static RangeTblEntry *get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
+				 TransactionId xid, CommandId cid,
+				 QueryEnvironment *queryEnv);
+static RangeTblEntry *union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
+		   QueryEnvironment *queryEnv);
+static Query *rewrite_query_for_distinct(Query *query, ParseState *pstate);
+
+static void calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
+			DestReceiver *dest_old, DestReceiver *dest_new,
+			TupleDesc *tupdesc_old, TupleDesc *tupdesc_new,
+			QueryEnvironment *queryEnv);
+static Query *rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte_index);
+
+static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
+			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
+			Query *query, bool use_count, char *count_colname);
+static void apply_old_delta(const char *matviewname, const char *deltaname_old,
+				List *keys);
+static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
+				List *keys, const char *count_colname);
+static void apply_new_delta(const char *matviewname, const char *deltaname_new,
+				StringInfo target_list);
+static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
+				List *keys, StringInfo target_list, const char* count_colname);
+static char *get_matching_condition_string(List *keys);
+static void generate_equal(StringInfo querybuf, Oid opttype,
+			   const char *leftop, const char *rightop);
+
+static void mv_InitHashTables(void);
+static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry);
+
+static List *get_securityQuals(Oid relId, int rt_index, Query *query);
 
 /*
  * SetMatViewPopulatedState
@@ -114,6 +220,46 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
 	CommandCounterIncrement();
 }
 
+/*
+ * SetMatViewIVMState
+ *		Mark a materialized view as IVM, or not.
+ *
+ * NOTE: caller must be holding an appropriate lock on the relation.
+ */
+void
+SetMatViewIVMState(Relation relation, bool newstate)
+{
+	Relation	pgrel;
+	HeapTuple	tuple;
+
+	Assert(relation->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Update relation's pg_class entry.  Crucial side-effect: other backends
+	 * (and this one too!) are sent SI message to make them rebuild relcache
+	 * entries.
+	 */
+	pgrel = table_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(relation)));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u",
+			 RelationGetRelid(relation));
+
+	((Form_pg_class) GETSTRUCT(tuple))->relisivm = newstate;
+
+	CatalogTupleUpdate(pgrel, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	table_close(pgrel, RowExclusiveLock);
+
+	/*
+	 * Advance command counter to make the updated pg_class row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+}
+
 /*
  * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command
  *
@@ -140,8 +286,6 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 {
 	Oid			matviewOid;
 	Relation	matviewRel;
-	RewriteRule *rule;
-	List	   *actions;
 	Query	   *dataQuery;
 	Oid			tableSpace;
 	Oid			relowner;
@@ -155,6 +299,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 	int			save_sec_context;
 	int			save_nestlevel;
 	ObjectAddress address;
+	bool oldPopulated;
 
 	/* Determine strength of lock needed. */
 	concurrent = stmt->concurrent;
@@ -167,6 +312,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										  lockmode, 0,
 										  RangeVarCallbackOwnsTable, NULL);
 	matviewRel = table_open(matviewOid, NoLock);
+	oldPopulated = RelationIsPopulated(matviewRel);
 
 	/* Make sure it is a materialized view. */
 	if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW)
@@ -187,32 +333,12 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("CONCURRENTLY and WITH NO DATA options cannot be used together")));
 
-	/*
-	 * Check that everything is correct for a refresh. Problems at this point
-	 * are internal errors, so elog is sufficient.
-	 */
-	if (matviewRel->rd_rel->relhasrules == false ||
-		matviewRel->rd_rules->numLocks < 1)
-		elog(ERROR,
-			 "materialized view \"%s\" is missing rewrite information",
-			 RelationGetRelationName(matviewRel));
-
-	if (matviewRel->rd_rules->numLocks > 1)
-		elog(ERROR,
-			 "materialized view \"%s\" has too many rules",
-			 RelationGetRelationName(matviewRel));
 
-	rule = matviewRel->rd_rules->rules[0];
-	if (rule->event != CMD_SELECT || !(rule->isInstead))
-		elog(ERROR,
-			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
-			 RelationGetRelationName(matviewRel));
+	dataQuery = get_matview_query(matviewRel);
 
-	actions = rule->actions;
-	if (list_length(actions) != 1)
-		elog(ERROR,
-			 "the rule for materialized view \"%s\" is not a single action",
-			 RelationGetRelationName(matviewRel));
+	/* For IMMV, we need to rewrite matview query */
+	if (!stmt->skipData && RelationIsIVM(matviewRel))
+		dataQuery = rewriteQueryForIMMV(dataQuery,NIL);
 
 	/*
 	 * Check that there is a unique index with no WHERE clause on one or more
@@ -247,12 +373,6 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 					 errhint("Create a unique index with no WHERE clause on one or more columns of the materialized view.")));
 	}
 
-	/*
-	 * The stored query was rewritten at the time of the MV definition, but
-	 * has not been scribbled on by the planner.
-	 */
-	dataQuery = linitial_node(Query, actions);
-
 	/*
 	 * Check for active uses of the relation in the current transaction, such
 	 * as open scans.
@@ -293,6 +413,52 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 		relpersistence = matviewRel->rd_rel->relpersistence;
 	}
 
+	/* delete immv triggers */
+	if (RelationIsIVM(matviewRel) && stmt->skipData )
+	{
+		/* use deleted trigger */
+		Relation	depRel;
+		ScanKeyData key;
+		SysScanDesc scan;
+		HeapTuple	tup;
+		ObjectAddresses *immv_triggers;
+
+		immv_triggers = new_object_addresses();
+
+		/*
+		 * We save some cycles by opening pg_depend just once and passing the
+		 * Relation pointer down to all the recursive deletion steps.
+		 */
+		depRel = table_open(DependRelationId, RowExclusiveLock);
+
+		ScanKeyInit(&key,
+					Anum_pg_depend_refobjid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(matviewOid));
+
+		scan = systable_beginscan(depRel, DependReferenceIndexId, true,
+								  NULL, 1, &key);
+		while ((tup = systable_getnext(scan)) != NULL)
+		{
+			ObjectAddress obj;
+			Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
+
+			if (foundDep->deptype == DEPENDENCY_IMMV)
+			{
+				obj.classId = foundDep->classid;
+				obj.objectId = foundDep->objid;
+				obj.objectSubId = foundDep->refobjsubid;
+				add_exact_object_address(&obj, immv_triggers);
+			}
+		}
+		systable_endscan(scan);
+
+		performMultipleDeletions(immv_triggers, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
+
+		table_close(depRel, RowExclusiveLock);
+		free_object_addresses(immv_triggers);
+	}
+
 	/*
 	 * Create the transient table that will receive the regenerated data. Lock
 	 * it against access by any other process until commit (by which time it
@@ -311,7 +477,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 
 	/* Generate the data, if wanted. */
 	if (!stmt->skipData)
-		processed = refresh_matview_datafill(dest, dataQuery, queryString);
+		processed = refresh_matview_datafill(dest, dataQuery, NULL, NULL, queryString);
 
 	/* Make the matview match the newly generated data. */
 	if (concurrent)
@@ -346,6 +512,9 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 			pgstat_count_heap_insert(matviewRel, processed);
 	}
 
+	if (!stmt->skipData && RelationIsIVM(matviewRel) && !oldPopulated)
+		CreateIvmTriggersOnBaseTables(dataQuery, matviewOid, false);
+
 	table_close(matviewRel, NoLock);
 
 	/* Roll back any GUC changes */
@@ -380,6 +549,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  */
 static uint64
 refresh_matview_datafill(DestReceiver *dest, Query *query,
+						 QueryEnvironment *queryEnv,
+						 TupleDesc *resultTupleDesc,
 						 const char *queryString)
 {
 	List	   *rewritten;
@@ -416,7 +587,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 	/* Create a QueryDesc, redirecting output to our tuple receiver */
 	queryDesc = CreateQueryDesc(plan, queryString,
 								GetActiveSnapshot(), InvalidSnapshot,
-								dest, NULL, NULL, 0);
+								dest, NULL, queryEnv ? queryEnv: NULL, 0);
 
 	/* call ExecutorStart to prepare the plan for execution */
 	ExecutorStart(queryDesc, 0);
@@ -426,6 +597,9 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 
 	processed = queryDesc->estate->es_processed;
 
+	if (resultTupleDesc)
+		*resultTupleDesc = CreateTupleDescCopy(queryDesc->tupDesc);
+
 	/* and clean up */
 	ExecutorFinish(queryDesc);
 	ExecutorEnd(queryDesc);
@@ -933,3 +1107,1308 @@ CloseMatViewIncrementalMaintenance(void)
 	matview_maintenance_depth--;
 	Assert(matview_maintenance_depth >= 0);
 }
+
+/*
+ * get_matview_query - get the Query from a matview's _RETURN rule.
+ */
+static Query *
+get_matview_query(Relation matviewRel)
+{
+	RewriteRule *rule;
+	List * actions;
+
+	/*
+	 * Check that everything is correct for a refresh. Problems at this point
+	 * are internal errors, so elog is sufficient.
+	 */
+	if (matviewRel->rd_rel->relhasrules == false ||
+		matviewRel->rd_rules->numLocks < 1)
+		elog(ERROR,
+			 "materialized view \"%s\" is missing rewrite information",
+			 RelationGetRelationName(matviewRel));
+
+	if (matviewRel->rd_rules->numLocks > 1)
+		elog(ERROR,
+			 "materialized view \"%s\" has too many rules",
+			 RelationGetRelationName(matviewRel));
+
+	rule = matviewRel->rd_rules->rules[0];
+	if (rule->event != CMD_SELECT || !(rule->isInstead))
+		elog(ERROR,
+			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
+			 RelationGetRelationName(matviewRel));
+
+	actions = rule->actions;
+	if (list_length(actions) != 1)
+		elog(ERROR,
+			 "the rule for materialized view \"%s\" is not a single action",
+			 RelationGetRelationName(matviewRel));
+
+	/*
+	 * The stored query was rewritten at the time of the MV definition, but
+	 * has not been scribbled on by the planner.
+	 */
+	return linitial_node(Query, actions);
+}
+
+
+/* ----------------------------------------------------
+ *		Incremental View Maintenance routines
+ * ---------------------------------------------------
+ */
+
+/*
+ * IVM_immediate_before
+ *
+ * IVM trigger function invoked before base table is modified. If this is
+ * invoked firstly in the same statement, we save the transaction id and the
+ * command id at that time.
+ */
+Datum
+IVM_immediate_before(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	char	   *matviewOid_text = trigdata->tg_trigger->tgargs[0];
+	char	   *ex_lock_text = trigdata->tg_trigger->tgargs[1];
+	Oid			matviewOid;
+	MV_TriggerHashEntry *entry;
+	bool	found;
+	bool	ex_lock;
+
+	matviewOid = DatumGetObjectId(DirectFunctionCall1(oidin, CStringGetDatum(matviewOid_text)));
+	ex_lock = DatumGetBool(DirectFunctionCall1(boolin, CStringGetDatum(ex_lock_text)));
+
+	/* If the view has more than one tables, we have to use an exclusive lock. */
+	if (ex_lock)
+	{
+		/*
+		 * Wait for concurrent transactions which update this materialized view at
+		 * READ COMMITED. This is needed to see changes committed in other
+		 * transactions. No wait and raise an error at REPEATABLE READ or
+		 * SERIALIZABLE to prevent update anomalies of matviews.
+		 * XXX: dead-lock is possible here.
+		 */
+		if (!IsolationUsesXactSnapshot())
+			LockRelationOid(matviewOid, ExclusiveLock);
+		else if (!ConditionalLockRelationOid(matviewOid, ExclusiveLock))
+		{
+			/* try to throw error by name; relation could be deleted... */
+			char	   *relname = get_rel_name(matviewOid);
+
+			if (!relname)
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						errmsg("could not obtain lock on materialized view during incremental maintenance")));
+
+			ereport(ERROR,
+					(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+					errmsg("could not obtain lock on materialized view \"%s\" during incremental maintenance",
+							relname)));
+		}
+	}
+	else
+		LockRelationOid(matviewOid, RowExclusiveLock);
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_trigger_info)
+		mv_InitHashTables();
+
+	entry = (MV_TriggerHashEntry *) hash_search(mv_trigger_info,
+											  (void *) &matviewOid,
+											  HASH_ENTER, &found);
+
+	/* On the first BEFORE to update the view, initialize trigger data */
+	if (!found)
+	{
+		Snapshot snapshot = GetActiveSnapshot();
+
+		entry->matview_id = matviewOid;
+		entry->before_trig_count = 0;
+		entry->after_trig_count = 0;
+		entry->xid = GetCurrentTransactionId();
+		entry->cid = snapshot->curcid;
+		entry->tables = NIL;
+		entry->has_old = false;
+		entry->has_new = false;
+	}
+
+	entry->before_trig_count++;
+
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * IVM_immediate_before
+ *
+ * IVM trigger function invoked after base table is modified.
+ * For each table, tuplestores of transition tables are collected.
+ * and after the last modification
+ */
+Datum
+IVM_immediate_maintenance(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	Relation	rel;
+	Oid			relid;
+	Oid			matviewOid;
+	Query	   *query;
+	Query	   *rewritten = NULL;
+	char	   *matviewOid_text = trigdata->tg_trigger->tgargs[0];
+	Relation	matviewRel;
+	int old_depth = matview_maintenance_depth;
+
+	Oid			relowner;
+	Tuplestorestate *old_tuplestore = NULL;
+	Tuplestorestate *new_tuplestore = NULL;
+	DestReceiver *dest_new = NULL, *dest_old = NULL;
+	Oid			save_userid;
+	int			save_sec_context;
+	int			save_nestlevel;
+
+	MV_TriggerHashEntry *entry;
+	MV_TriggerTable		*table;
+	bool	found;
+
+	ParseState		 *pstate;
+	QueryEnvironment *queryEnv = create_queryEnv();
+	MemoryContext	oldcxt;
+	ListCell   *lc;
+	int			i;
+
+
+	/* Create a ParseState for rewriting the view definition query */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	rel = trigdata->tg_relation;
+	relid = rel->rd_id;
+
+	matviewOid = DatumGetObjectId(DirectFunctionCall1(oidin, CStringGetDatum(matviewOid_text)));
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_trigger_info)
+		mv_InitHashTables();
+
+	/* get the entry for this materialized view */
+	entry = (MV_TriggerHashEntry *) hash_search(mv_trigger_info,
+											  (void *) &matviewOid,
+											  HASH_FIND, &found);
+	Assert (found && entry != NULL);
+	entry->after_trig_count++;
+
+	/* search the entry for the modified table and create new entry if not found */
+	found = false;
+	foreach(lc, entry->tables)
+	{
+		table = (MV_TriggerTable *) lfirst(lc);
+		if (table->table_id == relid)
+		{
+			found = true;
+			break;
+		}
+	}
+	if (!found)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		table = (MV_TriggerTable *) palloc0(sizeof(MV_TriggerTable));
+		table->table_id = relid;
+		table->old_tuplestores = NIL;
+		table->new_tuplestores = NIL;
+		table->old_rtes = NIL;
+		table->new_rtes = NIL;
+		table->rte_indexes = NIL;
+		entry->tables = lappend(entry->tables, table);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* Save the transition tables and make a request to not free immediately */
+	if (trigdata->tg_oldtable)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+		table->old_tuplestores = lappend(table->old_tuplestores, trigdata->tg_oldtable);
+		entry->has_old = true;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (trigdata->tg_newtable)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+		table->new_tuplestores = lappend(table->new_tuplestores, trigdata->tg_newtable);
+		entry->has_new = true;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (entry->has_new || entry->has_old)
+	{
+		CmdType cmd;
+
+		if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+			cmd = CMD_INSERT;
+		else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+			cmd = CMD_DELETE;
+		else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+			cmd = CMD_UPDATE;
+		else
+			elog(ERROR,"unsupported trigger type");
+
+		/* Prolong lifespan of transition tables to the end of the last AFTER trigger */
+		SetTransitionTablePreserved(relid, cmd);
+	}
+
+
+	/* If this is not the last AFTER trigger call, immediately exit. */
+	Assert (entry->before_trig_count >= entry->after_trig_count);
+	if (entry->before_trig_count != entry->after_trig_count)
+		return PointerGetDatum(NULL);
+
+	/*
+	 * If this is the last AFTER trigger call, continue and update the view.
+	 */
+
+	/*
+	 * Advance command counter to make the updated base table row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+
+	matviewRel = table_open(matviewOid, NoLock);
+
+	/* get view query*/
+	query = get_matview_query(matviewRel);
+
+	/* Make sure it is a materialized view. */
+	Assert(matviewRel->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Get and push the latast snapshot to see any changes which is committed
+	 * during waiting in other transactions at READ COMMITTED level.
+	 */
+	PushActiveSnapshot(GetTransactionSnapshot());
+
+	/*
+	 * Check for active uses of the relation in the current transaction, such
+	 * as open scans.
+	 *
+	 * NB: We count on this to protect us against problems with refreshing the
+	 * data using TABLE_INSERT_FROZEN.
+	 */
+	CheckTableNotInUse(matviewRel, "refresh a materialized view incrementally");
+
+	/*
+	 * Switch to the owner's userid, so that any functions are run as that
+	 * user.  Also arrange to make GUC variable changes local to this command.
+	 * We will switch modes when we are about to execute user code.
+	 */
+	relowner = matviewRel->rd_rel->relowner;
+	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+	save_nestlevel = NewGUCNestLevel();
+
+	/*
+	 * rewrite query for calculating deltas
+	 */
+
+	rewritten = copyObject(query);
+
+	/* Replace resnames in a target list with materialized view's attnames */
+	i = 0;
+	foreach (lc, rewritten->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char *resname = NameStr(attr->attname);
+
+		tle->resname = pstrdup(resname);
+		i++;
+	}
+
+	/* Set all tables in the query to pre-update state */
+	rewritten = rewrite_query_for_preupdate_state(rewritten, entry->tables,
+												  entry->xid, entry->cid,
+												  pstate);
+	/* Rewrite for DISTINCT clause */
+	rewritten = rewrite_query_for_distinct(rewritten, pstate);
+
+	/* Create tuplestores to store view deltas */
+	if (entry->has_old)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		old_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+		dest_old = CreateDestReceiver(DestTuplestore);
+		SetTuplestoreDestReceiverParams(dest_old,
+									old_tuplestore,
+									TopTransactionContext,
+									false,
+									NULL,
+									NULL);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (entry->has_new)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		new_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+		dest_new = CreateDestReceiver(DestTuplestore);
+		SetTuplestoreDestReceiverParams(dest_new,
+									new_tuplestore,
+									TopTransactionContext,
+									false,
+									NULL,
+									NULL);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* for all modified tables */
+	foreach(lc, entry->tables)
+	{
+		ListCell *lc2;
+
+		table = (MV_TriggerTable *) lfirst(lc);
+
+		/* loop for self-join */
+		foreach(lc2, table->rte_indexes)
+		{
+			int	rte_index = lfirst_int(lc2);
+			TupleDesc		tupdesc_old;
+			TupleDesc		tupdesc_new;
+			bool	use_count = false;
+			char   *count_colname = NULL;
+
+			count_colname = pstrdup("__ivm_count__");
+
+			if (query->distinctClause)
+				use_count = true;
+
+			/* calculate delta tables */
+			calc_delta(table, rte_index, rewritten, dest_old, dest_new,
+					   &tupdesc_old, &tupdesc_new, queryEnv);
+
+			/* Set the table in the query to post-update state */
+			rewritten = rewrite_query_for_postupdate_state(rewritten, table, rte_index);
+
+			PG_TRY();
+			{
+				/* apply the delta tables to the materialized view */
+				apply_delta(matviewOid, old_tuplestore, new_tuplestore,
+							tupdesc_old, tupdesc_new, query, use_count,
+							count_colname);
+			}
+			PG_CATCH();
+			{
+				matview_maintenance_depth = old_depth;
+				PG_RE_THROW();
+			}
+			PG_END_TRY();
+
+			/* clear view delta tuplestores */
+			if (old_tuplestore)
+				tuplestore_clear(old_tuplestore);
+			if (new_tuplestore)
+				tuplestore_clear(new_tuplestore);
+		}
+	}
+
+	/* Clean up hash entry and delete tuplestores */
+	clean_up_IVM_hash_entry(entry);
+	if (old_tuplestore)
+	{
+		dest_old->rDestroy(dest_old);
+		tuplestore_end(old_tuplestore);
+	}
+	if (new_tuplestore)
+	{
+		dest_new->rDestroy(dest_new);
+		tuplestore_end(new_tuplestore);
+	}
+
+	/* Pop the original snapshot. */
+	PopActiveSnapshot();
+
+	table_close(matviewRel, NoLock);
+
+	/* Roll back any GUC changes */
+	AtEOXact_GUC(false, save_nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(save_userid, save_sec_context);
+
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * rewrite_query_for_preupdate_state
+ *
+ * Rewrite the query so that base tables' RTEs will represent "pre-update"
+ * state of tables. This is necessary to calculate view delta after multiple
+ * tables are modified. xid and cid are the transaction id and command id
+ * before the first table was modified.
+ */
+static Query*
+rewrite_query_for_preupdate_state(Query *query, List *tables,
+								  TransactionId xid, CommandId cid,
+								  ParseState *pstate)
+{
+	ListCell *lc;
+	int num_rte = list_length(query->rtable);
+	int i;
+
+
+	/* register delta ENRs */
+	register_delta_ENRs(pstate, query, tables);
+
+	/* XXX: Is necessary? Is this right timing? */
+	AcquireRewriteLocks(query, true, false);
+
+	i = 1;
+	foreach(lc, query->rtable)
+	{
+		RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+
+		ListCell *lc2;
+		foreach(lc2, tables)
+		{
+			MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc2);
+			/*
+			 * if the modified table is found then replace the original RTE with
+			 * "pre-state" RTE and append its index to the list.
+			 */
+			if (r->relid == table->table_id)
+			{
+				lfirst(lc) = get_prestate_rte(r, table, xid, cid, pstate->p_queryEnv);
+				table->rte_indexes = lappend_int(table->rte_indexes, i);
+				break;
+			}
+		}
+
+		/* finish the loop if we processed all RTE included in the original query */
+		if (i++ >= num_rte)
+			break;
+	}
+
+	return query;
+}
+
+/*
+ * register_delta_ENRs
+ *
+ * For all modified tables, make ENRs for their transition tables
+ * and register them to the queryEnv. ENR's RTEs are also appended
+ * into the list in query tree.
+ */
+static void
+register_delta_ENRs(ParseState *pstate, Query *query, List *tables)
+{
+	QueryEnvironment *queryEnv = pstate->p_queryEnv;
+	ListCell *lc;
+	RangeTblEntry	*rte;
+
+	foreach(lc, tables)
+	{
+		MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc);
+		ListCell *lc2;
+		int count;
+
+		count = 0;
+		foreach(lc2, table->old_tuplestores)
+		{
+			Tuplestorestate *oldtable = (Tuplestorestate *) lfirst(lc2);
+			EphemeralNamedRelation enr =
+				palloc(sizeof(EphemeralNamedRelationData));
+			ParseNamespaceItem *nsitem;
+
+			enr->md.name = make_delta_enr_name("old", table->table_id, count);
+			enr->md.reliddesc = table->table_id;
+			enr->md.tupdesc = NULL;
+			enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+			enr->md.enrtuples = tuplestore_tuple_count(oldtable);
+			enr->reldata = oldtable;
+			register_ENR(queryEnv, enr);
+
+			nsitem = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+			rte = nsitem->p_rte;
+			/* if base table has RLS, set security condition to enr */
+			rte->securityQuals = get_securityQuals(table->table_id, list_length(query->rtable) + 1, query);
+
+			query->rtable = lappend(query->rtable, rte);
+			table->old_rtes = lappend(table->old_rtes, rte);
+
+			count++;
+		}
+
+		count = 0;
+		foreach(lc2, table->new_tuplestores)
+		{
+			Tuplestorestate *newtable = (Tuplestorestate *) lfirst(lc2);
+			EphemeralNamedRelation enr =
+				palloc(sizeof(EphemeralNamedRelationData));
+			ParseNamespaceItem *nsitem;
+
+			enr->md.name = make_delta_enr_name("new", table->table_id, count);
+			enr->md.reliddesc = table->table_id;
+			enr->md.tupdesc = NULL;
+			enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+			enr->md.enrtuples = tuplestore_tuple_count(newtable);
+			enr->reldata = newtable;
+			register_ENR(queryEnv, enr);
+
+			nsitem = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+			rte = nsitem->p_rte;
+			/* if base table has RLS, set security condition to enr*/
+			rte->securityQuals = get_securityQuals(table->table_id, list_length(query->rtable) + 1, query);
+
+			query->rtable = lappend(query->rtable, rte);
+			table->new_rtes = lappend(table->new_rtes, rte);
+
+			count++;
+		}
+	}
+}
+
+/*
+ * get_prestate_rte
+ *
+ * Rewrite RTE of the modified table to a subquery which represents
+ * "pre-state" table. The original RTE is saved in table->rte_original.
+ */
+static RangeTblEntry*
+get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
+				 TransactionId xid, CommandId cid,
+				 QueryEnvironment *queryEnv)
+{
+	StringInfoData str;
+	RawStmt *raw;
+	Query *sub;
+	Relation rel;
+	ParseState *pstate;
+	char *relname;
+	int i;
+
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	/*
+	 * We can use NoLock here since AcquireRewriteLocks should
+	 * have locked the rel already.
+	 */
+	rel = table_open(table->table_id, NoLock);
+	relname = quote_qualified_identifier(
+					get_namespace_name(RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+	table_close(rel, NoLock);
+
+	initStringInfo(&str);
+	appendStringInfo(&str,
+		"SELECT t.* FROM %s t"
+		" WHERE (age(t.xmin) - age(%u::text::xid) > 0) OR"
+		" (t.xmin = %u AND t.cmin::text::int < %u)",
+			relname, xid, xid, cid);
+
+	for (i = 0; i < list_length(table->old_tuplestores); i++)
+	{
+		appendStringInfo(&str, " UNION ALL ");
+		appendStringInfo(&str," SELECT * FROM %s",
+			make_delta_enr_name("old", table->table_id, i));
+	}
+
+	raw = (RawStmt*)linitial(raw_parser(str.data, RAW_PARSE_DEFAULT));
+	sub = transformStmt(pstate, raw->stmt);
+
+	/* If this query has setOperations, RTEs in rtables has a subquery which contains ENR */
+	if (sub->setOperations != NULL)
+	{
+		ListCell *lc;
+
+		/* add securityQuals for tuplestores */
+		foreach (lc, sub->rtable)
+		{
+			RangeTblEntry *rte;
+			RangeTblEntry *sub_rte;
+
+			rte = (RangeTblEntry *)lfirst(lc);
+			Assert(rte->subquery != NULL);
+
+			sub_rte = (RangeTblEntry *)linitial(rte->subquery->rtable);
+			if (sub_rte->rtekind == RTE_NAMEDTUPLESTORE)
+				/* rt_index is always 1, bacause subquery has enr_rte only */
+				sub_rte->securityQuals = get_securityQuals(sub_rte->relid, 1, sub);
+		}
+	}
+
+	/* save the original RTE */
+	table->original_rte = copyObject(rte);
+
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = sub;
+	rte->security_barrier = false;
+	/* Clear fields that should not be set in a subquery RTE */
+	rte->relid = InvalidOid;
+	rte->relkind = 0;
+	rte->rellockmode = 0;
+	rte->tablesample = NULL;
+	rte->inh = false;			/* must not be set for a subquery */
+
+	rte->requiredPerms = 0;		/* no permission check on subquery itself */
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->insertedCols = NULL;
+	rte->updatedCols = NULL;
+	rte->extraUpdatedCols = NULL;
+
+	return rte;
+}
+
+/*
+ * make_delta_enr_name
+ *
+ * Make a name for ENR of a transition table from the base table's oid.
+ * prefix will be "new" or "old" depending on its transition table kind..
+ */
+static char*
+make_delta_enr_name(const char *prefix, Oid relid, int count)
+{
+	char buf[NAMEDATALEN];
+	char *name;
+
+	snprintf(buf, NAMEDATALEN, "__ivm_%s_%u_%u", prefix, relid, count);
+	name = pstrdup(buf);
+
+	return name;
+}
+
+/*
+ * union_ENRs
+ *
+ * Make a single table delta by unionning all transition tables of the modified table
+ * whose RTE is specified by
+ */
+static RangeTblEntry*
+union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
+		   QueryEnvironment *queryEnv)
+{
+	StringInfoData str;
+	ParseState	*pstate;
+	RawStmt *raw;
+	Query *sub;
+	int	i;
+	RangeTblEntry *enr_rte;
+
+	/* Create a ParseState for rewriting the view definition query */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	initStringInfo(&str);
+
+	for (i = 0; i < list_length(enr_rtes); i++)
+	{
+		if (i > 0)
+			appendStringInfo(&str, " UNION ALL ");
+
+		appendStringInfo(&str,
+			" SELECT * FROM %s",
+			make_delta_enr_name(prefix, relid, i));
+	}
+
+	raw = (RawStmt*)linitial(raw_parser(str.data, RAW_PARSE_DEFAULT));
+	sub = transformStmt(pstate, raw->stmt);
+
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = sub;
+	rte->security_barrier = false;
+	/* Clear fields that should not be set in a subquery RTE */
+	rte->relid = InvalidOid;
+	rte->relkind = 0;
+	rte->rellockmode = 0;
+	rte->tablesample = NULL;
+	rte->inh = false;			/* must not be set for a subquery */
+
+	rte->requiredPerms = 0;		/* no permission check on subquery itself */
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->insertedCols = NULL;
+	rte->updatedCols = NULL;
+	rte->extraUpdatedCols = NULL;
+	/* if base table has RLS, set security condition to enr*/
+	enr_rte = (RangeTblEntry *)linitial(sub->rtable);
+	/* rt_index is always 1, bacause subquery has enr_rte only */
+	enr_rte->securityQuals = get_securityQuals(relid, 1, sub);
+
+	return rte;
+}
+
+/*
+ * rewrite_query_for_distinct
+ *
+ * Rewrite query for counting DISTINCT clause.
+ */
+static Query *
+rewrite_query_for_distinct(Query *query, ParseState *pstate)
+{
+	TargetEntry *tle_count;
+	FuncCall *fn;
+	Node *node;
+
+	/* Add count(*) for counting distinct tuples in views */
+	fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+	fn->agg_star = true;
+	if (!query->groupClause && !query->hasAggs)
+		query->groupClause = transformDistinctClause(NULL, &query->targetList, query->sortClause, false);
+
+	node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+	tle_count = makeTargetEntry((Expr *) node,
+								list_length(query->targetList) + 1,
+								pstrdup("__ivm_count__"),
+								false);
+	query->targetList = lappend(query->targetList, tle_count);
+	query->hasAggs = true;
+
+	return query;
+}
+
+/*
+ * calc_delta
+ *
+ * Calculate view deltas generated under the modification of a table specified
+ * by the RTE index.
+ */
+static void
+calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
+			DestReceiver *dest_old, DestReceiver *dest_new,
+			TupleDesc *tupdesc_old, TupleDesc *tupdesc_new,
+			QueryEnvironment *queryEnv)
+{
+	ListCell *lc = list_nth_cell(query->rtable, rte_index - 1);
+	RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+	/* Generate old delta */
+	if (list_length(table->old_rtes) > 0)
+	{
+		/* Replace the modified table with the old delta table and calculate the old view delta. */
+		lfirst(lc) = union_ENRs(rte, table->table_id, table->old_rtes, "old", queryEnv);
+		refresh_matview_datafill(dest_old, query, queryEnv, tupdesc_old, "");
+	}
+
+	/* Generate new delta */
+	if (list_length(table->new_rtes) > 0)
+	{
+		/* Replace the modified table with the new delta table and calculate the new view delta*/
+		lfirst(lc) = union_ENRs(rte, table->table_id, table->new_rtes, "new", queryEnv);
+		refresh_matview_datafill(dest_new, query, queryEnv, tupdesc_new, "");
+	}
+}
+
+/*
+ * rewrite_query_for_postupdate_state
+ *
+ * Rewrite the query so that the specified base table's RTEs will represent
+ * "post-update" state of tables. This is called after the view delta
+ * calculation due to changes on this table finishes.
+ */
+static Query*
+rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte_index)
+{
+	ListCell *lc = list_nth_cell(query->rtable, rte_index - 1);
+
+	/* Retore the original RTE */
+	lfirst(lc) = table->original_rte;
+
+	return query;
+}
+
+/*
+ * apply_delta
+ *
+ * Apply deltas to the materialized view. In outer join cases, this requires
+ * the view maintenance graph.
+ */
+static void
+apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
+			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
+			Query *query, bool use_count, char *count_colname)
+{
+	StringInfoData querybuf;
+	StringInfoData target_list_buf;
+	Relation	matviewRel;
+	char	   *matviewname;
+	ListCell	*lc;
+	int			i;
+	List	   *keys = NIL;
+
+
+	/*
+	 * get names of the materialized view and delta tables
+	 */
+
+	matviewRel = table_open(matviewOid, NoLock);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	/*
+	 * Build parts of the maintenance queries
+	 */
+
+	initStringInfo(&querybuf);
+	initStringInfo(&target_list_buf);
+
+	/* build string of target list */
+	for (i = 0; i < matviewRel->rd_att->natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char   *resname = NameStr(attr->attname);
+
+		if (i != 0)
+			appendStringInfo(&target_list_buf, ", ");
+		appendStringInfo(&target_list_buf, "%s", quote_qualified_identifier(NULL, resname));
+	}
+
+	i = 0;
+	foreach (lc, query->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char *resname = NameStr(attr->attname);
+
+		i++;
+
+		if (tle->resjunk)
+			continue;
+
+		keys = lappend(keys, resname);
+	}
+
+	/* Start maintaining the materialized view. */
+	OpenMatViewIncrementalMaintenance();
+
+	/* Open SPI context. */
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	/* For tuple deletion */
+	if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
+	{
+		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		int				rc;
+
+		/* convert tuplestores to ENR, and register for SPI */
+		enr->md.name = pstrdup(OLD_DELTA_ENRNAME);
+		enr->md.reliddesc = InvalidOid;
+		enr->md.tupdesc = tupdesc_old;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(old_tuplestores);
+		enr->reldata = old_tuplestores;
+
+		rc = SPI_register_relation(enr);
+		if (rc != SPI_OK_REL_REGISTER)
+			elog(ERROR, "SPI_register failed");
+
+		if (use_count)
+			/* apply old delta and get rows to be recalculated */
+			apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME,
+									   keys, count_colname);
+		else
+			apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys);
+
+	}
+	/* For tuple insertion */
+	if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
+	{
+		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		int rc;
+
+		/* convert tuplestores to ENR, and register for SPI */
+		enr->md.name = pstrdup(NEW_DELTA_ENRNAME);
+		enr->md.reliddesc = InvalidOid;
+		enr->md.tupdesc = tupdesc_new;;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(new_tuplestores);
+		enr->reldata = new_tuplestores;
+
+		rc = SPI_register_relation(enr);
+		if (rc != SPI_OK_REL_REGISTER)
+			elog(ERROR, "SPI_register failed");
+
+		/* apply new delta */
+		if (use_count)
+			apply_new_delta_with_count(matviewname, NEW_DELTA_ENRNAME,
+								keys, &target_list_buf, count_colname);
+		else
+			apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf);
+	}
+
+	/* We're done maintaining the materialized view. */
+	CloseMatViewIncrementalMaintenance();
+
+	table_close(matviewRel, NoLock);
+
+	/* Close SPI context. */
+	if (SPI_finish() != SPI_OK_FINISH)
+		elog(ERROR, "SPI_finish failed");
+}
+
+/*
+ * apply_old_delta_with_count
+ *
+ * Execute a query for applying a delta table given by deltname_old
+ * which contains tuples to be deleted from to a materialized view given by
+ * matviewname.  This is used when counting is required, that is, the view
+ * has aggregate or distinct.
+ */
+static void
+apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
+				List *keys, const char *count_colname)
+{
+	StringInfoData	querybuf;
+	char   *match_cond;
+
+	/* build WHERE condition for searching tuples to be deleted */
+	match_cond = get_matching_condition_string(keys);
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"WITH t AS ("			/* collecting tid of target tuples in the view */
+						"SELECT diff.%s, "			/* count column */
+								"(diff.%s OPERATOR(pg_catalog.=) mv.%s) AS for_dlt, "
+								"mv.ctid "
+						"FROM %s AS mv, %s AS diff "
+						"WHERE %s"					/* tuple matching condition */
+					"), updt AS ("			/* update a tuple if this is not to be deleted */
+						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.-) t.%s "
+						"FROM t WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND NOT for_dlt "
+					"), dlt AS ("			/* delete a tuple if this is to be deleted */
+						"DELETE FROM %s AS mv USING t "
+						"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt"
+					")",
+					count_colname,
+					count_colname, count_colname,
+					matviewname, deltaname_old,
+					match_cond,
+					matviewname, count_colname, count_colname, count_colname,
+					matviewname);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_old_delta
+ *
+ * Execute a query for applying a delta table given by deltname_old
+ * which contains tuples to be deleted from to a materialized view given by
+ * matviewname.  This is used when counting is not required.
+ */
+static void
+apply_old_delta(const char *matviewname, const char *deltaname_old,
+				List *keys)
+{
+	StringInfoData	querybuf;
+	StringInfoData	keysbuf;
+	char   *match_cond;
+	ListCell *lc;
+
+	/* build WHERE condition for searching tuples to be deleted */
+	match_cond = get_matching_condition_string(keys);
+
+	/* build string of keys list */
+	initStringInfo(&keysbuf);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char   *resname = NameStr(attr->attname);
+		appendStringInfo(&keysbuf, "%s", quote_qualified_identifier("mv", resname));
+		if (lnext(keys, lc))
+			appendStringInfo(&keysbuf, ", ");
+	}
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+	"DELETE FROM %s WHERE ctid IN ("
+		"SELECT tid FROM (SELECT row_number() over (partition by %s) AS \"__ivm_row_number__\","
+								  "mv.ctid AS tid,"
+								  "diff.\"__ivm_count__\""
+						 "FROM %s AS mv, %s AS diff "
+						 "WHERE %s) v "
+					"WHERE v.\"__ivm_row_number__\" OPERATOR(pg_catalog.<=) v.\"__ivm_count__\")",
+					matviewname,
+					keysbuf.data,
+					matviewname, deltaname_old,
+					match_cond);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_new_delta_with_count
+ *
+ * Execute a query for applying a delta table given by deltname_new
+ * which contains tuples to be inserted into a materialized view given by
+ * matviewname.  This is used when counting is required, that is, the view
+ * has aggregate or distinct. Also, when a table in EXISTS sub queries
+ * is modified.
+ */
+static void
+apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
+				List *keys, StringInfo target_list, const char* count_colname)
+{
+	StringInfoData	querybuf;
+	StringInfoData	returning_keys;
+	ListCell	*lc;
+	char	*match_cond = "";
+
+	/* build WHERE condition for searching tuples to be updated */
+	match_cond = get_matching_condition_string(keys);
+
+	/* build string of keys list */
+	initStringInfo(&returning_keys);
+	if (keys)
+	{
+		foreach (lc, keys)
+		{
+			Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+			char   *resname = NameStr(attr->attname);
+			appendStringInfo(&returning_keys, "%s", quote_qualified_identifier("mv", resname));
+			if (lnext(keys, lc))
+				appendStringInfo(&returning_keys, ", ");
+		}
+	}
+	else
+		appendStringInfo(&returning_keys, "NULL");
+
+	/* Search for matching tuples from the view and update if found or insert if not. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"WITH updt AS ("		/* update a tuple if this exists in the view */
+						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.+) diff.%s "
+						"FROM %s AS diff "
+						"WHERE %s "					/* tuple matching condition */
+						"RETURNING %s"				/* returning keys of updated tuples */
+					") INSERT INTO %s (%s)"	/* insert a new tuple if this doesn't existw */
+						"SELECT %s FROM %s AS diff "
+						"WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE %s);",
+					matviewname, count_colname, count_colname, count_colname,
+					deltaname_new,
+					match_cond,
+					returning_keys.data,
+					matviewname, target_list->data,
+					target_list->data, deltaname_new,
+					match_cond);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_new_delta
+ *
+ * Execute a query for applying a delta table given by deltname_new
+ * which contains tuples to be inserted into a materialized view given by
+ * matviewname.  This is used when counting is not required.
+ */
+static void
+apply_new_delta(const char *matviewname, const char *deltaname_new,
+				StringInfo target_list)
+{
+	StringInfoData	querybuf;
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"INSERT INTO %s (%s) SELECT %s FROM ("
+						"SELECT diff.*, generate_series(1, diff.\"__ivm_count__\") "
+						"FROM %s AS diff) AS v",
+					matviewname, target_list->data, target_list->data,
+					deltaname_new);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * get_matching_condition_string
+ *
+ * Build a predicate string for looking for a tuple with given keys.
+ */
+static char *
+get_matching_condition_string(List *keys)
+{
+	StringInfoData match_cond;
+	ListCell	*lc;
+
+	/* If there is no key columns, the condition is always true. */
+	if (keys == NIL)
+		return "true";
+
+	initStringInfo(&match_cond);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char   *resname = NameStr(attr->attname);
+		char   *mv_resname = quote_qualified_identifier("mv", resname);
+		char   *diff_resname = quote_qualified_identifier("diff", resname);
+		Oid		typid = attr->atttypid;
+
+		/* Considering NULL values, we can not use simple = operator. */
+		appendStringInfo(&match_cond, "(");
+		generate_equal(&match_cond, typid, mv_resname, diff_resname);
+		appendStringInfo(&match_cond, " OR (%s IS NULL AND %s IS NULL))",
+						 mv_resname, diff_resname);
+
+		if (lnext(keys, lc))
+			appendStringInfo(&match_cond, " AND ");
+	}
+
+	return match_cond.data;
+}
+
+/*
+ * generate_equals
+ *
+ * Generate an equality clause using given operands' default equality
+ * operator.
+ */
+static void
+generate_equal(StringInfo querybuf, Oid opttype,
+			   const char *leftop, const char *rightop)
+{
+	TypeCacheEntry *typentry;
+
+	typentry = lookup_type_cache(opttype, TYPECACHE_EQ_OPR);
+	if (!OidIsValid(typentry->eq_opr))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_FUNCTION),
+				 errmsg("could not identify an equality operator for type %s",
+						format_type_be(opttype))));
+
+	generate_operator_clause(querybuf,
+							 leftop, opttype,
+							 typentry->eq_opr,
+							 rightop, opttype);
+}
+
+/*
+ * mv_InitHashTables
+ */
+static void
+mv_InitHashTables(void)
+{
+	HASHCTL		ctl;
+
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(MV_TriggerHashEntry);
+	mv_trigger_info = hash_create("MV trigger info",
+								 MV_INIT_QUERYHASHSIZE,
+								 &ctl, HASH_ELEM | HASH_BLOBS);
+}
+
+/*
+ * AtAbort_IVM
+ *
+ * Clean up hash entries for all materialized views. This is called at
+ * transaction abort.
+ */
+void
+AtAbort_IVM()
+{
+	HASH_SEQ_STATUS seq;
+	MV_TriggerHashEntry *entry;
+
+	if (mv_trigger_info)
+	{
+		hash_seq_init(&seq, mv_trigger_info);
+		while ((entry = hash_seq_search(&seq)) != NULL)
+			clean_up_IVM_hash_entry(entry);
+	}
+}
+
+/*
+ * clean_up_IVM_hash_entry
+ *
+ * Clean up tuple stores and hash entries for a materialized view after its
+ * maintenance finished.
+ */
+static void
+clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry)
+{
+	bool found;
+	ListCell *lc;
+
+	foreach(lc, entry->tables)
+	{
+		MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc);
+
+		list_free(table->old_tuplestores);
+		list_free(table->new_tuplestores);
+	}
+	list_free(entry->tables);
+
+	hash_search(mv_trigger_info, (void *) &entry->matview_id, HASH_REMOVE, &found);
+}
+
+/*
+ * isIvmName
+ *
+ * Check if this is a IVM hidden column from the name.
+ */
+bool
+isIvmName(const char *s)
+{
+	if (s)
+		return (strncmp(s, "__ivm_", 6) == 0);
+	return false;
+}
+
+/*
+ * get_securityQuals
+ *
+ * Get row security policy on a relation.
+ * This is used by IVM for copying RLS from base table to enr.
+ */
+static List *
+get_securityQuals(Oid relId, int rt_index, Query *query)
+{
+	ParseState *pstate;
+	Relation rel;
+	ParseNamespaceItem *nsitem;
+	RangeTblEntry *rte;
+	List *securityQuals;
+	List *withCheckOptions;
+	bool  hasRowSecurity;
+	bool  hasSubLinks;
+
+	securityQuals = NIL;
+	pstate = make_parsestate(NULL);
+
+	rel = table_open(relId, NoLock);
+	nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, false);
+	rte = nsitem->p_rte;
+
+	get_row_security_policies(query, rte, rt_index,
+							  &securityQuals, &withCheckOptions,
+							  &hasRowSecurity, &hasSubLinks);
+
+	/*
+	 * Make sure the query is marked correctly if row level security
+	 * applies, or if the new quals had sublinks.
+	 */
+	if (hasRowSecurity)
+		query->hasRowSecurity = true;
+	if (hasSubLinks)
+		query->hasSubLinks = true;
+
+	table_close(rel, NoLock);
+
+	return securityQuals;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a16e749506..9aad4792e5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -50,6 +50,7 @@
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
+#include "commands/matview.h"
 #include "commands/event_trigger.h"
 #include "commands/policy.h"
 #include "commands/sequence.h"
@@ -3437,6 +3438,14 @@ renameatt_internal(Oid myrelid,
 	targetrelation = relation_open(myrelid, AccessExclusiveLock);
 	renameatt_check(myrelid, RelationGetForm(targetrelation), recursing);
 
+	/*
+	 * Don't rename IVM columns.
+	 */
+	if (RelationIsIVM(targetrelation) && isIvmName(oldattname))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("IVM column can not be renamed")));
+
 	/*
 	 * if the 'recurse' flag is set then we are supposed to rename this
 	 * attribute in all classes that inherit from 'relname' (as well as in
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 29020c908e..afda022a60 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2467,6 +2467,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(relisivm);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8a1762000c..32345b7f41 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2730,6 +2730,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(relisivm);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 48202d2232..fd87a6153a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3251,6 +3251,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 77d082d8b4..cd85f6910a 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1442,6 +1442,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 7465919044..5246c78ff8 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -36,6 +36,7 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 #include "utils/varlena.h"
+#include "commands/matview.h"
 
 
 /*
@@ -79,7 +80,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							int count, int offset,
 							int rtindex, int sublevels_up,
 							int location, bool include_dropped,
-							List **colnames, List **colvars);
+							List **colnames, List **colvars, bool is_ivm);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
 
@@ -1433,6 +1434,7 @@ addRangeTableEntry(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -1521,6 +1523,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -2676,7 +2679,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 						expandTupleDesc(tupdesc, rte->eref,
 										rtfunc->funccolcount, atts_done,
 										rtindex, sublevels_up, location,
-										include_dropped, colnames, colvars);
+										include_dropped, colnames, colvars, false);
 					}
 					else if (functypclass == TYPEFUNC_SCALAR)
 					{
@@ -2944,7 +2947,7 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
 	expandTupleDesc(rel->rd_att, eref, rel->rd_att->natts, 0,
 					rtindex, sublevels_up,
 					location, include_dropped,
-					colnames, colvars);
+					colnames, colvars, RelationIsIVM(rel));
 	relation_close(rel, AccessShareLock);
 }
 
@@ -2961,7 +2964,7 @@ static void
 expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 				int rtindex, int sublevels_up,
 				int location, bool include_dropped,
-				List **colnames, List **colvars)
+				List **colnames, List **colvars, bool is_ivm)
 {
 	ListCell   *aliascell;
 	int			varattno;
@@ -2974,6 +2977,9 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 	{
 		Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
 
+		if (is_ivm && isIvmName(NameStr(attr->attname)) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		if (attr->attisdropped)
 		{
 			if (include_dropped)
@@ -3127,6 +3133,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 		Var		   *varnode = (Var *) lfirst(var);
 		TargetEntry *te;
 
+		/* if transform * into columnlist with IMMV, remove IVM columns */
+		if (rte->relisivm && isIvmName(label) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		te = makeTargetEntry((Expr *) varnode,
 							 (AttrNumber) pstate->p_next_resno++,
 							 label,
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 6589345ac4..02f33d404b 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -776,7 +776,8 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 														attr->atttypmod))));
 	}
 
-	if (i != resultDesc->natts)
+	/* No check for materialized views since this could have special columns for IVM */
+	if ((!isSelect || requireColumnNameMatch) && i != resultDesc->natts)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 isSelect ?
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 8cd0252082..c2841ab604 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11625,4 +11625,12 @@
   prorettype => 'bytea', proargtypes => 'pg_brin_minmax_multi_summary',
   prosrc => 'brin_minmax_multi_summary_send' },
 
+# IVM
+{ oid => '786', descr => 'ivm trigger (before)',
+  proname => 'IVM_immediate_before', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_before' },
+{ oid => '787', descr => 'ivm trigger (after)',
+  proname => 'IVM_immediate_maintenance', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_maintenance' },
+
 ]
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index ad5054d116..a57ce463e1 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -16,6 +16,7 @@
 
 #include "catalog/objectaddress.h"
 #include "nodes/params.h"
+#include "nodes/pathnodes.h"
 #include "parser/parse_node.h"
 #include "tcop/dest.h"
 #include "utils/queryenvironment.h"
@@ -25,6 +26,10 @@ extern ObjectAddress ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *st
 									   ParamListInfo params, QueryEnvironment *queryEnv,
 									   QueryCompletion *qc);
 
+extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create);
+
+extern Query *rewriteQueryForIMMV(Query *query, List *colNames);
+
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
 
 extern DestReceiver *CreateIntoRelDestReceiver(IntoClause *intoClause);
diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h
index 214b1c1df6..13a5722f17 100644
--- a/src/include/commands/matview.h
+++ b/src/include/commands/matview.h
@@ -15,6 +15,7 @@
 #define MATVIEW_H
 
 #include "catalog/objectaddress.h"
+#include "fmgr.h"
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
 #include "tcop/dest.h"
@@ -23,6 +24,8 @@
 
 extern void SetMatViewPopulatedState(Relation relation, bool newstate);
 
+extern void SetMatViewIVMState(Relation relation, bool newstate);
+
 extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										ParamListInfo params, QueryCompletion *qc);
 
@@ -30,4 +33,9 @@ extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
 
 extern bool MatViewIncrementalMaintenanceIsEnabled(void);
 
+extern Datum IVM_immediate_before(PG_FUNCTION_ARGS);
+extern Datum IVM_immediate_maintenance(PG_FUNCTION_ARGS);
+extern void AtAbort_IVM(void);
+extern bool isIvmName(const char *s);
+
 #endif							/* MATVIEW_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 947660a4b0..d10ce8780b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1023,6 +1023,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		relisivm;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -2180,6 +2181,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		ivm;			/* incremental view maintenance is used by materialized view */
 } CreateStmt;
 
 /* ----------
-- 
2.17.1

v23-0008-Add-aggregates-support-in-IVM.patchtext/x-diff; name=v23-0008-Add-aggregates-support-in-IVM.patchDownload
From 7f00d0bbf0601f391c5709296061c743cc9d4200 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Mon, 2 Aug 2021 14:59:27 +0900
Subject: [PATCH v23 08/15] Add aggregates support in IVM
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Currently, count, sum, avg, min and max are supported.

As a restriction, expressions specified in GROUP BY must appear in
the target list because tuples to be updated in IMMV are identified
by using this group keys. However, in the case of aggregates without
GROUP BY, there is only one tuple in the view, so keys are not uses
to identify tuples.

When creating a IMMV, in addition to __ivm_count column, some hidden
columns for each aggregate are added to the target list. For example,
names of these hidden columns are ivm_count_avg and ivm_sum_avg for
the average function, and so on.

In the case of views without aggregate functions, only the number of
tuple multiplicities in __ivm_count__ column are updated at incremental
maintenance. On the other hand, in the case of view with aggregates,
the aggregated values and related hidden columns are also updated. The
way of update depends the kind of aggregate function. Specifically,
sum and count are updated by simply adding or subtracting delta value
calculated from delta tables. avg is updated by using values of sum
and count stored in views as hidden columns and deltas calculated
from delta tables.

In min or max cases, it becomes more complicated. For an example of min,
when tuples are inserted, the smaller value between the current min value
in the view and the value calculated from the new delta table is used.
When tuples are deleted, if the current min value in the view equals to
the min in the old delta table, we need re-computation the latest min
value from base tables. Otherwise, the current value in the view remains.

As to sum, avg, min, and max (any aggregate functions except to count),
NULL in input values is ignored, and this returns a null value when no
rows are selected. To support this specification, the number of not-NULL
input values is counted and stored in views as a hidden column. In the
case of count(), count(x) returns zero when no rows are selected, and
count(*) doesn't ignore NULL input. These specification are also supported.
---
 src/backend/commands/createas.c |  294 ++++++++-
 src/backend/commands/matview.c  | 1016 ++++++++++++++++++++++++++++++-
 src/include/commands/createas.h |    1 +
 3 files changed, 1280 insertions(+), 31 deletions(-)

diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 3fc2af1c4a..aebbb7290e 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/regproc.h"
 #include "utils/rel.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
@@ -80,6 +81,11 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_intorel;
 
+typedef struct
+{
+	bool	has_agg;
+} check_ivm_restriction_context;
+
 /* utility functions for CTAS definition creation */
 static ObjectAddress create_ctas_internal(List *attrList, IntoClause *into);
 static ObjectAddress create_ctas_nodata(List *tlist, IntoClause *into);
@@ -93,9 +99,10 @@ static void intorel_destroy(DestReceiver *self);
 static void CreateIvmTriggersOnBaseTables_recurse(Query *qry, Node *node, Oid matviewOid, Relids *relids, bool ex_lock);
 static void CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock);
 static void check_ivm_restriction(Node *node);
-static bool check_ivm_restriction_walker(Node *node, void *context);
+static bool check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context);
 static void CreateIndexOnIMMV(Query *query, Relation matviewRel);
 static Bitmapset *get_primary_key_attnos_from_query(Query *qry, List **constraintList);
+static bool check_aggregate_supports_ivm(Oid aggfnoid);
 
 /*
  * create_ctas_internal
@@ -431,6 +438,7 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
  * rewriteQueryForIMMV -- rewrite view definition query for IMMV
  *
  * count(*) is added for counting distinct tuples in views.
+ * Also, additional hidden columns are added for aggregate values.
  */
 Query *
 rewriteQueryForIMMV(Query *query, List *colNames)
@@ -445,12 +453,45 @@ rewriteQueryForIMMV(Query *query, List *colNames)
 	rewritten = copyObject(query);
 	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
 
+	/* group keys must be in targetlist */
+	if (rewritten->groupClause)
+	{
+		ListCell *lc;
+		foreach(lc, rewritten->groupClause)
+		{
+			SortGroupClause *scl = (SortGroupClause *) lfirst(lc);
+			TargetEntry *tle = get_sortgroupclause_tle(scl, rewritten->targetList);
+
+			if (tle->resjunk)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("GROUP BY expression not appeared in select list is not supported on incrementally maintainable materialized view")));
+		}
+	}
 	/* Convert DISTINCT to GROUP BY.  count(*) will be added afterward. */
-	if (rewritten->distinctClause)
+	else if (!rewritten->hasAggs && rewritten->distinctClause)
 		rewritten->groupClause = transformDistinctClause(NULL, &rewritten->targetList, rewritten->sortClause, false);
 
+	/* Add additional columns for aggregate values */
+	if (rewritten->hasAggs)
+	{
+		ListCell *lc;
+		List *aggs = NIL;
+		AttrNumber next_resno = list_length(rewritten->targetList) + 1;
+
+		foreach(lc, rewritten->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+			char *resname = (colNames == NIL ? tle->resname : strVal(list_nth(colNames, tle->resno - 1)));
+
+			if (IsA(tle->expr, Aggref))
+				makeIvmAggColumn(pstate, (Aggref *)tle->expr, resname, &next_resno, &aggs);
+		}
+		rewritten->targetList = list_concat(rewritten->targetList, aggs);
+	}
+
 	/* Add count(*) for counting distinct tuples in views */
-	if (rewritten->distinctClause)
+	if (rewritten->distinctClause || rewritten->hasAggs)
 	{
 		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
 		fn->agg_star = true;
@@ -468,6 +509,91 @@ rewriteQueryForIMMV(Query *query, List *colNames)
 	return rewritten;
 }
 
+/*
+ * makeIvmAggColumn -- make additional aggregate columns for IVM
+ *
+ * For an aggregate column specified by aggref, additional aggregate columns
+ * are added, which are used to calculate the new aggregate value in IMMV.
+ * An additional aggregate columns has a name based on resname
+ * (ex. ivm_count_resname), and resno specified by next_resno. The created
+ * columns are returned to aggs, and the resno for the next column is also
+ * returned to next_resno.
+ *
+ * Currently, an additional count() is created for aggref other than count.
+ * In addition, sum() is created for avg aggregate column.
+ */
+void
+makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber *next_resno, List **aggs)
+{
+	TargetEntry *tle_count;
+	Node *node;
+	FuncCall *fn;
+	Const	*dmy_arg = makeConst(INT4OID,
+								 -1,
+								 InvalidOid,
+								 sizeof(int32),
+								 Int32GetDatum(1),
+								 false,
+								 true); /* pass by value */
+	const char *aggname = get_func_name(aggref->aggfnoid);
+
+	/*
+	 * For aggregate functions except to count, add count() func with the same arg parameters.
+	 * This count result is used for determining if the aggregate value should be NULL or not.
+	 * Also, add sum() func for avg because we need to calculate an average value as sum/count.
+	 *
+	 * XXX: If there are same expressions explicitly in the target list, we can use this instead
+	 * of adding new duplicated one.
+	 */
+	if (strcmp(aggname, "count") != 0)
+	{
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+
+		/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+		node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+		((Aggref *)node)->args = aggref->args;
+
+		tle_count = makeTargetEntry((Expr *) node,
+									*next_resno,
+									pstrdup(makeObjectName("__ivm_count",resname, "_")),
+									false);
+		*aggs = lappend(*aggs, tle_count);
+		(*next_resno)++;
+	}
+	if (strcmp(aggname, "avg") == 0)
+	{
+		List *dmy_args = NIL;
+		ListCell *lc;
+		foreach(lc, aggref->aggargtypes)
+		{
+			Oid		typeid = lfirst_oid(lc);
+			Type	type = typeidType(typeid);
+
+			Const *con = makeConst(typeid,
+								   -1,
+								   typeTypeCollation(type),
+								   typeLen(type),
+								   (Datum) 0,
+								   true,
+								   typeByVal(type));
+			dmy_args = lappend(dmy_args, con);
+			ReleaseSysCache(type);
+		}
+		fn = makeFuncCall(list_make1(makeString("sum")), NIL, COERCE_EXPLICIT_CALL, -1);
+
+		/* Make a Func with dummy args, and then override this by the original agg's args. */
+		node = ParseFuncOrColumn(pstate, fn->funcname, dmy_args, NULL, fn, false, -1);
+		((Aggref *)node)->args = aggref->args;
+
+		tle_count = makeTargetEntry((Expr *) node,
+									*next_resno,
+									pstrdup(makeObjectName("__ivm_sum",resname, "_")),
+									false);
+		*aggs = lappend(*aggs, tle_count);
+		(*next_resno)++;
+	}
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -923,11 +1049,13 @@ CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock
 static void
 check_ivm_restriction(Node *node)
 {
-	check_ivm_restriction_walker(node, NULL);
+	check_ivm_restriction_context context = {false};
+
+	check_ivm_restriction_walker(node, &context);
 }
 
 static bool
-check_ivm_restriction_walker(Node *node, void *context)
+check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context)
 {
 	/* This can recurse, so check for excessive recursion */
 	check_stack_depth();
@@ -1008,6 +1136,8 @@ check_ivm_restriction_walker(Node *node, void *context)
 					}
 				}
 
+				context->has_agg |= qry->hasAggs;
+
 				/* restrictions for rtable */
 				foreach(lc, qry->rtable)
 				{
@@ -1041,7 +1171,7 @@ check_ivm_restriction_walker(Node *node, void *context)
 
 				}
 
-				query_tree_walker(qry, check_ivm_restriction_walker, NULL, QTW_IGNORE_RANGE_TABLE);
+				query_tree_walker(qry, check_ivm_restriction_walker, (void *) context, QTW_IGNORE_RANGE_TABLE);
 
 				break;
 			}
@@ -1052,8 +1182,12 @@ check_ivm_restriction_walker(Node *node, void *context)
 						ereport(ERROR,
 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								 errmsg("column name %s is not supported on incrementally maintainable materialized view", tle->resname)));
+				if (context->has_agg && !IsA(tle->expr, Aggref) && contain_aggs_of_level((Node *) tle->expr, 0))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("expression containing an aggregate in it is not supported on incrementally maintainable materialized view")));
 
-				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
 				break;
 			}
 		case T_JoinExpr:
@@ -1065,15 +1199,128 @@ check_ivm_restriction_walker(Node *node, void *context)
 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								 errmsg("OUTER JOIN is not supported on incrementally maintainable materialized view")));
 
-				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
+				break;
 			}
-			break;
-			expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+		case T_Aggref:
+			{
+				/* Check if this supports IVM */
+				Aggref *aggref = (Aggref *) node;
+				const char *aggname = format_procedure(aggref->aggfnoid);
+
+				if (aggref->aggfilter != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with FILTER clause is not supported on incrementally maintainable materialized view")));
+
+				if (aggref->aggdistinct != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with DISTINCT arguments is not supported on incrementally maintainable materialized view")));
+
+				if (aggref->aggorder != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with ORDER clause is not supported on incrementally maintainable materialized view")));
+
+				if (!check_aggregate_supports_ivm(aggref->aggfnoid))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function %s is not supported on incrementally maintainable materialized view", aggname)));
+				break;
+			}
+		default:
+			expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
 			break;
 	}
 	return false;
 }
 
+/*
+ * check_aggregate_supports_ivm
+ *
+ * Check if the given aggregate function is supporting IVM
+ */
+static bool
+check_aggregate_supports_ivm(Oid aggfnoid)
+{
+	switch (aggfnoid)
+	{
+		/* count */
+		case F_COUNT_ANY:
+		case F_COUNT_:
+
+		/* sum */
+		case F_SUM_INT8:
+		case F_SUM_INT4:
+		case F_SUM_INT2:
+		case F_SUM_FLOAT4:
+		case F_SUM_FLOAT8:
+		case F_SUM_MONEY:
+		case F_SUM_INTERVAL:
+		case F_SUM_NUMERIC:
+
+		/* avg */
+		case F_AVG_INT8:
+		case F_AVG_INT4:
+		case F_AVG_INT2:
+		case F_AVG_NUMERIC:
+		case F_AVG_FLOAT4:
+		case F_AVG_FLOAT8:
+		case F_AVG_INTERVAL:
+
+		/* min */
+		case F_MIN_ANYARRAY:
+		case F_MIN_INT8:
+		case F_MIN_INT4:
+		case F_MIN_INT2:
+		case F_MIN_OID:
+		case F_MIN_FLOAT4:
+		case F_MIN_FLOAT8:
+		case F_MIN_DATE:
+		case F_MIN_TIME:
+		case F_MIN_TIMETZ:
+		case F_MIN_MONEY:
+		case F_MIN_TIMESTAMP:
+		case F_MIN_TIMESTAMPTZ:
+		case F_MIN_INTERVAL:
+		case F_MIN_TEXT:
+		case F_MIN_NUMERIC:
+		case F_MIN_BPCHAR:
+		case F_MIN_TID:
+		case F_MIN_ANYENUM:
+		case F_MIN_INET:
+		case F_MIN_PG_LSN:
+
+		/* max */
+		case F_MAX_ANYARRAY:
+		case F_MAX_INT8:
+		case F_MAX_INT4:
+		case F_MAX_INT2:
+		case F_MAX_OID:
+		case F_MAX_FLOAT4:
+		case F_MAX_FLOAT8:
+		case F_MAX_DATE:
+		case F_MAX_TIME:
+		case F_MAX_TIMETZ:
+		case F_MAX_MONEY:
+		case F_MAX_TIMESTAMP:
+		case F_MAX_TIMESTAMPTZ:
+		case F_MAX_INTERVAL:
+		case F_MAX_TEXT:
+		case F_MAX_NUMERIC:
+		case F_MAX_BPCHAR:
+		case F_MAX_TID:
+		case F_MAX_ANYENUM:
+		case F_MAX_INET:
+		case F_MAX_PG_LSN:
+			return true;
+
+		default:
+			return false;
+	}
+}
+
 /*
  * CreateindexOnIMMV
  *
@@ -1123,7 +1370,32 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel)
 	index->concurrent = false;
 	index->if_not_exists = false;
 
-	if (qry->distinctClause)
+
+	if (qry->groupClause)
+	{
+		/* create unique constraint on GROUP BY expression columns */
+		foreach(lc, qry->groupClause)
+		{
+			SortGroupClause *scl = (SortGroupClause *) lfirst(lc);
+			TargetEntry *tle = get_sortgroupclause_tle(scl, qry->targetList);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+			IndexElem  *iparam;
+
+			iparam = makeNode(IndexElem);
+			iparam->name = pstrdup(NameStr(attr->attname));
+			iparam->expr = NULL;
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->opclassopts = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+		}
+
+		index->isconstraint = true;
+	}
+	else if (qry->distinctClause)
 	{
 		/* create unique constraint on all columns */
 		index->isconstraint = true;
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index f1f46e2e14..bce2e3ae3d 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -81,6 +81,32 @@ typedef struct
 
 #define MV_INIT_QUERYHASHSIZE	16
 
+/* MV query type codes */
+#define MV_PLAN_RECALC			1
+#define MV_PLAN_SET_VALUE		2
+
+/*
+ * MI_QueryKey
+ *
+ * The key identifying a prepared SPI plan in our query hashtable
+ */
+typedef struct MV_QueryKey
+{
+	Oid			matview_id;	/* OID of materialized view */
+	int32		query_type;	/* query type ID, see MV_PLAN_XXX above */
+} MV_QueryKey;
+
+/*
+ * MV_QueryHashEntry
+ *
+ * Hash entry for cached plans used to maintain materialized views.
+ */
+typedef struct MV_QueryHashEntry
+{
+	MV_QueryKey key;
+	SPIPlanPtr	plan;
+} MV_QueryHashEntry;
+
 /*
  * MV_TriggerHashEntry
  *
@@ -117,8 +143,16 @@ typedef struct MV_TriggerTable
 	RangeTblEntry *original_rte;	/* the original RTE saved before rewriting query */
 } MV_TriggerTable;
 
+static HTAB *mv_query_cache = NULL;
 static HTAB *mv_trigger_info = NULL;
 
+/* kind of IVM operation for the view */
+typedef enum
+{
+	IVM_ADD,
+	IVM_SUB
+} IvmOp;
+
 /* ENR name for materialized view delta */
 #define NEW_DELTA_ENRNAME "new_delta"
 #define OLD_DELTA_ENRNAME "old_delta"
@@ -152,7 +186,7 @@ static RangeTblEntry *get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *tabl
 				 QueryEnvironment *queryEnv);
 static RangeTblEntry *union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
 		   QueryEnvironment *queryEnv);
-static Query *rewrite_query_for_distinct(Query *query, ParseState *pstate);
+static Query *rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate);
 
 static void calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
 			DestReceiver *dest_old, DestReceiver *dest_new,
@@ -163,19 +197,48 @@ static Query *rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *
 static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
 			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
 			Query *query, bool use_count, char *count_colname);
+static void append_set_clause_for_count(const char *resname, StringInfo buf_old,
+							StringInfo buf_new,StringInfo aggs_list);
+static void append_set_clause_for_sum(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list);
+static void append_set_clause_for_avg(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list,
+						  const char *aggtype);
+static void append_set_clause_for_minmax(const char *resname, StringInfo buf_old,
+							 StringInfo buf_new, StringInfo aggs_list,
+							 bool is_min);
+static char *get_operation_string(IvmOp op, const char *col, const char *arg1, const char *arg2,
+					 const char* count_col, const char *castType);
+static char *get_null_condition_string(IvmOp op, const char *arg1, const char *arg2,
+						  const char* count_col);
 static void apply_old_delta(const char *matviewname, const char *deltaname_old,
 				List *keys);
 static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
-				List *keys, const char *count_colname);
+				List *keys, StringInfo aggs_list, StringInfo aggs_set,
+				List *minmax_list, List *is_min_list,
+				const char *count_colname,
+				SPITupleTable **tuptable_recalc, uint64 *num_recalc);
 static void apply_new_delta(const char *matviewname, const char *deltaname_new,
 				StringInfo target_list);
 static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
-				List *keys, StringInfo target_list, const char* count_colname);
+				List *keys, StringInfo target_list, StringInfo aggs_set,
+				const char* count_colname);
 static char *get_matching_condition_string(List *keys);
+static char *get_returning_string(List *minmax_list, List *is_min_list, List *keys);
+static char *get_minmax_recalc_condition_string(List *minmax_list, List *is_min_list);
+static char *get_select_for_recalc_string(List *keys);
+static void recalc_and_set_values(SPITupleTable *tuptable_recalc, int64 num_tuples,
+					  List *namelist, List *keys, Relation matviewRel);
+static SPIPlanPtr get_plan_for_recalc(Oid matviewOid, List *namelist, List *keys, Oid *keyTypes);
+static SPIPlanPtr get_plan_for_set_values(Oid matviewOid, char *matviewname, List *namelist,
+						Oid *valTypes);
 static void generate_equal(StringInfo querybuf, Oid opttype,
 			   const char *leftop, const char *rightop);
 
 static void mv_InitHashTables(void);
+static SPIPlanPtr mv_FetchPreparedPlan(MV_QueryKey *key);
+static void mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan);
+static void mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type);
 static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry);
 
 static List *get_securityQuals(Oid relId, int rt_index, Query *query);
@@ -1432,8 +1495,8 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
 	rewritten = rewrite_query_for_preupdate_state(rewritten, entry->tables,
 												  entry->xid, entry->cid,
 												  pstate);
-	/* Rewrite for DISTINCT clause */
-	rewritten = rewrite_query_for_distinct(rewritten, pstate);
+	/* Rewrite for DISTINCT clause and aggregates functions */
+	rewritten = rewrite_query_for_distinct_and_aggregates(rewritten, pstate);
 
 	/* Create tuplestores to store view deltas */
 	if (entry->has_old)
@@ -1484,7 +1547,7 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
 
 			count_colname = pstrdup("__ivm_count__");
 
-			if (query->distinctClause)
+			if (query->hasAggs || query->distinctClause)
 				use_count = true;
 
 			/* calculate delta tables */
@@ -1846,17 +1909,34 @@ union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
 }
 
 /*
- * rewrite_query_for_distinct
+ * rewrite_query_for_distinct_and_aggregates
  *
- * Rewrite query for counting DISTINCT clause.
+ * Rewrite query for counting DISTINCT clause and aggregate functions.
  */
 static Query *
-rewrite_query_for_distinct(Query *query, ParseState *pstate)
+rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate)
 {
 	TargetEntry *tle_count;
 	FuncCall *fn;
 	Node *node;
 
+	/* For aggregate views */
+	if (query->hasAggs)
+	{
+		ListCell *lc;
+		List *aggs = NIL;
+		AttrNumber next_resno = list_length(query->targetList) + 1;
+
+		foreach(lc, query->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+			if (IsA(tle->expr, Aggref))
+				makeIvmAggColumn(pstate, (Aggref *)tle->expr, tle->resname, &next_resno, &aggs);
+		}
+		query->targetList = list_concat(query->targetList, aggs);
+	}
+
 	/* Add count(*) for counting distinct tuples in views */
 	fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
 	fn->agg_star = true;
@@ -1925,6 +2005,8 @@ rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte
 	return query;
 }
 
+#define IVM_colname(type, col) makeObjectName("__ivm_" type, col, "_")
+
 /*
  * apply_delta
  *
@@ -1938,11 +2020,16 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 {
 	StringInfoData querybuf;
 	StringInfoData target_list_buf;
+	StringInfo	aggs_list_buf = NULL;
+	StringInfo	aggs_set_old = NULL;
+	StringInfo	aggs_set_new = NULL;
 	Relation	matviewRel;
 	char	   *matviewname;
 	ListCell	*lc;
 	int			i;
 	List	   *keys = NIL;
+	List	   *minmax_list = NIL;
+	List	   *is_min_list = NIL;
 
 
 	/*
@@ -1960,6 +2047,15 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 	initStringInfo(&querybuf);
 	initStringInfo(&target_list_buf);
 
+	if (query->hasAggs)
+	{
+		if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
+			aggs_set_old = makeStringInfo();
+		if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
+			aggs_set_new = makeStringInfo();
+		aggs_list_buf = makeStringInfo();
+	}
+
 	/* build string of target list */
 	for (i = 0; i < matviewRel->rd_att->natts; i++)
 	{
@@ -1983,7 +2079,65 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		if (tle->resjunk)
 			continue;
 
-		keys = lappend(keys, resname);
+		/*
+		 * For views without aggregates, all attributes are used as keys to identify a
+		 * tuple in a view.
+		 */
+		if (!query->hasAggs)
+			keys = lappend(keys, attr);
+
+		/* For views with aggregates, we need to build SET clause for updating aggregate
+		 * values. */
+		if (query->hasAggs && IsA(tle->expr, Aggref))
+		{
+			Aggref *aggref = (Aggref *) tle->expr;
+			const char *aggname = get_func_name(aggref->aggfnoid);
+
+			/*
+			 * We can use function names here because it is already checked if these
+			 * can be used in IMMV by its OID at the definition time.
+			 */
+
+			/* count */
+			if (!strcmp(aggname, "count"))
+				append_set_clause_for_count(resname, aggs_set_old, aggs_set_new, aggs_list_buf);
+
+			/* sum */
+			else if (!strcmp(aggname, "sum"))
+				append_set_clause_for_sum(resname, aggs_set_old, aggs_set_new, aggs_list_buf);
+
+			/* avg */
+			else if (!strcmp(aggname, "avg"))
+				append_set_clause_for_avg(resname, aggs_set_old, aggs_set_new, aggs_list_buf,
+										  format_type_be(aggref->aggtype));
+
+			/* min/max */
+			else if (!strcmp(aggname, "min") || !strcmp(aggname, "max"))
+			{
+				bool	is_min = (!strcmp(aggname, "min"));
+
+				append_set_clause_for_minmax(resname, aggs_set_old, aggs_set_new, aggs_list_buf, is_min);
+
+				/* make a resname list of min and max aggregates */
+				minmax_list = lappend(minmax_list, resname);
+				is_min_list = lappend_int(is_min_list, is_min);
+			}
+			else
+				elog(ERROR, "unsupported aggregate function: %s", aggname);
+		}
+	}
+
+	/* If we have GROUP BY clause, we use its entries as keys. */
+	if (query->hasAggs && query->groupClause)
+	{
+		foreach (lc, query->groupClause)
+		{
+			SortGroupClause *sgcl = (SortGroupClause *) lfirst(lc);
+			TargetEntry		*tle = get_sortgroupclause_tle(sgcl, query->targetList);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+
+			keys = lappend(keys, attr);
+		}
 	}
 
 	/* Start maintaining the materialized view. */
@@ -1997,6 +2151,8 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 	if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
 	{
 		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		SPITupleTable  *tuptable_recalc = NULL;
+		uint64			num_recalc;
 		int				rc;
 
 		/* convert tuplestores to ENR, and register for SPI */
@@ -2014,10 +2170,19 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		if (use_count)
 			/* apply old delta and get rows to be recalculated */
 			apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME,
-									   keys, count_colname);
+									   keys, aggs_list_buf, aggs_set_old,
+									   minmax_list, is_min_list,
+									   count_colname, &tuptable_recalc, &num_recalc);
 		else
 			apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys);
 
+		/*
+		 * If we have min or max, we might have to recalculate aggregate values from base tables
+		 * on some tuples. TIDs and keys such tuples are returned as a result of the above query.
+		 */
+		if (minmax_list && tuptable_recalc)
+			recalc_and_set_values(tuptable_recalc, num_recalc, minmax_list, keys, matviewRel);
+
 	}
 	/* For tuple insertion */
 	if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
@@ -2040,7 +2205,7 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		/* apply new delta */
 		if (use_count)
 			apply_new_delta_with_count(matviewname, NEW_DELTA_ENRNAME,
-								keys, &target_list_buf, count_colname);
+								keys, aggs_set_new, &target_list_buf, count_colname);
 		else
 			apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf);
 	}
@@ -2055,49 +2220,410 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		elog(ERROR, "SPI_finish failed");
 }
 
+/*
+ * append_set_clause_for_count
+ *
+ * Append SET clause string for count aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_count(const char *resname, StringInfo buf_old,
+							StringInfo buf_new,StringInfo aggs_list)
+{
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* resname = mv.resname - t.resname */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, resname, "mv", "t", NULL, NULL));
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* resname = mv.resname + diff.resname */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, resname, "mv", "diff", NULL, NULL));
+	}
+
+	appendStringInfo(aggs_list, ", %s",
+		quote_qualified_identifier("diff", resname)
+	);
+}
+
+/*
+ * append_set_clause_for_sum
+ *
+ * Append SET clause string for sum aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_sum(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list)
+{
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* sum = mv.sum - t.sum */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, resname, "mv", "t", count_col, NULL)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* sum = mv.sum + diff.sum */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, resname, "mv", "diff", count_col, NULL)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * append_set_clause_for_avg
+ *
+ * Append SET clause string for avg aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_avg(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list,
+						  const char *aggtype)
+{
+	char *sum_col = IVM_colname("sum", resname);
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* avg = (mv.sum - t.sum)::aggtype / (mv.count - t.count) */
+		appendStringInfo(buf_old,
+			", %s = %s OPERATOR(pg_catalog./) %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, aggtype),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+		/* sum = mv.sum - t.sum */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, sum_col),
+			get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, NULL)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* avg = (mv.sum + diff.sum)::aggtype / (mv.count + diff.count) */
+		appendStringInfo(buf_new,
+			", %s = %s OPERATOR(pg_catalog./) %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, aggtype),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+		/* sum = mv.sum + diff.sum */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, sum_col),
+			get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, NULL)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("sum", resname)),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * append_set_clause_for_minmax
+ *
+ * Append SET clause string for min or max aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ * is_min is true if this is min, false if not.
+ */
+static void
+append_set_clause_for_minmax(const char *resname, StringInfo buf_old,
+							 StringInfo buf_new, StringInfo aggs_list,
+							 bool is_min)
+{
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/*
+		 * If the new value doesn't became NULL then use the value remaining
+		 * in the view although this will be recomputated afterwords.
+		 */
+		appendStringInfo(buf_old,
+			", %s = CASE WHEN %s THEN NULL ELSE %s END",
+			quote_qualified_identifier(NULL, resname),
+			get_null_condition_string(IVM_SUB, "mv", "t", count_col),
+			quote_qualified_identifier("mv", resname)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/*
+		 * min = LEAST(mv.min, diff.min)
+		 * max = GREATEST(mv.max, diff.max)
+		 */
+		appendStringInfo(buf_new,
+			", %s = CASE WHEN %s THEN NULL ELSE %s(%s,%s) END",
+			quote_qualified_identifier(NULL, resname),
+			get_null_condition_string(IVM_ADD, "mv", "diff", count_col),
+
+			is_min ? "LEAST" : "GREATEST",
+			quote_qualified_identifier("mv", resname),
+			quote_qualified_identifier("diff", resname)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * get_operation_string
+ *
+ * Build a string to calculate the new aggregate values.
+ */
+static char *
+get_operation_string(IvmOp op, const char *col, const char *arg1, const char *arg2,
+					 const char* count_col, const char *castType)
+{
+	StringInfoData buf;
+	StringInfoData castString;
+	char   *col1 = quote_qualified_identifier(arg1, col);
+	char   *col2 = quote_qualified_identifier(arg2, col);
+	char	op_char = (op == IVM_SUB ? '-' : '+');
+
+	initStringInfo(&buf);
+	initStringInfo(&castString);
+
+	if (castType)
+		appendStringInfo(&castString, "::%s", castType);
+
+	if (!count_col)
+	{
+		/*
+		 * If the attributes don't have count columns then calc the result
+		 * by using the operator simply.
+		 */
+		appendStringInfo(&buf, "(%s OPERATOR(pg_catalog.%c) %s)%s",
+			col1, op_char, col2, castString.data);
+	}
+	else
+	{
+		/*
+		 * If the attributes have count columns then consider the condition
+		 * where the result becomes NULL.
+		 */
+		char *null_cond = get_null_condition_string(op, arg1, arg2, count_col);
+
+		appendStringInfo(&buf,
+			"(CASE WHEN %s THEN NULL "
+				"WHEN %s IS NULL THEN %s "
+				"WHEN %s IS NULL THEN %s "
+				"ELSE (%s OPERATOR(pg_catalog.%c) %s)%s END)",
+			null_cond,
+			col1, col2,
+			col2, col1,
+			col1, op_char, col2, castString.data
+		);
+	}
+
+	return buf.data;
+}
+
+/*
+ * get_null_condition_string
+ *
+ * Build a predicate string for CASE clause to check if an aggregate value
+ * will became NULL after the given operation is applied.
+ */
+static char *
+get_null_condition_string(IvmOp op, const char *arg1, const char *arg2,
+						  const char* count_col)
+{
+	StringInfoData null_cond;
+	initStringInfo(&null_cond);
+
+	switch (op)
+	{
+		case IVM_ADD:
+			appendStringInfo(&null_cond,
+				"%s OPERATOR(pg_catalog.=) 0 AND %s OPERATOR(pg_catalog.=) 0",
+				quote_qualified_identifier(arg1, count_col),
+				quote_qualified_identifier(arg2, count_col)
+			);
+			break;
+		case IVM_SUB:
+			appendStringInfo(&null_cond,
+				"%s OPERATOR(pg_catalog.=) %s",
+				quote_qualified_identifier(arg1, count_col),
+				quote_qualified_identifier(arg2, count_col)
+			);
+			break;
+		default:
+			elog(ERROR,"unknown operation");
+	}
+
+	return null_cond.data;
+}
+
+
 /*
  * apply_old_delta_with_count
  *
  * Execute a query for applying a delta table given by deltname_old
  * which contains tuples to be deleted from to a materialized view given by
  * matviewname.  This is used when counting is required, that is, the view
- * has aggregate or distinct.
+ * has aggregate or distinct. Also, when a table in EXISTS sub queries
+ * is modified.
+ *
+ * If the view desn't have aggregates or has GROUP BY, this requires a keys
+ * list to identify a tuple in the view. If the view has aggregates, this
+ * requires strings representing resnames of aggregates and SET clause for
+ * updating aggregate values.
+ *
+ * If the view has min or max aggregate, this requires a list of resnames of
+ * min/max aggregates and a list of boolean which represents which entries in
+ * minmax_list is min. These are necessary to check if we need to recalculate
+ * min or max aggregate values. In this case, this query returns TID and keys
+ * of tuples which need to be recalculated.  This result and the number of rows
+ * are stored in tuptables and num_recalc repectedly.
+ *
  */
 static void
 apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
-				List *keys, const char *count_colname)
+				List *keys, StringInfo aggs_list, StringInfo aggs_set,
+				List *minmax_list, List *is_min_list,
+				const char *count_colname,
+				SPITupleTable **tuptable_recalc, uint64 *num_recalc)
 {
 	StringInfoData	querybuf;
 	char   *match_cond;
+	char   *updt_returning = "";
+	char   *select_for_recalc = "SELECT";
+	bool	agg_without_groupby = (list_length(keys) == 0);
+
+	Assert(tuptable_recalc != NULL);
+	Assert(num_recalc != NULL);
 
 	/* build WHERE condition for searching tuples to be deleted */
 	match_cond = get_matching_condition_string(keys);
 
+	/*
+	 * We need a special RETURNING clause and SELECT statement for min/max to
+	 * check which tuple needs re-calculation from base tables.
+	 */
+	if (minmax_list)
+	{
+		updt_returning = get_returning_string(minmax_list, is_min_list, keys);
+		select_for_recalc = get_select_for_recalc_string(keys);
+	}
+
 	/* Search for matching tuples from the view and update or delete if found. */
 	initStringInfo(&querybuf);
 	appendStringInfo(&querybuf,
 					"WITH t AS ("			/* collecting tid of target tuples in the view */
 						"SELECT diff.%s, "			/* count column */
-								"(diff.%s OPERATOR(pg_catalog.=) mv.%s) AS for_dlt, "
+								"(diff.%s OPERATOR(pg_catalog.=) mv.%s AND %s) AS for_dlt, "
 								"mv.ctid "
+								"%s "				/* aggregate columns */
 						"FROM %s AS mv, %s AS diff "
 						"WHERE %s"					/* tuple matching condition */
 					"), updt AS ("			/* update a tuple if this is not to be deleted */
 						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.-) t.%s "
+											"%s"	/* SET clauses for aggregates */
 						"FROM t WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND NOT for_dlt "
+						"%s"						/* RETURNING clause for recalc infomation */
 					"), dlt AS ("			/* delete a tuple if this is to be deleted */
 						"DELETE FROM %s AS mv USING t "
 						"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt"
-					")",
+					") %s",							/* SELECT returning which tuples need to be recalculated */
 					count_colname,
-					count_colname, count_colname,
+					count_colname, count_colname, (agg_without_groupby ? "false" : "true"),
+					(aggs_list != NULL ? aggs_list->data : ""),
 					matviewname, deltaname_old,
 					match_cond,
 					matviewname, count_colname, count_colname, count_colname,
-					matviewname);
+					(aggs_set != NULL ? aggs_set->data : ""),
+					updt_returning,
+					matviewname,
+					select_for_recalc);
 
-	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_SELECT)
 		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+
+
+	/* Return tuples to be recalculated. */
+	if (minmax_list)
+	{
+		*tuptable_recalc = SPI_tuptable;
+		*num_recalc = SPI_processed;
+	}
+	else
+	{
+		*tuptable_recalc = NULL;
+		*num_recalc = 0;
+	}
 }
 
 /*
@@ -2157,10 +2683,15 @@ apply_old_delta(const char *matviewname, const char *deltaname_old,
  * matviewname.  This is used when counting is required, that is, the view
  * has aggregate or distinct. Also, when a table in EXISTS sub queries
  * is modified.
+ *
+ * If the view desn't have aggregates or has GROUP BY, this requires a keys
+ * list to identify a tuple in the view. If the view has aggregates, this
+ * requires strings representing SET clause for updating aggregate values.
  */
 static void
 apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
-				List *keys, StringInfo target_list, const char* count_colname)
+				List *keys, StringInfo aggs_set, StringInfo target_list,
+				const char* count_colname)
 {
 	StringInfoData	querybuf;
 	StringInfoData	returning_keys;
@@ -2191,6 +2722,7 @@ apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
 	appendStringInfo(&querybuf,
 					"WITH updt AS ("		/* update a tuple if this exists in the view */
 						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.+) diff.%s "
+											"%s "	/* SET clauses for aggregates */
 						"FROM %s AS diff "
 						"WHERE %s "					/* tuple matching condition */
 						"RETURNING %s"				/* returning keys of updated tuples */
@@ -2198,6 +2730,7 @@ apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
 						"SELECT %s FROM %s AS diff "
 						"WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE %s);",
 					matviewname, count_colname, count_colname, count_colname,
+					(aggs_set != NULL ? aggs_set->data : ""),
 					deltaname_new,
 					match_cond,
 					returning_keys.data,
@@ -2272,6 +2805,349 @@ get_matching_condition_string(List *keys)
 	return match_cond.data;
 }
 
+/*
+ * get_returning_string
+ *
+ * Build a string for RETURNING clause of UPDATE used in apply_old_delta_with_count.
+ * This clause returns ctid and a boolean value that indicates if we need to
+ * recalculate min or max value, for each updated row.
+ */
+static char *
+get_returning_string(List *minmax_list, List *is_min_list, List *keys)
+{
+	StringInfoData returning;
+	char		*recalc_cond;
+	ListCell	*lc;
+
+	Assert(minmax_list != NIL && is_min_list != NIL);
+	recalc_cond = get_minmax_recalc_condition_string(minmax_list, is_min_list);
+
+	initStringInfo(&returning);
+
+	appendStringInfo(&returning, "RETURNING mv.ctid AS tid, (%s) AS recalc", recalc_cond);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char *resname = NameStr(attr->attname);
+		appendStringInfo(&returning, ", %s", quote_qualified_identifier("mv", resname));
+	}
+
+	return returning.data;
+}
+
+/*
+ * get_minmax_recalc_condition_string
+ *
+ * Build a predicate string for checking if any min/max aggregate
+ * value needs to be recalculated.
+ */
+static char *
+get_minmax_recalc_condition_string(List *minmax_list, List *is_min_list)
+{
+	StringInfoData recalc_cond;
+	ListCell	*lc1, *lc2;
+
+	initStringInfo(&recalc_cond);
+
+	Assert (list_length(minmax_list) == list_length(is_min_list));
+
+	forboth (lc1, minmax_list, lc2, is_min_list)
+	{
+		char   *resname = (char *) lfirst(lc1);
+		bool	is_min = (bool) lfirst_int(lc2);
+		char   *op_str = (is_min ? ">=" : "<=");
+
+		appendStringInfo(&recalc_cond, "%s OPERATOR(pg_catalog.%s) %s",
+			quote_qualified_identifier("mv", resname),
+			op_str,
+			quote_qualified_identifier("t", resname)
+		);
+
+		if (lnext(minmax_list, lc1))
+			appendStringInfo(&recalc_cond, " OR ");
+	}
+
+	return recalc_cond.data;
+}
+
+/*
+ * get_select_for_recalc_string
+ *
+ * Build a query to return tid and keys of tuples which need
+ * recalculation. This is used as the result of the query
+ * built by apply_old_delta.
+ */
+static char *
+get_select_for_recalc_string(List *keys)
+{
+	StringInfoData qry;
+	ListCell	*lc;
+
+	initStringInfo(&qry);
+
+	appendStringInfo(&qry, "SELECT tid");
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		appendStringInfo(&qry, ", %s", NameStr(attr->attname));
+	}
+
+	appendStringInfo(&qry, " FROM updt WHERE recalc");
+
+	return qry.data;
+}
+
+/*
+ * recalc_and_set_values
+ *
+ * Recalculate tuples in a materialized from base tables and update these.
+ * The tuples which needs recalculation are specified by keys, and resnames
+ * of columns to be updated are specified by namelist. TIDs and key values
+ * are given by tuples in tuptable_recalc. Its first attribute must be TID
+ * and key values must be following this.
+ */
+static void
+recalc_and_set_values(SPITupleTable *tuptable_recalc, int64 num_tuples,
+					  List *namelist, List *keys, Relation matviewRel)
+{
+	TupleDesc   tupdesc_recalc = tuptable_recalc->tupdesc;
+	Oid		   *keyTypes = NULL, *types = NULL;
+	char	   *keyNulls = NULL, *nulls = NULL;
+	Datum	   *keyVals = NULL, *vals = NULL;
+	int			num_vals = list_length(namelist);
+	int			num_keys = list_length(keys);
+	uint64      i;
+	Oid			matviewOid;
+	char	   *matviewname;
+
+	matviewOid = RelationGetRelid(matviewRel);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	/* If we have keys, initialize arrays for them. */
+	if (keys)
+	{
+		keyTypes = palloc(sizeof(Oid) * num_keys);
+		keyNulls = palloc(sizeof(char) * num_keys);
+		keyVals = palloc(sizeof(Datum) * num_keys);
+		/* a tuple contains keys to be recalculated and ctid to be updated*/
+		Assert(tupdesc_recalc->natts == num_keys + 1);
+
+		/* Types of key attributes  */
+		for (i = 0; i < num_keys; i++)
+			keyTypes[i] = TupleDescAttr(tupdesc_recalc, i + 1)->atttypid;
+	}
+
+	/* allocate memory for all attribute names and tid */
+	types = palloc(sizeof(Oid) * (num_vals + 1));
+	nulls = palloc(sizeof(char) * (num_vals + 1));
+	vals = palloc(sizeof(Datum) * (num_vals + 1));
+
+	/* For each tuple which needs recalculation */
+	for (i = 0; i < num_tuples; i++)
+	{
+		int j;
+		bool isnull;
+		SPIPlanPtr plan;
+		SPITupleTable *tuptable_newvals;
+		TupleDesc   tupdesc_newvals;
+
+		/* Set group key values as parameters if needed. */
+		if (keys)
+		{
+			for (j = 0; j < num_keys; j++)
+			{
+				keyVals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, j + 2, &isnull);
+				if (isnull)
+					keyNulls[j] = 'n';
+				else
+					keyNulls[j] = ' ';
+			}
+		}
+
+		/*
+		 * Get recalculated values from base tables. The result must be
+		 * only one tuple thich contains the new values for specified keys.
+		 */
+		plan = get_plan_for_recalc(matviewOid, namelist, keys, keyTypes);
+		if (SPI_execute_plan(plan, keyVals, keyNulls, false, 0) != SPI_OK_SELECT)
+			elog(ERROR, "SPI_execute_plan");
+		if (SPI_processed != 1)
+			elog(ERROR, "SPI_execute_plan returned zero or more than one rows");
+
+		tuptable_newvals = SPI_tuptable;
+		tupdesc_newvals = tuptable_newvals->tupdesc;
+
+		Assert(tupdesc_newvals->natts == num_vals);
+
+		/* Set the new values as parameters */
+		for (j = 0; j < tupdesc_newvals->natts; j++)
+		{
+			if (i == 0)
+				types[j] = TupleDescAttr(tupdesc_newvals, j)->atttypid;
+
+			vals[j] = SPI_getbinval(tuptable_newvals->vals[0], tupdesc_newvals, j + 1, &isnull);
+			if (isnull)
+				nulls[j] = 'n';
+			else
+				nulls[j] = ' ';
+		}
+		/* Set TID of the view tuple to be updated as a parameter */
+		types[j] = TIDOID;
+		vals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, 1, &isnull);
+		nulls[j] = ' ';
+
+		/* Update the view tuple to the new values */
+		plan = get_plan_for_set_values(matviewOid, matviewname, namelist, types);
+		if (SPI_execute_plan(plan, vals, nulls, false, 0) != SPI_OK_UPDATE)
+			elog(ERROR, "SPI_execute_plan");
+	}
+}
+
+
+/*
+ * get_plan_for_recalc
+ *
+ * Create or fetch a plan for recalculating value in the view's target list
+ * from base tables using the definition query of materialized view specified
+ * by matviewOid. namelist is a list of resnames of values to be recalculated.
+ *
+ * keys is a list of keys to identify tuples to be recalculated if this is not
+ * empty. KeyTypes is an array of types of keys.
+ */
+static SPIPlanPtr
+get_plan_for_recalc(Oid matviewOid, List *namelist, List *keys, Oid *keyTypes)
+{
+	MV_QueryKey hash_key;
+	SPIPlanPtr	plan;
+
+	/* Fetch or prepare a saved plan for the recalculation */
+	mv_BuildQueryKey(&hash_key, matviewOid, MV_PLAN_RECALC);
+	if ((plan = mv_FetchPreparedPlan(&hash_key)) == NULL)
+	{
+		ListCell	   *lc;
+		StringInfoData	str;
+		char   *viewdef;
+
+		/* get view definition of matview */
+		viewdef = text_to_cstring((text *) DatumGetPointer(
+					DirectFunctionCall1(pg_get_viewdef, ObjectIdGetDatum(matviewOid))));
+		/* get rid of trailing semi-colon */
+		viewdef[strlen(viewdef)-1] = '\0';
+
+		/*
+		 * Build a query string for recalculating values. This is like
+		 *
+		 *  SELECT x1, x2, x3, ... FROM ( ... view definition query ...) mv
+		 *   WHERE (key1, key2, ...) = ($1, $2, ...);
+		 */
+
+		initStringInfo(&str);
+		appendStringInfo(&str, "SELECT ");
+		foreach (lc, namelist)
+		{
+			appendStringInfo(&str, "%s", (char *) lfirst(lc));
+			if (lnext(namelist, lc))
+				appendStringInfoString(&str, ", ");
+		}
+		appendStringInfo(&str, " FROM (%s) mv", viewdef);
+
+		if (keys)
+		{
+			int		i = 1;
+			char	paramname[16];
+
+			appendStringInfo(&str, " WHERE (");
+			foreach (lc, keys)
+			{
+				Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+				char   *resname = NameStr(attr->attname);
+				Oid		typid = attr->atttypid;
+
+				sprintf(paramname, "$%d", i);
+				appendStringInfo(&str, "(");
+				generate_equal(&str, typid, resname, paramname);
+				appendStringInfo(&str, " OR (%s IS NULL AND %s IS NULL))",
+								 resname, paramname);
+
+				if (lnext(keys, lc))
+					appendStringInfoString(&str, " AND ");
+				i++;
+			}
+			appendStringInfo(&str, ")");
+		}
+		else
+			keyTypes = NULL;
+
+		plan = SPI_prepare(str.data, list_length(keys), keyTypes);
+		if (plan == NULL)
+			elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), str.data);
+
+		SPI_keepplan(plan);
+		mv_HashPreparedPlan(&hash_key, plan);
+	}
+
+	return plan;
+}
+
+/*
+ * get_plan_for_set_values
+ *
+ * Create or fetch a plan for applying new values calculated by
+ * get_plan_for_recalc to a materialized view specified by matviewOid.
+ * matviewname is the name of the view.  namelist is a list of resnames
+ * of attributes to be updated, and valTypes is an array of types of the
+ * values.
+ */
+static SPIPlanPtr
+get_plan_for_set_values(Oid matviewOid, char *matviewname, List *namelist,
+						Oid *valTypes)
+{
+	MV_QueryKey	key;
+	SPIPlanPtr	plan;
+
+	/* Fetch or prepare a saved plan for the real check */
+	mv_BuildQueryKey(&key, matviewOid, MV_PLAN_SET_VALUE);
+	if ((plan = mv_FetchPreparedPlan(&key)) == NULL)
+	{
+		ListCell	  *lc;
+		StringInfoData str;
+		int		i;
+
+		/*
+		 * Build a query string for applying min/max values. This is like
+		 *
+		 *  UPDATE matviewname AS mv
+		 *   SET (x1, x2, x3, x4) = ($1, $2, $3, $4)
+		 *   WHERE ctid = $5;
+		 */
+
+		initStringInfo(&str);
+		appendStringInfo(&str, "UPDATE %s AS mv SET (", matviewname);
+		foreach (lc, namelist)
+		{
+			appendStringInfo(&str, "%s", (char *) lfirst(lc));
+			if (lnext(namelist, lc))
+				appendStringInfoString(&str, ", ");
+		}
+		appendStringInfo(&str, ") = ROW(");
+
+		for (i = 1; i <= list_length(namelist); i++)
+			appendStringInfo(&str, "%s$%d", (i==1 ? "" : ", "), i);
+
+		appendStringInfo(&str, ") WHERE ctid OPERATOR(pg_catalog.=) $%d", i);
+
+		plan = SPI_prepare(str.data, list_length(namelist) + 1, valTypes);
+		if (plan == NULL)
+			elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), str.data);
+
+		SPI_keepplan(plan);
+		mv_HashPreparedPlan(&key, plan);
+	}
+
+	return plan;
+}
+
 /*
  * generate_equals
  *
@@ -2305,6 +3181,13 @@ mv_InitHashTables(void)
 {
 	HASHCTL		ctl;
 
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(MV_QueryKey);
+	ctl.entrysize = sizeof(MV_QueryHashEntry);
+	mv_query_cache = hash_create("MV query cache",
+								 MV_INIT_QUERYHASHSIZE,
+								 &ctl, HASH_ELEM | HASH_BLOBS);
+
 	memset(&ctl, 0, sizeof(ctl));
 	ctl.keysize = sizeof(Oid);
 	ctl.entrysize = sizeof(MV_TriggerHashEntry);
@@ -2313,6 +3196,99 @@ mv_InitHashTables(void)
 								 &ctl, HASH_ELEM | HASH_BLOBS);
 }
 
+/*
+ * mv_FetchPreparedPlan
+ */
+static SPIPlanPtr
+mv_FetchPreparedPlan(MV_QueryKey *key)
+{
+	MV_QueryHashEntry *entry;
+	SPIPlanPtr	plan;
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_query_cache)
+		mv_InitHashTables();
+
+	/*
+	 * Lookup for the key
+	 */
+	entry = (MV_QueryHashEntry *) hash_search(mv_query_cache,
+											  (void *) key,
+											  HASH_FIND, NULL);
+	if (entry == NULL)
+		return NULL;
+
+	/*
+	 * Check whether the plan is still valid.  If it isn't, we don't want to
+	 * simply rely on plancache.c to regenerate it; rather we should start
+	 * from scratch and rebuild the query text too.  This is to cover cases
+	 * such as table/column renames.  We depend on the plancache machinery to
+	 * detect possible invalidations, though.
+	 *
+	 * CAUTION: this check is only trustworthy if the caller has already
+	 * locked both materialized views and base tables.
+	 */
+	plan = entry->plan;
+	if (plan && SPI_plan_is_valid(plan))
+		return plan;
+
+	/*
+	 * Otherwise we might as well flush the cached plan now, to free a little
+	 * memory space before we make a new one.
+	 */
+	entry->plan = NULL;
+	if (plan)
+		SPI_freeplan(plan);
+
+	return NULL;
+}
+
+/*
+ * mv_HashPreparedPlan
+ *
+ * Add another plan to our private SPI query plan hashtable.
+ */
+static void
+mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan)
+{
+	MV_QueryHashEntry *entry;
+	bool		found;
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_query_cache)
+		mv_InitHashTables();
+
+	/*
+	 * Add the new plan.  We might be overwriting an entry previously found
+	 * invalid by mv_FetchPreparedPlan.
+	 */
+	entry = (MV_QueryHashEntry *) hash_search(mv_query_cache,
+											  (void *) key,
+											  HASH_ENTER, &found);
+	Assert(!found || entry->plan == NULL);
+	entry->plan = plan;
+}
+
+/*
+ * mv_BuildQueryKey
+ *
+ * Construct a hashtable key for a prepared SPI plan for IVM.
+ */
+static void
+mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type)
+{
+	/*
+	 * We assume struct MV_QueryKey contains no padding bytes, else we'd need
+	 * to use memset to clear them.
+	 */
+	key->matview_id = matview_id;
+	key->query_type = query_type;
+}
+
 /*
  * AtAbort_IVM
  *
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index a57ce463e1..702b097079 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -29,6 +29,7 @@ extern ObjectAddress ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *st
 extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create);
 
 extern Query *rewriteQueryForIMMV(Query *query, List *colNames);
+extern void makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber *next_resno, List **aggs);
 
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
 
-- 
2.17.1

v23-0009-Add-regression-tests-for-Incremental-View-Mainte.patchtext/x-diff; name=v23-0009-Add-regression-tests-for-Incremental-View-Mainte.patchDownload
From 59ef91e832577adc23d679f397b5921d2163357b Mon Sep 17 00:00:00 2001
From: Takuma Hoshiai <takuma.hoshiai@gmail.com>
Date: Wed, 10 Mar 2021 11:11:13 +0900
Subject: [PATCH v23 09/15] Add regression tests for Incremental View
 Maintenance

---
 .../regress/expected/incremental_matview.out  | 799 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/incremental_matview.sql  | 400 +++++++++
 3 files changed, 1200 insertions(+), 1 deletion(-)
 create mode 100644 src/test/regress/expected/incremental_matview.out
 create mode 100644 src/test/regress/sql/incremental_matview.sql

diff --git a/src/test/regress/expected/incremental_matview.out b/src/test/regress/expected/incremental_matview.out
new file mode 100644
index 0000000000..fe5bc734e2
--- /dev/null
+++ b/src/test/regress/expected/incremental_matview.out
@@ -0,0 +1,799 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) WITH NO DATA;
+NOTICE:  could not create an index on materialized view "mv_ivm_1" automatically
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ERROR:  materialized view "mv_ivm_1" has not been populated
+HINT:  Use the REFRESH MATERIALIZED VIEW command.
+REFRESH MATERIALIZED VIEW mv_ivm_1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- immediate maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- rename of IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_rename AS SELECT DISTINCT * FROM mv_base_a;
+NOTICE:  created index "mv_ivm_rename_index" on materialized view "mv_ivm_rename"
+ALTER MATERIALIZED VIEW mv_ivm_rename RENAME COLUMN __ivm_count__ TO xxx;
+ERROR:  IVM column can not be renamed
+DROP MATERIALIZED VIEW mv_ivm_rename;
+-- unique index on IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_unique AS SELECT DISTINCT * FROM mv_base_a;
+NOTICE:  created index "mv_ivm_unique_index" on materialized view "mv_ivm_unique"
+CREATE UNIQUE INDEX ON mv_ivm_unique(__ivm_count__);
+ERROR:  unique index creation on IVM columns is not supported
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__));
+ERROR:  unique index creation on IVM columns is not supported
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__ + 1));
+ERROR:  unique index creation on IVM columns is not supported
+DROP MATERIALIZED VIEW mv_ivm_unique;
+-- some query syntax
+BEGIN;
+CREATE FUNCTION ivm_func() RETURNS int LANGUAGE 'sql'
+       AS 'SELECT 1' IMMUTABLE;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_func AS SELECT * FROM ivm_func();
+NOTICE:  could not create an index on materialized view "mv_ivm_func" automatically
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_no_tbl AS SELECT 1;
+NOTICE:  could not create an index on materialized view "mv_ivm_no_tbl" automatically
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+ROLLBACK;
+-- result of materialized view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_duplicate" automatically
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+NOTICE:  created index "mv_ivm_distinct_index" on materialized view "mv_ivm_distinct"
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 20
+ 30
+ 40
+ 50
+(6 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+ROLLBACK;
+-- support SUM(), COUNT() and AVG() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i), AVG(j) FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_agg_index" on materialized view "mv_ivm_agg"
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 | 120 |     2 | 60.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg          
+---+-----+-------+----------------------
+ 1 |  10 |     1 |  10.0000000000000000
+ 2 | 220 |     2 | 110.0000000000000000
+ 3 |  30 |     1 |  30.0000000000000000
+ 4 |  40 |     1 |  40.0000000000000000
+ 5 |  50 |     1 |  50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support COUNT(*) aggregate function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(*) FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_agg_index" on materialized view "mv_ivm_agg"
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 |  20 |     1
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 | 120 |     2
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+ROLLBACK;
+-- support aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j), COUNT(j), AVG(j) FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_group" automatically
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count |         avg         
+-----+-------+---------------------
+ 150 |     5 | 30.0000000000000000
+(1 row)
+
+INSERT INTO mv_base_a VALUES(6,60);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count |         avg         
+-----+-------+---------------------
+ 210 |     6 | 35.0000000000000000
+(1 row)
+
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count | avg 
+-----+-------+-----
+     |     0 |    
+(1 row)
+
+ROLLBACK;
+-- resolved issue: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+NOTICE:  created index "mv_ivm_avg_bug_index" on materialized view "mv_ivm_avg_bug"
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     3 |  3.3333333333333333
+ 2 |  80 |     3 | 26.6666666666666667
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support MIN(), MAX() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT i, MIN(j), MAX(j)  FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_min_max_index" on materialized view "mv_ivm_min_max"
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  10 |  10
+ 2 |  20 |  20
+ 3 |  30 |  30
+ 4 |  40 |  40
+ 5 |  50 |  50
+(5 rows)
+
+INSERT INTO mv_base_a VALUES
+  (1,11), (1,12),
+  (2,21), (2,22),
+  (3,31), (3,32),
+  (4,41), (4,42),
+  (5,51), (5,52);
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  10 |  12
+ 2 |  20 |  22
+ 3 |  30 |  32
+ 4 |  40 |  42
+ 5 |  50 |  52
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  11 |  12
+ 2 |  20 |  22
+ 3 |  30 |  31
+ 4 |  40 |  42
+ 5 |  50 |  52
+(5 rows)
+
+ROLLBACK;
+-- support MIN(), MAX() aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT MIN(j), MAX(j)  FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_min_max" automatically
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+  10 |  50
+(1 row)
+
+INSERT INTO mv_base_a VALUES
+  (0,0), (6,60), (7,70);
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+   0 |  70
+(1 row)
+
+DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+  10 |  60
+(1 row)
+
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+     |    
+(1 row)
+
+ROLLBACK;
+-- support self join view and multiple change on the same table
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1, 10), (2, 20), (3, 30);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_self(v1, v2) AS
+ SELECT t1.v, t2.v FROM base_t AS t1 JOIN base_t AS t2 ON t1.i = t2.i;
+NOTICE:  could not create an index on materialized view "mv_self" automatically
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_self ORDER BY v1;
+ v1 | v2 
+----+----
+ 10 | 10
+ 20 | 20
+ 30 | 30
+(3 rows)
+
+INSERT INTO base_t VALUES (4,40);
+DELETE FROM base_t WHERE i = 1;
+UPDATE base_t SET v = v*10 WHERE i=2;
+SELECT * FROM mv_self ORDER BY v1;
+ v1  | v2  
+-----+-----
+  30 |  30
+  40 |  40
+ 200 | 200
+(3 rows)
+
+WITH
+ ins_t1 AS (INSERT INTO base_t VALUES (5,50) RETURNING 1),
+ ins_t2 AS (INSERT INTO base_t VALUES (6,60) RETURNING 1),
+ upd_t AS (UPDATE base_t SET v = v + 100  RETURNING 1),
+ dlt_t AS (DELETE FROM base_t WHERE i IN (4,5)  RETURNING 1)
+SELECT NULL;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT * FROM mv_self ORDER BY v1;
+ v1  | v2  
+-----+-----
+  50 |  50
+  60 |  60
+ 130 | 130
+ 300 | 300
+(4 rows)
+
+ROLLBACK;
+-- support simultaneous table changes
+BEGIN;
+CREATE TABLE base_r (i int, v int);
+CREATE TABLE base_s (i int, v int);
+INSERT INTO base_r VALUES (1, 10), (2, 20), (3, 30);
+INSERT INTO base_s VALUES (1, 100), (2, 200), (3, 300);
+CREATE INCREMENTAL MATERIALIZED VIEW mv(v1, v2) AS
+ SELECT r.v, s.v FROM base_r AS r JOIN base_s AS s USING(i);
+NOTICE:  could not create an index on materialized view "mv" automatically
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY v1;
+ v1 | v2  
+----+-----
+ 10 | 100
+ 20 | 200
+ 30 | 300
+(3 rows)
+
+WITH
+ ins_r AS (INSERT INTO base_r VALUES (1,11) RETURNING 1),
+ ins_r2 AS (INSERT INTO base_r VALUES (3,33) RETURNING 1),
+ ins_s AS (INSERT INTO base_s VALUES (2,222) RETURNING 1),
+ upd_r AS (UPDATE base_r SET v = v + 1000 WHERE i = 2 RETURNING 1),
+ dlt_s AS (DELETE FROM base_s WHERE i = 3 RETURNING 1)
+SELECT NULL;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT * FROM mv ORDER BY v1;
+  v1  | v2  
+------+-----
+   10 | 100
+   11 | 100
+ 1020 | 200
+ 1020 | 222
+(4 rows)
+
+ROLLBACK;
+-- support foreign reference constraints
+BEGIN;
+CREATE TABLE ri1 (i int PRIMARY KEY);
+CREATE TABLE ri2 (i int PRIMARY KEY REFERENCES ri1(i) ON UPDATE CASCADE ON DELETE CASCADE, v int);
+INSERT INTO ri1 VALUES (1),(2),(3);
+INSERT INTO ri2 VALUES (1),(2),(3);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ri(i1, i2) AS
+ SELECT ri1.i, ri2.i FROM ri1 JOIN ri2 USING(i);
+NOTICE:  created index "mv_ri_index" on materialized view "mv_ri"
+SELECT * FROM mv_ri ORDER BY i1;
+ i1 | i2 
+----+----
+  1 |  1
+  2 |  2
+  3 |  3
+(3 rows)
+
+UPDATE ri1 SET i=10 where i=1;
+DELETE FROM ri1 WHERE i=2;
+SELECT * FROM mv_ri ORDER BY i2;
+ i1 | i2 
+----+----
+  3 |  3
+ 10 | 10
+(2 rows)
+
+ROLLBACK;
+-- views including NULL
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1,10),(2, NULL);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+NOTICE:  could not create an index on materialized view "mv" automatically
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY i;
+ i | v  
+---+----
+ 1 | 10
+ 2 |   
+(2 rows)
+
+UPDATE base_t SET v = 20 WHERE i = 2;
+SELECT * FROM mv ORDER BY i;
+ i | v  
+---+----
+ 1 | 10
+ 2 | 20
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+NOTICE:  could not create an index on materialized view "mv" automatically
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY i;
+ i 
+---
+(0 rows)
+
+INSERT INTO base_t VALUES (1),(NULL);
+SELECT * FROM mv ORDER BY i;
+ i 
+---
+ 1
+  
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (1, 10), (1, 20);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, sum(v) FROM base_t GROUP BY i;
+NOTICE:  created index "mv_index" on materialized view "mv"
+SELECT * FROM mv ORDER BY i;
+ i | sum 
+---+-----
+ 1 |  30
+   |   3
+(2 rows)
+
+UPDATE base_t SET v = v * 10;
+SELECT * FROM mv ORDER BY i;
+ i | sum 
+---+-----
+ 1 | 300
+   |  30
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (NULL, 3), (NULL, 4), (NULL, 5);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, min(v), max(v) FROM base_t GROUP BY i;
+NOTICE:  created index "mv_index" on materialized view "mv"
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   1 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 1;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 3;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 5;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   4
+(1 row)
+
+ROLLBACK;
+-- IMMV containing user defined type
+BEGIN;
+CREATE TYPE mytype;
+CREATE FUNCTION mytype_in(cstring)
+ RETURNS mytype AS 'int4in'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+NOTICE:  return type mytype is only a shell
+CREATE FUNCTION mytype_out(mytype)
+ RETURNS cstring AS 'int4out'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+NOTICE:  argument type mytype is only a shell
+CREATE TYPE mytype (
+ LIKE = int4,
+ INPUT = mytype_in,
+ OUTPUT = mytype_out
+);
+CREATE FUNCTION mytype_eq(mytype, mytype)
+ RETURNS bool AS 'int4eq'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_lt(mytype, mytype)
+ RETURNS bool AS 'int4lt'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_cmp(mytype, mytype)
+ RETURNS integer AS 'btint4cmp'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE OPERATOR = (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_eq);
+CREATE OPERATOR < (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_lt);
+CREATE OPERATOR CLASS mytype_ops
+ DEFAULT FOR TYPE mytype USING btree AS
+ OPERATOR        1       <,
+ OPERATOR        3       = ,
+ FUNCTION		1		mytype_cmp(mytype,mytype);
+CREATE TABLE t_mytype (x mytype);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_mytype AS
+ SELECT * FROM t_mytype;
+NOTICE:  could not create an index on materialized view "mv_mytype" automatically
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+INSERT INTO t_mytype VALUES ('1'::mytype);
+SELECT * FROM mv_mytype;
+ x 
+---
+ 1
+(1 row)
+
+ROLLBACK;
+-- outer join is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i;
+ERROR:  OUTER JOIN is not supported on incrementally maintainable materialized view
+-- CTE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS
+    WITH b AS ( SELECT * FROM mv_base_b) SELECT a.i,a.j FROM mv_base_a a, b WHERE a.i = b.i;
+ERROR:  CTE is not supported on incrementally maintainable materialized view
+-- contain system column
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm01 AS SELECT i,j,xmin FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT i,j,xmin::text AS x_min FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm06 AS SELECT i,j,xidsend(xmin) AS x_min FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+-- contain subquery
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm03 AS SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT * FROM mv_base_b) b WHERE a.i = b.i;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm05 AS SELECT i,j, (SELECT k FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+-- contain ORDER BY
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm07 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) ORDER BY i,j,k;
+ERROR:  ORDER BY clause is not supported on incrementally maintainable materialized view
+-- contain HAVING
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm08 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) GROUP BY i,j,k HAVING SUM(i) > 5;
+ERROR:   HAVING clause is not supported on incrementally maintainable materialized view
+-- contain view or materialized view
+CREATE VIEW b_view AS SELECT i,k FROM mv_base_b;
+CREATE MATERIALIZED VIEW b_mview AS SELECT i,k FROM mv_base_b;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm07 AS SELECT a.i,a.j FROM mv_base_a a,b_view b WHERE a.i = b.i;
+ERROR:  VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm08 AS SELECT a.i,a.j FROM mv_base_a a,b_mview b WHERE a.i = b.i;
+ERROR:  VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm09 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm10 AS SELECT a.i,a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) OR a.i > 5;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+-- contain mutable functions
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+ERROR:  mutable function is not supported on incrementally maintainable materialized view
+HINT:  functions must be marked IMMUTABLE
+-- LIMIT/OFFSET is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm13 AS SELECT i,j FROM mv_base_a LIMIT 10 OFFSET 5;
+ERROR:  LIMIT/OFFSET clause is not supported on incrementally maintainable materialized view
+-- DISTINCT ON is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm14 AS SELECT DISTINCT ON(i) i, j FROM mv_base_a;
+ERROR:  DISTINCT ON is not supported on incrementally maintainable materialized view
+-- TABLESAMPLE clause is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm15 AS SELECT i, j FROM mv_base_a TABLESAMPLE SYSTEM(50);
+ERROR:  TABLESAMPLE clause is not supported on incrementally maintainable materialized view
+-- window functions are not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm16 AS SELECT *, cume_dist() OVER (ORDER BY i) AS rank FROM mv_base_a;
+ERROR:  window functions are not supported on incrementally maintainable materialized view
+-- aggregate function with some options is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm17 AS SELECT COUNT(*) FILTER(WHERE i < 3) FROM mv_base_a;
+ERROR:  aggregate function with FILTER clause is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm18 AS SELECT COUNT(DISTINCT i)  FROM mv_base_a;
+ERROR:  aggregate function with DISTINCT arguments is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm19 AS SELECT array_agg(j ORDER BY i DESC) FROM mv_base_a;
+ERROR:  aggregate function with ORDER clause is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm20 AS SELECT i,SUM(j) FROM mv_base_a GROUP BY GROUPING SETS((i),());
+ERROR:  GROUPING SETS, ROLLUP, or CUBE clauses is not supported on incrementally maintainable materialized view
+-- inheritance parent is not supported
+BEGIN;
+CREATE TABLE parent (i int, v int);
+CREATE TABLE child_a(options text) INHERITS(parent);
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm21 AS SELECT * FROM parent;
+ERROR:  inheritance parent is not supported on incrementally maintainable materialized view
+ROLLBACK;
+-- UNION statement is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm22 AS SELECT i,j FROM mv_base_a UNION ALL SELECT i,k FROM mv_base_b;;
+ERROR:  UNION/INTERSECT/EXCEPT statements are not supported on incrementally maintainable materialized view
+-- empty target list is not allowed with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm25 AS SELECT FROM mv_base_a;
+ERROR:  empty target list is not supported on incrementally maintainable materialized view
+-- FOR UPDATE/SHARE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm26 AS SELECT i,j FROM mv_base_a FOR UPDATE;
+ERROR:  FOR UPDATE/SHARE clause is not supported on incrementally maintainable materialized view
+-- tartget list cannot contain ivm column that start with '__ivm'
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm28 AS SELECT i AS "__ivm_count__" FROM mv_base_a;
+ERROR:  column name __ivm_count__ is not supported on incrementally maintainable materialized view
+-- expressions specified in GROUP BY must appear in the target list.
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm29 AS SELECT COUNT(i) FROM mv_base_a GROUP BY i;
+ERROR:  GROUP BY expression not appeared in select list is not supported on incrementally maintainable materialized view
+-- experssions containing an aggregate is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm30 AS SELECT sum(i)*0.5 FROM mv_base_a;
+ERROR:  expression containing an aggregate in it is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm31 AS SELECT sum(i)/sum(j) FROM mv_base_a;
+ERROR:  expression containing an aggregate in it is not supported on incrementally maintainable materialized view
+-- VALUES is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_only_values1 AS values(1);
+ERROR:  VALUES is not supported on incrementally maintainable materialized view
+-- base table which has row level security
+DROP USER IF EXISTS ivm_admin;
+NOTICE:  role "ivm_admin" does not exist, skipping
+DROP USER IF EXISTS ivm_user;
+NOTICE:  role "ivm_user" does not exist, skipping
+CREATE USER ivm_admin;
+CREATE USER ivm_user;
+SET SESSION AUTHORIZATION ivm_admin;
+CREATE TABLE rls_tbl(id int, data text, owner name);
+INSERT INTO rls_tbl VALUES
+  (1,'foo','ivm_user'),
+  (2,'bar','postgres');
+CREATE TABLE num_tbl(id int, num text);
+INSERT INTO num_tbl VALUES
+  (1,'one'),
+  (2,'two'),
+  (3,'three'),
+  (4,'four');
+CREATE POLICY rls_tbl_policy ON rls_tbl FOR SELECT TO PUBLIC USING(owner = current_user);
+CREATE POLICY rls_tbl_policy2 ON rls_tbl FOR INSERT TO PUBLIC WITH CHECK(current_user LIKE 'ivm_%');
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+GRANT ALL on rls_tbl TO PUBLIC;
+GRANT ALL on num_tbl TO PUBLIC;
+SET SESSION AUTHORIZATION ivm_user;
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls AS SELECT * FROM rls_tbl;
+NOTICE:  could not create an index on materialized view "ivm_rls" automatically
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+ id | data |  owner   
+----+------+----------
+  1 | foo  | ivm_user
+(1 row)
+
+INSERT INTO rls_tbl VALUES
+  (3,'baz','ivm_user'),
+  (4,'qux','postgres');
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+ id | data |  owner   
+----+------+----------
+  1 | foo  | ivm_user
+  3 | baz  | ivm_user
+(2 rows)
+
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls2 AS SELECT * FROM rls_tbl JOIN num_tbl USING(id);
+NOTICE:  could not create an index on materialized view "ivm_rls2" automatically
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+RESET SESSION AUTHORIZATION;
+WITH
+ x AS (UPDATE rls_tbl SET data = data || '_2' where id in (3,4)),
+ y AS (UPDATE num_tbl SET num = num || '_2' where id in (3,4))
+SELECT;
+--
+(1 row)
+
+SELECT * FROM ivm_rls2 ORDER BY 1,2,3;
+ id | data  |  owner   |   num   
+----+-------+----------+---------
+  1 | foo   | ivm_user | one
+  3 | baz_2 | ivm_user | three_2
+(2 rows)
+
+DROP TABLE rls_tbl CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to materialized view ivm_rls
+drop cascades to materialized view ivm_rls2
+DROP TABLE num_tbl CASCADE;
+DROP USER ivm_user;
+DROP USER ivm_admin;
+DROP TABLE mv_base_b CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to materialized view mv_ivm_1
+drop cascades to view b_view
+drop cascades to materialized view b_mview
+DROP TABLE mv_base_a CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0..b0350f47dd 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -86,7 +86,7 @@ test: brin_bloom brin_multi
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort incremental_matview
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/sql/incremental_matview.sql b/src/test/regress/sql/incremental_matview.sql
new file mode 100644
index 0000000000..311e9b96fb
--- /dev/null
+++ b/src/test/regress/sql/incremental_matview.sql
@@ -0,0 +1,400 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) WITH NO DATA;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+REFRESH MATERIALIZED VIEW mv_ivm_1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+-- immediate maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+
+-- rename of IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_rename AS SELECT DISTINCT * FROM mv_base_a;
+ALTER MATERIALIZED VIEW mv_ivm_rename RENAME COLUMN __ivm_count__ TO xxx;
+DROP MATERIALIZED VIEW mv_ivm_rename;
+
+-- unique index on IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_unique AS SELECT DISTINCT * FROM mv_base_a;
+CREATE UNIQUE INDEX ON mv_ivm_unique(__ivm_count__);
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__));
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__ + 1));
+DROP MATERIALIZED VIEW mv_ivm_unique;
+
+-- some query syntax
+BEGIN;
+CREATE FUNCTION ivm_func() RETURNS int LANGUAGE 'sql'
+       AS 'SELECT 1' IMMUTABLE;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_func AS SELECT * FROM ivm_func();
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_no_tbl AS SELECT 1;
+ROLLBACK;
+
+-- result of materialized view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ROLLBACK;
+
+-- support SUM(), COUNT() and AVG() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i), AVG(j) FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ROLLBACK;
+
+-- support COUNT(*) aggregate function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(*) FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j), COUNT(j), AVG(j) FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+INSERT INTO mv_base_a VALUES(6,60);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ROLLBACK;
+
+-- resolved issue: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support MIN(), MAX() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT i, MIN(j), MAX(j)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES
+  (1,11), (1,12),
+  (2,21), (2,22),
+  (3,31), (3,32),
+  (4,41), (4,42),
+  (5,51), (5,52);
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support MIN(), MAX() aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT MIN(j), MAX(j)  FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+INSERT INTO mv_base_a VALUES
+  (0,0), (6,60), (7,70);
+SELECT * FROM mv_ivm_min_max;
+DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
+SELECT * FROM mv_ivm_min_max;
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+ROLLBACK;
+
+-- support self join view and multiple change on the same table
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1, 10), (2, 20), (3, 30);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_self(v1, v2) AS
+ SELECT t1.v, t2.v FROM base_t AS t1 JOIN base_t AS t2 ON t1.i = t2.i;
+SELECT * FROM mv_self ORDER BY v1;
+INSERT INTO base_t VALUES (4,40);
+DELETE FROM base_t WHERE i = 1;
+UPDATE base_t SET v = v*10 WHERE i=2;
+SELECT * FROM mv_self ORDER BY v1;
+WITH
+ ins_t1 AS (INSERT INTO base_t VALUES (5,50) RETURNING 1),
+ ins_t2 AS (INSERT INTO base_t VALUES (6,60) RETURNING 1),
+ upd_t AS (UPDATE base_t SET v = v + 100  RETURNING 1),
+ dlt_t AS (DELETE FROM base_t WHERE i IN (4,5)  RETURNING 1)
+SELECT NULL;
+SELECT * FROM mv_self ORDER BY v1;
+ROLLBACK;
+
+-- support simultaneous table changes
+BEGIN;
+CREATE TABLE base_r (i int, v int);
+CREATE TABLE base_s (i int, v int);
+INSERT INTO base_r VALUES (1, 10), (2, 20), (3, 30);
+INSERT INTO base_s VALUES (1, 100), (2, 200), (3, 300);
+CREATE INCREMENTAL MATERIALIZED VIEW mv(v1, v2) AS
+ SELECT r.v, s.v FROM base_r AS r JOIN base_s AS s USING(i);
+SELECT * FROM mv ORDER BY v1;
+WITH
+ ins_r AS (INSERT INTO base_r VALUES (1,11) RETURNING 1),
+ ins_r2 AS (INSERT INTO base_r VALUES (3,33) RETURNING 1),
+ ins_s AS (INSERT INTO base_s VALUES (2,222) RETURNING 1),
+ upd_r AS (UPDATE base_r SET v = v + 1000 WHERE i = 2 RETURNING 1),
+ dlt_s AS (DELETE FROM base_s WHERE i = 3 RETURNING 1)
+SELECT NULL;
+SELECT * FROM mv ORDER BY v1;
+ROLLBACK;
+
+-- support foreign reference constraints
+BEGIN;
+CREATE TABLE ri1 (i int PRIMARY KEY);
+CREATE TABLE ri2 (i int PRIMARY KEY REFERENCES ri1(i) ON UPDATE CASCADE ON DELETE CASCADE, v int);
+INSERT INTO ri1 VALUES (1),(2),(3);
+INSERT INTO ri2 VALUES (1),(2),(3);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ri(i1, i2) AS
+ SELECT ri1.i, ri2.i FROM ri1 JOIN ri2 USING(i);
+SELECT * FROM mv_ri ORDER BY i1;
+UPDATE ri1 SET i=10 where i=1;
+DELETE FROM ri1 WHERE i=2;
+SELECT * FROM mv_ri ORDER BY i2;
+ROLLBACK;
+
+-- views including NULL
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1,10),(2, NULL);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+SELECT * FROM mv ORDER BY i;
+UPDATE base_t SET v = 20 WHERE i = 2;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+SELECT * FROM mv ORDER BY i;
+INSERT INTO base_t VALUES (1),(NULL);
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (1, 10), (1, 20);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, sum(v) FROM base_t GROUP BY i;
+SELECT * FROM mv ORDER BY i;
+UPDATE base_t SET v = v * 10;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (NULL, 3), (NULL, 4), (NULL, 5);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, min(v), max(v) FROM base_t GROUP BY i;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 1;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 3;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 5;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+-- IMMV containing user defined type
+BEGIN;
+
+CREATE TYPE mytype;
+CREATE FUNCTION mytype_in(cstring)
+ RETURNS mytype AS 'int4in'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_out(mytype)
+ RETURNS cstring AS 'int4out'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE TYPE mytype (
+ LIKE = int4,
+ INPUT = mytype_in,
+ OUTPUT = mytype_out
+);
+
+CREATE FUNCTION mytype_eq(mytype, mytype)
+ RETURNS bool AS 'int4eq'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_lt(mytype, mytype)
+ RETURNS bool AS 'int4lt'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_cmp(mytype, mytype)
+ RETURNS integer AS 'btint4cmp'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+
+CREATE OPERATOR = (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_eq);
+CREATE OPERATOR < (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_lt);
+
+CREATE OPERATOR CLASS mytype_ops
+ DEFAULT FOR TYPE mytype USING btree AS
+ OPERATOR        1       <,
+ OPERATOR        3       = ,
+ FUNCTION		1		mytype_cmp(mytype,mytype);
+
+CREATE TABLE t_mytype (x mytype);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_mytype AS
+ SELECT * FROM t_mytype;
+INSERT INTO t_mytype VALUES ('1'::mytype);
+SELECT * FROM mv_mytype;
+
+ROLLBACK;
+
+-- outer join is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i;
+-- CTE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS
+    WITH b AS ( SELECT * FROM mv_base_b) SELECT a.i,a.j FROM mv_base_a a, b WHERE a.i = b.i;
+-- contain system column
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm01 AS SELECT i,j,xmin FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT i,j,xmin::text AS x_min FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm06 AS SELECT i,j,xidsend(xmin) AS x_min FROM mv_base_a;
+-- contain subquery
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm03 AS SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT * FROM mv_base_b) b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm05 AS SELECT i,j, (SELECT k FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+-- contain ORDER BY
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm07 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) ORDER BY i,j,k;
+-- contain HAVING
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm08 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) GROUP BY i,j,k HAVING SUM(i) > 5;
+
+-- contain view or materialized view
+CREATE VIEW b_view AS SELECT i,k FROM mv_base_b;
+CREATE MATERIALIZED VIEW b_mview AS SELECT i,k FROM mv_base_b;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm07 AS SELECT a.i,a.j FROM mv_base_a a,b_view b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm08 AS SELECT a.i,a.j FROM mv_base_a a,b_mview b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm09 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm10 AS SELECT a.i,a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) OR a.i > 5;
+
+-- contain mutable functions
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+
+-- LIMIT/OFFSET is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm13 AS SELECT i,j FROM mv_base_a LIMIT 10 OFFSET 5;
+
+-- DISTINCT ON is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm14 AS SELECT DISTINCT ON(i) i, j FROM mv_base_a;
+
+-- TABLESAMPLE clause is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm15 AS SELECT i, j FROM mv_base_a TABLESAMPLE SYSTEM(50);
+
+-- window functions are not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm16 AS SELECT *, cume_dist() OVER (ORDER BY i) AS rank FROM mv_base_a;
+
+-- aggregate function with some options is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm17 AS SELECT COUNT(*) FILTER(WHERE i < 3) FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm18 AS SELECT COUNT(DISTINCT i)  FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm19 AS SELECT array_agg(j ORDER BY i DESC) FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm20 AS SELECT i,SUM(j) FROM mv_base_a GROUP BY GROUPING SETS((i),());
+
+-- inheritance parent is not supported
+BEGIN;
+CREATE TABLE parent (i int, v int);
+CREATE TABLE child_a(options text) INHERITS(parent);
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm21 AS SELECT * FROM parent;
+ROLLBACK;
+
+-- UNION statement is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm22 AS SELECT i,j FROM mv_base_a UNION ALL SELECT i,k FROM mv_base_b;;
+
+-- empty target list is not allowed with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm25 AS SELECT FROM mv_base_a;
+
+-- FOR UPDATE/SHARE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm26 AS SELECT i,j FROM mv_base_a FOR UPDATE;
+
+-- tartget list cannot contain ivm column that start with '__ivm'
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm28 AS SELECT i AS "__ivm_count__" FROM mv_base_a;
+
+-- expressions specified in GROUP BY must appear in the target list.
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm29 AS SELECT COUNT(i) FROM mv_base_a GROUP BY i;
+
+-- experssions containing an aggregate is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm30 AS SELECT sum(i)*0.5 FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm31 AS SELECT sum(i)/sum(j) FROM mv_base_a;
+
+-- VALUES is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_only_values1 AS values(1);
+
+-- base table which has row level security
+DROP USER IF EXISTS ivm_admin;
+DROP USER IF EXISTS ivm_user;
+CREATE USER ivm_admin;
+CREATE USER ivm_user;
+SET SESSION AUTHORIZATION ivm_admin;
+
+CREATE TABLE rls_tbl(id int, data text, owner name);
+INSERT INTO rls_tbl VALUES
+  (1,'foo','ivm_user'),
+  (2,'bar','postgres');
+CREATE TABLE num_tbl(id int, num text);
+INSERT INTO num_tbl VALUES
+  (1,'one'),
+  (2,'two'),
+  (3,'three'),
+  (4,'four');
+CREATE POLICY rls_tbl_policy ON rls_tbl FOR SELECT TO PUBLIC USING(owner = current_user);
+CREATE POLICY rls_tbl_policy2 ON rls_tbl FOR INSERT TO PUBLIC WITH CHECK(current_user LIKE 'ivm_%');
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+GRANT ALL on rls_tbl TO PUBLIC;
+GRANT ALL on num_tbl TO PUBLIC;
+
+SET SESSION AUTHORIZATION ivm_user;
+
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls AS SELECT * FROM rls_tbl;
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+INSERT INTO rls_tbl VALUES
+  (3,'baz','ivm_user'),
+  (4,'qux','postgres');
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls2 AS SELECT * FROM rls_tbl JOIN num_tbl USING(id);
+
+RESET SESSION AUTHORIZATION;
+
+WITH
+ x AS (UPDATE rls_tbl SET data = data || '_2' where id in (3,4)),
+ y AS (UPDATE num_tbl SET num = num || '_2' where id in (3,4))
+SELECT;
+SELECT * FROM ivm_rls2 ORDER BY 1,2,3;
+
+DROP TABLE rls_tbl CASCADE;
+DROP TABLE num_tbl CASCADE;
+DROP USER ivm_user;
+DROP USER ivm_admin;
+
+DROP TABLE mv_base_b CASCADE;
+DROP TABLE mv_base_a CASCADE;
-- 
2.17.1

v23-0010-Add-documentations-about-Incremental-View-Mainte.patchtext/x-diff; name=v23-0010-Add-documentations-about-Incremental-View-Mainte.patchDownload
From f37f3a26721cf2577731b05f294de11762098060 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:25:34 +0900
Subject: [PATCH v23 10/15] Add documentations about Incremental View
 Maintenance

---
 doc/src/sgml/catalogs.sgml                    |  24 +-
 .../sgml/ref/create_materialized_view.sgml    | 133 +++++-
 .../sgml/ref/refresh_materialized_view.sgml   |   6 +-
 doc/src/sgml/rules.sgml                       | 443 ++++++++++++++++++
 4 files changed, 601 insertions(+), 5 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..b1ebdae501 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2183,6 +2183,15 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>relisivm</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if materialized view enables incremental view maintenance
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>relrewrite</structfield> <type>oid</type>
@@ -3465,6 +3474,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para>
      </listitem>
     </varlistentry>
+
+    <varlistentry>
+     <term><symbol>DEPENDENCY_IMMV</symbol> (<literal>m</literal>)</term>
+     <listitem>
+      <para>
+       The dependent object was created as part of creation of the Materialized
+       View with Incremental View Maintenance reference, and is really just a 
+       part of its internal implementation. The dependent object must not be
+       dropped unless the materialized view is also dropped.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
 
    Other dependency flavors might be needed in future.
@@ -3848,7 +3869,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <structfield>extrelocatable</structfield> <type>bool</type>
       </para>
       <para>
-       True if extension can be relocated to another schema
+      True for materialized views which are enabled for incremental
+      view maintenance (IVM).
       </para></entry>
      </row>
 
diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml
index d8c48252f4..5abccf14ba 100644
--- a/doc/src/sgml/ref/create_materialized_view.sgml
+++ b/doc/src/sgml/ref/create_materialized_view.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
+CREATE [ INCREMENTAL ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
     [ (<replaceable>column_name</replaceable> [, ...] ) ]
     [ USING <replaceable class="parameter">method</replaceable> ]
     [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
@@ -60,6 +60,134 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
   <title>Parameters</title>
 
   <variablelist>
+   <varlistentry>
+    <term><literal>INCREMENTAL</literal></term>
+    <listitem>
+     <para>
+      If specified, some triggers are automatically created so that the rows
+      of the materialized view are immediately updated when base tables of the
+      materialized view are updated. In general, this allows faster update of
+      the materialized view at a price of slower update of the base tables
+      because the triggers will be invoked. We call this form of materialized
+      view as "Incrementally Maintainable Materialized View" (IMMV).
+     </para>
+     <para>
+      When <acronym>IMMV</acronym> is defined, a unique index is created on the view
+      automatically if possible.  If the view definition query has a GROUP BY clause,
+      a unique index is created on the columns of GROUP BY expressions.  Also, if the
+      view has DISTINCT clause, a unique index is created on all columns in the target
+      list.  Otherwise, if the view contains all primary key attritubes of its base
+      tables in the target list, a unique index is created on these attritubes.  In
+      other cases, no index is created.
+     </para>
+     <para>
+      There are restrictions of query definitions allowed to use this
+      option. The following are supported in query definitions for IMMV:
+      <itemizedlist>
+
+       <listitem>
+        <para>
+         Inner joins (including self-joins).
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Some built-in aggregate functions (count, sum, avg, min, max) without a HAVING
+         clause. 
+        </para>
+        </listitem>
+      </itemizedlist>
+
+      Unsupported queries with this option include the following:
+
+      <itemizedlist>
+       <listitem>
+        <para>
+         Outer joins.
+        </para>
+        </listitem>
+       <listitem>
+
+       <listitem>
+        <para>
+         Sub-queries.
+        </para>
+        </listitem>
+       <listitem>
+
+       <listitem>
+        <para>
+         Aggregate functions other than built-in count, sum, avg, min and max.
+        </para>
+        </listitem>
+       <listitem>
+        <para>
+         Aggregate functions with a HAVING clause.
+        </para>
+        </listitem>
+       <listitem>
+        <para>
+         DISTINCT ON, WINDOW, VALUES, LIMIT and OFFSET clause.
+        </para>
+       </listitem>
+      </itemizedlist>
+
+      Other restrictions include:
+      <itemizedlist>
+
+       <listitem>
+        <para>
+         IMMVs must be based on simple base tables. It's not supported to
+         create them on top of views or materialized views.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         When the TRUNCATE command is executed on a base table,
+         no changes are made to the materialized view.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         It is not supported to include system columns in an IMMV.
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+ERROR:  system column is not supported with IVM
+         </programlisting>
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Non-immutable functions are not supported.
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+ERROR:  functions in IMMV must be marked IMMUTABLE
+         </programlisting>
+        </para>
+        </listitem>
+
+       <listitem>
+        <para>
+         IMMVs do not support expressions that contains aggregates
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Logical replication does not support IMMVs.
+        </para>
+       </listitem>
+
+      </itemizedlist>
+
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>IF NOT EXISTS</literal></term>
     <listitem>
@@ -153,7 +281,8 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
       This clause specifies whether or not the materialized view should be
       populated at creation time.  If not, the materialized view will be
       flagged as unscannable and cannot be queried until <command>REFRESH
-      MATERIALIZED VIEW</command> is used.
+      MATERIALIZED VIEW</command> is used.  Also, if the view is IMMV,
+      triggers for maintaining the view are not created.
      </para>
     </listitem>
    </varlistentry>
diff --git a/doc/src/sgml/ref/refresh_materialized_view.sgml b/doc/src/sgml/ref/refresh_materialized_view.sgml
index 3bf8884447..dc81853057 100644
--- a/doc/src/sgml/ref/refresh_materialized_view.sgml
+++ b/doc/src/sgml/ref/refresh_materialized_view.sgml
@@ -35,9 +35,11 @@ REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] <replaceable class="parameter">name</
    owner of the materialized view.  The old contents are discarded.  If
    <literal>WITH DATA</literal> is specified (or defaults) the backing query
    is executed to provide the new data, and the materialized view is left in a
-   scannable state.  If <literal>WITH NO DATA</literal> is specified no new
+   scannable state.  Also, if the view is an incrementally maintainable materialized
+   view (IMMV) and was unpopulated, triggers for maintaining
+   the view are created.  If <literal>WITH NO DATA</literal> is specified no new
    data is generated and the materialized view is left in an unscannable
-   state.
+   state.  If the view is IMMV, the triggers are dropped.
   </para>
   <para>
    <literal>CONCURRENTLY</literal> and <literal>WITH NO DATA</literal> may not
diff --git a/doc/src/sgml/rules.sgml b/doc/src/sgml/rules.sgml
index 5024e4ff70..597332fe96 100644
--- a/doc/src/sgml/rules.sgml
+++ b/doc/src/sgml/rules.sgml
@@ -1093,6 +1093,449 @@ SELECT word FROM words ORDER BY word &lt;-&gt; 'caterpiler' LIMIT 10;
 
 </sect1>
 
+<sect1 id="rules-ivm">
+<title>Incremental View Maintenance</title>
+
+<indexterm zone="rules-ivm">
+ <primary>incremental view maintenance</primary>
+</indexterm>
+
+<indexterm zone="rules-ivm">
+ <primary>materialized view</primary>
+ <secondary>incremental view maintenance</secondary>
+</indexterm>
+
+<indexterm zone="rules-ivm">
+ <primary>view</primary>
+ <secondary>incremental view maintenance</secondary>
+</indexterm>
+
+<sect2 id="rules-ivm-overview">
+<title>Overview</title>
+
+<para>
+    Incremental View Maintenance (<acronym>IVM</acronym>) is a way to make
+    materialized views up-to-date in which only incremental changes are computed
+    and applied on views rather than recomputing the contents from scratch as
+    <command>REFRESH MATERIALIZED VIEW</command> does.  <acronym>IVM</acronym>
+    can update materialized views more efficiently than recomputation when only
+    small parts of the view are changed.
+</para>
+
+<para>
+    There are two approaches with regard to timing of view maintenance:
+    immediate and deferred.  In immediate maintenance, views are updated in the
+    same transaction that its base table is modified.  In deferred maintenance,
+    views are updated after the transaction is committed, for example, when the
+    view is accessed, as a response to user command like <command>REFRESH
+    MATERIALIZED VIEW</command>, or periodically in background, and so on.
+    <productname>PostgreSQL</productname> currently implements only a kind of
+    immediate maintenance, in which materialized views are updated immediately
+    in AFTER triggers when a base table is modified.
+</para>
+
+<para>
+    To create materialized views supporting <acronym>IVM</acronym>, use the
+    <command>CREATE INCREMENTAL MATERIALIZED VIEW</command>, for example:
+<programlisting>
+CREATE <emphasis>INCREMENTAL</emphasis> MATERIALIZED VIEW mymatview AS SELECT * FROM mytab;
+</programlisting>
+    When a materialized view is created with the <literal>INCREMENTAL</literal>
+    keyword, some triggers are automatically created so that the view's contents are
+    immediately updated when its base tables are modified. We call this form
+    of materialized view an Incrementally Maintainable Materialized View
+    (<acronym>IMMV</acronym>).
+<programlisting>
+postgres=# CREATE INCREMENTAL MATERIALIZED VIEW m AS SELECT * FROM t0;
+NOTICE:  could not create an index on materialized view "m" automatically
+HINT:  Create an index on the materialized view for effcient incremental maintenance.
+SELECT 3
+postgres=# SELECT * FROM m;
+ i
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+postgres=# INSERT INTO t0 VALUES (4);
+INSERT 0 1
+postgres=# SELECT * FROM m; -- automatically updated
+ i
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+</programlisting>
+</para>
+
+<para>
+    Some <acronym>IMMV</acronym>s have hidden columns which are added
+    automatically when a materialized view is created. Their name starts
+    with <literal>__ivm_</literal> and they contain information required
+    for maintaining the <acronym>IMMV</acronym>. Such columns are not visible
+    when the <acronym>IMMV</acronym> is accessed by <literal>SELECT *</literal>
+    but are visible if the column name is explicitly specified in the target
+    list. We can also see the hidden columns in <literal>\d</literal>
+    meta-commands of <command>psql</command> commands.
+</para>
+
+<para>
+    In general, <acronym>IMMV</acronym>s allow faster updates of materialized
+    views at the price of slower updates to their base tables. Updates of
+    <acronym>IMMV</acronym> is slower because triggers will be invoked and the
+    view is updated in triggers per modification statement.
+</para>
+
+<para>
+    For example, suppose a normal materialized view defined as below:
+
+<programlisting>
+test=# CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm AS
+        SELECT a.aid, b.bid, a.abalance, b.bbalance
+        FROM pgbench_accounts a JOIN pgbench_branches b USING(bid);
+SELECT 10000000
+
+</programlisting>
+
+    Updating a tuple in a base table of this materialized view is rapid but the
+   <command>REFRESH MATERIALIZED VIEW</command> command on this view takes a long time:
+
+<programlisting>
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 0.990 ms
+
+test=# REFRESH MATERIALIZED VIEW mv_normal ;
+REFRESH MATERIALIZED VIEW
+Time: 33533.952 ms (00:33.534)
+</programlisting>
+</para>
+
+<para>
+    On the other hand, after creating <acronym>IMMV</acronym> with the same view
+    definition as below:
+
+<programlisting>
+test=# CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm AS
+        SELECT a.aid, b.bid, a.abalance, b.bbalance
+        FROM pgbench_accounts a JOIN pgbench_branches b USING(bid);
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+NOTICE:  created index "mv_ivm_index" on materialized view "mv_ivm"
+</programlisting>
+
+    updating a tuple in a base table takes more than the normal view,
+    but its content is updated automatically and this is faster than the
+    <command>REFRESH MATERIALIZED VIEW</command> command.
+
+<programlisting>
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 13.068 ms
+</programlisting>
+
+</para>
+
+<para>
+    Appropriate indexes on <acronym>IMMV</acronym>s are necessary for
+    efficient <acronym>IVM</acronym> because it looks for tuples to be
+    updated in <acronym>IMMV</acronym>.  If there are no indexes, it
+    will take a long time.
+</para>
+
+<para>
+    Therefore, when <acronym>IMMV</acronym> is defined, a unique index is created on the view
+    automatically if possible.  If the view definition query has a GROUP BY clause, a unique
+    index is created on the columns of GROUP BY expressions.  Also, if the view has DISTINCT
+    clause, a unique index is created on all columns in the target list. Otherwise, if the
+    view contains all primary key attritubes of its base tables in the target list, a unique
+    index is created on these attritubes.  In other cases, no index is created.
+</para>
+
+<para>
+    In the previous example, a unique index "mv_ivm_index" is created on aid and bid
+    columns of materialized view "mv_ivm", and this enables the rapid update of the view.
+    Dropping this index make updating the view take a loger time.
+<programlisting>
+test=# DROP INDEX mv_ivm_index;
+DROP INDEX
+Time: 67.081 ms
+
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 16386.245 ms (00:16.386)
+</programlisting>
+
+</para>
+
+<para>
+    <acronym>IVM</acronym> is effective when we want to keep a materialized
+    view up-to-date and small fraction of a base table is modified
+    infrequently.  Due to the overhead of immediate maintenance, <acronym>IVM</acronym>
+    is not effective when a base table is modified frequently.  Also, when a
+    large part of a base table is modified or large data is inserted into a
+    base table, <acronym>IVM</acronym> is not effective and the cost of
+    maintenance can be larger than the <command>REFRESH MATERIALIZED VIEW</command>
+    command. In such situation, we can use <command>REFRESH MATERIALIZED VIEW</command>
+    and specify <literal>WITH NO DATA</literal> to disable immediate
+    maintenance before modifying a base table. After a base table modification,
+    execute the <command>REFRESH MATERIALIZED VIEW</command> (with <literal>WITH DATA</literal>)
+    command to refresh the view data and enable immediate maintenance.
+</para>
+
+</sect2>
+
+<sect2>
+<title>Supported View Definitions and Restrictions</title>
+
+<para>
+    Currently, we can create <acronym>IMMV</acronym>s using inner joins, and some
+    aggregates. However, several restrictions apply to the definition of IMMV.
+</para>
+
+<sect3>
+<title>Joins</title>
+<para>
+    Inner joins including self-join are supported. Outer joins are not supported.
+</para>
+</sect3>
+
+<sect3>
+<title>Aggregates</title>
+<para>
+    Supported aggregate functions are <function>count</function>, <function>sum</function>,
+    <function>avg</function>, <function>min</function>, and <function>max</function>.
+    Currently, only built-in aggregate functions are supported and user defined
+    aggregates cannot be used.  When a base table is modified, the new aggregated
+    values are incrementally calculated using the old aggregated values and values
+    of related hidden columns stored in <acronym>IMMV</acronym>.
+</para>
+
+<para>
+     Note that for <function>min</function> or <function>max</function>, the new values
+     could be re-calculated from base tables with regard to the affected groups when a
+     tuple containing the current minimal or maximal values are deleted from a base table.
+     Therefore, it can takes a long time to update an <acronym>IMMV</acronym> containing
+     these functions.
+</para>
+
+<para>
+    Also note that using <function>sum</function> or <function>avg</function> on
+    <type>real</type> (<type>float4</type>) type or <type>double precision</type>
+    (<type>float8</type>) type in <acronym>IMMV</acronym> is unsafe. This is
+    because aggregated values in <acronym>IMMV</acronym> can become different from
+    results calculated from base tables due to the limited precision of these types.
+    To avoid this problem, use the <type>numeric</type> type instead.
+</para>
+
+    <sect4>
+    <title>Restrictions on Aggregates</title>
+    <para>
+        There are the following restrictions:
+    <itemizedlist>
+        <listitem>
+        <para>
+            If we have a <literal>GROUP BY</literal> clause, expressions specified in
+               <literal>GROUP BY</literal> must appear in the target list.  This is
+               how tuples to be updated in the <acronym>IMMV</acronym> are identified.
+               These attributes are used as scan keys for searching tuples in the
+               <acronym>IMMV</acronym>, so indexes on them are required for efficient
+               <acronym>IVM</acronym>.
+        </para>
+        </listitem>
+
+        <listitem>
+        <para>
+            <literal>HAVING</literal> clause cannot be used.
+        </para>
+        </listitem>
+    </itemizedlist>
+    </para>
+    </sect4>
+</sect3>
+
+<sect3>
+<title>Other General Restrictions</title>
+<para>
+    There are other restrictions which generally apply to <acronym>IMMV</acronym>:
+    <itemizedlist>
+        <listitem>
+          <para>
+           Sub-queries cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+           CTEs cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+           Window functions cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s must be based on simple base tables.  It's not
+               supported to create them on top of views or materialized views.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            LIMIT and OFFSET clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s cannot contain system columns.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s cannot contain non-immutable functions.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            UNION/INTERSECT/EXCEPT clauses cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            DISTINCT ON clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            TABLESAMPLE parameter cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            inheritance parent tables cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            VALUES clause cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <literal>GROUPING SETS</literal> and <literal>FILTER</literal> clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            FOR UPDATE/SHARE cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            targetlist cannot contain columns whose name start with <literal>__ivm_</literal>.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            targetlist cannot contain expressions which contain an aggregate in it.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+              Logical replication is not supported, that is, even when a base table
+               at a publisher node is modified, <acronym>IMMV</acronym>s at subscriber
+               nodes are not updated.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            When the <literal>TRUNCATE</literal> command is executed on a base table,
+               nothing is changed on the <acronym>IMMV</acronym>.
+          </para>
+        </listitem>
+
+    </itemizedlist>
+</para>
+</sect3>
+
+</sect2>
+
+<sect2>
+<title><literal>DISTINCT</literal></title>
+
+<para>
+    <productname>PostgreSQL</productname> supports <acronym>IMMV</acronym> with
+    <literal>DISTINCT</literal>.  For example, suppose a <acronym>IMMV</acronym>
+    defined with <literal>DISTINCT</literal> on a base table containing duplicate
+    tuples.  When tuples are deleted from the base table, a tuple in the view is
+    deleted if and only if the multiplicity of the tuple becomes zero.  Moreover,
+    when tuples are inserted into the base table, a tuple is inserted into the
+    view only if the same tuple doesn't already exist in it.
+</para>
+
+<para>
+    Physically, an <acronym>IMMV</acronym> defined with <literal>DISTINCT</literal>
+    contains tuples after eliminating duplicates, and the multiplicity of each tuple
+    is stored in a hidden column named <literal>__ivm_count__</literal>.
+</para>
+</sect2>
+
+<sect2>
+<title>Concurrent Transactions</title>
+<para>
+    Suppose an <acronym>IMMV</acronym> is defined on two base tables and each
+    table was modified in different a concurrent transaction simultaneously.
+    In the transaction which was committed first, <acronym>IMMV</acronym> can
+    be updated considering only the change which happened in this transaction.
+    On the other hand, in order to update the view correctly in the transaction
+    which was committed later, we need to know the changes occurred in
+    both transactions.  For this reason, <literal>ExclusiveLock</literal>
+    is held on an <acronym>IMMV</acronym> immediately after a base table is
+    modified in <literal>READ COMMITTED</literal> mode to make sure that
+    the <acronym>IMMV</acronym> is updated in the latter transaction after
+    the former transaction is committed.  In <literal>REPEATABLE READ</literal>
+    or <literal>SERIALIZABLE</literal> mode, an error is raised immediately
+    if lock acquisition fails because any changes which occurred in
+    other transactions are not be visible in these modes and
+    <acronym>IMMV</acronym> cannot be updated correctly in such situations.
+    However, as an exception if the view has only one base table, 
+    the lock held on thew view is <literal>RowExclusiveLock</literal>.
+</para>
+</sect2>
+
+<sect2>
+<title>Row Level Security</title>
+<para>
+    If some base tables have row level security policy, rows that are not visible
+    to the materialized view's owner are excluded from the result.  In addition, such
+    rows are excluded as well when views are incrementally maintained.  However, if a
+    new policy is defined or policies are changed after the materialized view was created,
+    the new policy will not be applied to the view contents.  To apply the new policy,
+    you need to refresh materialized views.
+</para>
+</sect2>
+
+</sect1>
+
 <sect1 id="rules-update">
 <title>Rules on <command>INSERT</command>, <command>UPDATE</command>, and <command>DELETE</command></title>
 
-- 
2.17.1

#189Zhihong Yu
zyu@yugabyte.com
In reply to: Yugo NAGATA (#188)
Re: Implementing Incremental View Maintenance

On Sun, Aug 1, 2021 at 11:30 PM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi hackers,

On Mon, 19 Jul 2021 09:24:30 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Wed, 14 Jul 2021 21:22:37 +0530
vignesh C <vignesh21@gmail.com> wrote:

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

Ok. I'll update the patch in a few days.

Attached is the latest patch set to add support for Incremental
Materialized View Maintenance (IVM)

The patches are rebased to the master and also revised with some
code cleaning.

IVM is a way to make materialized views up-to-date in which only
incremental changes are computed and applied on views rather than
recomputing the contents from scratch as REFRESH MATERIALIZED VIEW
does. IVM can update materialized views more efficiently
than recomputation when only small part of the view need updates.

The patch set implements a feature so that materialized views could be
updated automatically and immediately when a base table is modified.

Currently, our IVM implementation supports views which could contain
tuple duplicates whose definition includes:

- inner and outer joins including self-join
- DISTINCT
- some built-in aggregate functions (count, sum, agv, min, and max)
- a part of subqueries
-- simple subqueries in FROM clause
-- EXISTS subqueries in WHERE clause
- CTEs

We hope the IVM feature would be adopted into pg15. However, the size of
patch set has grown too large through supporting above features.
Therefore,
I think it is better to consider only a part of these features for the
first
release. Especially, I would like propose the following features for pg15.

- inner joins including self-join
- DISTINCT and views with tuple duplicates
- some built-in aggregate functions (count, sum, agv, min, and max)

By omitting outer-join, sub-queries, and CTE features, the patch size
becomes
less than half. I hope this will make a bit easer to review the IVM patch
set.

Here is a list of separated patches.

- 0001: Add a new syntax:
CREATE INCREMENTAL MATERIALIZED VIEW
- 0002: Add a new column relisivm to pg_class
- 0003: Add new deptype option 'm' in pg_depend
- 0004: Change trigger.c to allow to prolong life span of tupestores
containing Transition Tables generated via AFTER trigger
- 0005: Add IVM supprot for pg_dump
- 0006: Add IVM support for psql
- 0007: Add the basic IVM future:
This supports inner joins, DISTINCT, and tuple duplicates.
- 0008: Add aggregates (count, sum, avg, min, max) support for IVM
- 0009: Add regression tests for IVM
- 0010: Add documentation for IVM

We could split the patch furthermore if this would make reviews much
easer.
For example, I think 0007 could be split into the more basic part and the
part
for handling tuple duplicates. Moreover, 0008 could be split into "min/max"
and other aggregates because handling min/max is a bit more complicated
than
others.

I also attached IVM_extra.tar.gz that contains patches for sub-quereis,
outer-join, CTE support, just for your information.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

Hi,
For v23-0008-Add-aggregates-support-in-IVM.patch :

As a restriction, expressions specified in GROUP BY must appear in
the target list because tuples to be updated in IMMV are identified
by using this group keys.

IMMV -> IMVM (Incremental Materialized View Maintenance, as said above)
Or maybe it means 'incrementally maintainable materialized view'. It would
be better to use the same abbreviation.

this group keys -> this group key

+ errmsg("GROUP BY expression not appeared in select
list is not supported on incrementally maintainable materialized view")));

expression not appeared in select list -> expression not appearing in
select list

+ * For aggregate functions except to count

except to count -> except count

Cheers

#190r.takahashi_2@fujitsu.com
r.takahashi_2@fujitsu.com
In reply to: Zhihong Yu (#189)
RE: Implementing Incremental View Maintenance

Hi Nagata-san,

I am interested in this patch since it is good feature.

I run some simple tests.
I found the following problems.

(1)
Failed to "make world".
I think there are extra "<lineitem>" in doc/src/sgml/ref/create_materialized_view.sgml
(line 110 and 117)

(2)
In the case of partition, it seems that IVM does not work well.
I run as follows.

postgres=# create table parent (c int) partition by range (c);
CREATE TABLE
postgres=# create table child partition of parent for values from (1) to (100);
CREATE TABLE
postgres=# create incremental materialized view ivm_parent as select c from parent;
NOTICE: could not create an index on materialized view "ivm_parent" automatically
HINT: Create an index on the materialized view for efficient incremental maintenance.
SELECT 0
postgres=# create incremental materialized view ivm_child as select c from child;
NOTICE: could not create an index on materialized view "ivm_child" automatically
HINT: Create an index on the materialized view for efficient incremental maintenance.
SELECT 0
postgres=# insert into parent values (1);
INSERT 0 1
postgres=# insert into child values (2);
INSERT 0 1
postgres=# select * from parent;
c
---
1
2
(2 rows)

postgres=# select * from child;
c
---
1
2
(2 rows)

postgres=# select * from ivm_parent;
c
---
1
(1 row)

postgres=# select * from ivm_child;
c
---
2
(1 row)

I think ivm_parent and ivm_child should return 2 rows.

(3)
I think IVM does not support foreign table, but try to make IVM.

postgres=# create incremental materialized view ivm_foreign as select c from foreign_table;
NOTICE: could not create an index on materialized view "ivm_foreign" automatically
HINT: Create an index on the materialized view for efficient incremental maintenance.
ERROR: "foreign_table" is a foreign table
DETAIL: Triggers on foreign tables cannot have transition tables.

It finally failed to make IVM, but I think it should be checked more early.

Regards,
Ryohei Takahashi

#191Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Zhihong Yu (#189)
Re: Implementing Incremental View Maintenance

Hello Zhihong Yu,

On Mon, 2 Aug 2021 14:33:46 -0700
Zhihong Yu <zyu@yugabyte.com> wrote:

On Sun, Aug 1, 2021 at 11:30 PM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi hackers,

On Mon, 19 Jul 2021 09:24:30 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Wed, 14 Jul 2021 21:22:37 +0530
vignesh C <vignesh21@gmail.com> wrote:

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

Ok. I'll update the patch in a few days.

Attached is the latest patch set to add support for Incremental
Materialized View Maintenance (IVM)

The patches are rebased to the master and also revised with some
code cleaning.

IVM is a way to make materialized views up-to-date in which only
incremental changes are computed and applied on views rather than
recomputing the contents from scratch as REFRESH MATERIALIZED VIEW
does. IVM can update materialized views more efficiently
than recomputation when only small part of the view need updates.

The patch set implements a feature so that materialized views could be
updated automatically and immediately when a base table is modified.

Currently, our IVM implementation supports views which could contain
tuple duplicates whose definition includes:

- inner and outer joins including self-join
- DISTINCT
- some built-in aggregate functions (count, sum, agv, min, and max)
- a part of subqueries
-- simple subqueries in FROM clause
-- EXISTS subqueries in WHERE clause
- CTEs

We hope the IVM feature would be adopted into pg15. However, the size of
patch set has grown too large through supporting above features.
Therefore,
I think it is better to consider only a part of these features for the
first
release. Especially, I would like propose the following features for pg15.

- inner joins including self-join
- DISTINCT and views with tuple duplicates
- some built-in aggregate functions (count, sum, agv, min, and max)

By omitting outer-join, sub-queries, and CTE features, the patch size
becomes
less than half. I hope this will make a bit easer to review the IVM patch
set.

Here is a list of separated patches.

- 0001: Add a new syntax:
CREATE INCREMENTAL MATERIALIZED VIEW
- 0002: Add a new column relisivm to pg_class
- 0003: Add new deptype option 'm' in pg_depend
- 0004: Change trigger.c to allow to prolong life span of tupestores
containing Transition Tables generated via AFTER trigger
- 0005: Add IVM supprot for pg_dump
- 0006: Add IVM support for psql
- 0007: Add the basic IVM future:
This supports inner joins, DISTINCT, and tuple duplicates.
- 0008: Add aggregates (count, sum, avg, min, max) support for IVM
- 0009: Add regression tests for IVM
- 0010: Add documentation for IVM

We could split the patch furthermore if this would make reviews much
easer.
For example, I think 0007 could be split into the more basic part and the
part
for handling tuple duplicates. Moreover, 0008 could be split into "min/max"
and other aggregates because handling min/max is a bit more complicated
than
others.

I also attached IVM_extra.tar.gz that contains patches for sub-quereis,
outer-join, CTE support, just for your information.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

Hi,
For v23-0008-Add-aggregates-support-in-IVM.patch :

Thank you for looking into this!

As a restriction, expressions specified in GROUP BY must appear in
the target list because tuples to be updated in IMMV are identified
by using this group keys.

IMMV -> IMVM (Incremental Materialized View Maintenance, as said above)
Or maybe it means 'incrementally maintainable materialized view'. It would
be better to use the same abbreviation.

IMMV is correct in the commit message of this patch. Rather, IMVM used
in v23-0003-Add-new-deptype-option-m-in-pg_depend-system-cat.patch
should be corrected to IMMV.

this group keys -> this group key

+ errmsg("GROUP BY expression not appeared in select
list is not supported on incrementally maintainable materialized view")));

expression not appeared in select list -> expression not appearing in
select list

+ * For aggregate functions except to count

except to count -> except count

Thank you for pointing out them. I'll fix.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#192Yugo NAGATA
nagata@sraoss.co.jp
In reply to: r.takahashi_2@fujitsu.com (#190)
Re: Implementing Incremental View Maintenance

Hello Takahashi-san,

On Tue, 3 Aug 2021 10:15:42 +0000
"r.takahashi_2@fujitsu.com" <r.takahashi_2@fujitsu.com> wrote:

Hi Nagata-san,

I am interested in this patch since it is good feature.

I run some simple tests.
I found the following problems.

Thank you for your interest for this patch!

(1)
Failed to "make world".
I think there are extra "<lineitem>" in doc/src/sgml/ref/create_materialized_view.sgml
(line 110 and 117)

Oops. I'll fix it.

(2)
In the case of partition, it seems that IVM does not work well.
I run as follows.

postgres=# create table parent (c int) partition by range (c);
CREATE TABLE
postgres=# create table child partition of parent for values from (1) to (100);
CREATE TABLE
postgres=# create incremental materialized view ivm_parent as select c from parent;
NOTICE: could not create an index on materialized view "ivm_parent" automatically
HINT: Create an index on the materialized view for efficient incremental maintenance.
SELECT 0
postgres=# create incremental materialized view ivm_child as select c from child;
NOTICE: could not create an index on materialized view "ivm_child" automatically
HINT: Create an index on the materialized view for efficient incremental maintenance.
SELECT 0
postgres=# insert into parent values (1);
INSERT 0 1
postgres=# insert into child values (2);
INSERT 0 1
postgres=# select * from parent;
c
---
1
2
(2 rows)

postgres=# select * from child;
c
---
1
2
(2 rows)

postgres=# select * from ivm_parent;
c
---
1
(1 row)

postgres=# select * from ivm_child;
c
---
2
(1 row)

I think ivm_parent and ivm_child should return 2 rows.

Good point!
I'll investigate this more, but we may have to prohibit views on partitioned
table and partitions.

(3)
I think IVM does not support foreign table, but try to make IVM.

postgres=# create incremental materialized view ivm_foreign as select c from foreign_table;
NOTICE: could not create an index on materialized view "ivm_foreign" automatically
HINT: Create an index on the materialized view for efficient incremental maintenance.
ERROR: "foreign_table" is a foreign table
DETAIL: Triggers on foreign tables cannot have transition tables.

It finally failed to make IVM, but I think it should be checked more early.

You are right. We don't support foreign tables as long as we use triggers.

I'll fix.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#193r.takahashi_2@fujitsu.com
r.takahashi_2@fujitsu.com
In reply to: Yugo NAGATA (#192)
RE: Implementing Incremental View Maintenance

Hi Nagata-san,

Thank you for your reply.

I'll investigate this more, but we may have to prohibit views on partitioned
table and partitions.

I think this restriction is strict.
This feature is useful when the base table is large and partitioning is also useful in such case.

I have several additional comments on the patch.

(1)
The following features are added to transition table.
- Prolong lifespan of transition table
- If table has row security policies, set them to the transition table
- Calculate pre-state of the table

Are these features only for IVM?
If there are other useful case, they should be separated from IVM patch and
should be independent patch for transition table.

(2)
DEPENDENCY_IMMV (m) is added to deptype of pg_depend.
What is the difference compared with existing deptype such as DEPENDENCY_INTERNAL (i)?

(3)
Converting from normal materialized view to IVM or from IVM to normal materialized view is not implemented yet.
Is it difficult?

I think create/drop triggers and __ivm_ columns can achieve this feature.

Regards,
Ryohei Takahashi

#194Zhihong Yu
zyu@yugabyte.com
In reply to: Yugo NAGATA (#188)
Re: Implementing Incremental View Maintenance

On Sun, Aug 1, 2021 at 11:30 PM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi hackers,

On Mon, 19 Jul 2021 09:24:30 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Wed, 14 Jul 2021 21:22:37 +0530
vignesh C <vignesh21@gmail.com> wrote:

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

Ok. I'll update the patch in a few days.

Attached is the latest patch set to add support for Incremental
Materialized View Maintenance (IVM)

The patches are rebased to the master and also revised with some
code cleaning.

IVM is a way to make materialized views up-to-date in which only
incremental changes are computed and applied on views rather than
recomputing the contents from scratch as REFRESH MATERIALIZED VIEW
does. IVM can update materialized views more efficiently
than recomputation when only small part of the view need updates.

The patch set implements a feature so that materialized views could be
updated automatically and immediately when a base table is modified.

Currently, our IVM implementation supports views which could contain
tuple duplicates whose definition includes:

- inner and outer joins including self-join
- DISTINCT
- some built-in aggregate functions (count, sum, agv, min, and max)
- a part of subqueries
-- simple subqueries in FROM clause
-- EXISTS subqueries in WHERE clause
- CTEs

We hope the IVM feature would be adopted into pg15. However, the size of
patch set has grown too large through supporting above features.
Therefore,
I think it is better to consider only a part of these features for the
first
release. Especially, I would like propose the following features for pg15.

- inner joins including self-join
- DISTINCT and views with tuple duplicates
- some built-in aggregate functions (count, sum, agv, min, and max)

By omitting outer-join, sub-queries, and CTE features, the patch size
becomes
less than half. I hope this will make a bit easer to review the IVM patch
set.

Here is a list of separated patches.

- 0001: Add a new syntax:
CREATE INCREMENTAL MATERIALIZED VIEW
- 0002: Add a new column relisivm to pg_class
- 0003: Add new deptype option 'm' in pg_depend
- 0004: Change trigger.c to allow to prolong life span of tupestores
containing Transition Tables generated via AFTER trigger
- 0005: Add IVM supprot for pg_dump
- 0006: Add IVM support for psql
- 0007: Add the basic IVM future:
This supports inner joins, DISTINCT, and tuple duplicates.
- 0008: Add aggregates (count, sum, avg, min, max) support for IVM
- 0009: Add regression tests for IVM
- 0010: Add documentation for IVM

We could split the patch furthermore if this would make reviews much
easer.
For example, I think 0007 could be split into the more basic part and the
part
for handling tuple duplicates. Moreover, 0008 could be split into "min/max"
and other aggregates because handling min/max is a bit more complicated
than
others.

I also attached IVM_extra.tar.gz that contains patches for sub-quereis,
outer-join, CTE support, just for your information.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

Hi,
For v23-0007-Add-Incremental-View-Maintenance-support.patch :

bq. In this implementation, AFTER triggers are used to collecting
tuplestores

'to collecting' -> to collect

bq. are contained in a old transition table.

'a old' -> an old

bq. updates of more than one base tables

one base tables -> one base table

bq. DISTINCT and tuple duplicates in views are supported

Since distinct and duplicate have opposite meanings, it would be better to
rephrase the above sentence.

bq. The value in__ivm_count__ is updated

I searched the patch for in__ivm_count__ - there was no (second) match. I
think there should be a space between in and underscore.

+static void CreateIvmTriggersOnBaseTables_recurse(Query *qry, Node *node,
Oid matviewOid, Relids *relids, bool ex_lock);

nit: long line. please wrap.

+   if (rewritten->distinctClause)
+       rewritten->groupClause = transformDistinctClause(NULL,
&rewritten->targetList, rewritten->sortClause, false);
+
+   /* Add count(*) for counting distinct tuples in views */
+   if (rewritten->distinctClause)

It seems the body of the two if statements can be combined into one.

More to follow for this patch.

Cheers

#195Zhihong Yu
zyu@yugabyte.com
In reply to: Zhihong Yu (#194)
Re: Implementing Incremental View Maintenance

On Sat, Aug 7, 2021 at 12:00 AM Zhihong Yu <zyu@yugabyte.com> wrote:

On Sun, Aug 1, 2021 at 11:30 PM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi hackers,

On Mon, 19 Jul 2021 09:24:30 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Wed, 14 Jul 2021 21:22:37 +0530
vignesh C <vignesh21@gmail.com> wrote:

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

Ok. I'll update the patch in a few days.

Attached is the latest patch set to add support for Incremental
Materialized View Maintenance (IVM)

The patches are rebased to the master and also revised with some
code cleaning.

IVM is a way to make materialized views up-to-date in which only
incremental changes are computed and applied on views rather than
recomputing the contents from scratch as REFRESH MATERIALIZED VIEW
does. IVM can update materialized views more efficiently
than recomputation when only small part of the view need updates.

The patch set implements a feature so that materialized views could be
updated automatically and immediately when a base table is modified.

Currently, our IVM implementation supports views which could contain
tuple duplicates whose definition includes:

- inner and outer joins including self-join
- DISTINCT
- some built-in aggregate functions (count, sum, agv, min, and max)
- a part of subqueries
-- simple subqueries in FROM clause
-- EXISTS subqueries in WHERE clause
- CTEs

We hope the IVM feature would be adopted into pg15. However, the size of
patch set has grown too large through supporting above features.
Therefore,
I think it is better to consider only a part of these features for the
first
release. Especially, I would like propose the following features for pg15.

- inner joins including self-join
- DISTINCT and views with tuple duplicates
- some built-in aggregate functions (count, sum, agv, min, and max)

By omitting outer-join, sub-queries, and CTE features, the patch size
becomes
less than half. I hope this will make a bit easer to review the IVM patch
set.

Here is a list of separated patches.

- 0001: Add a new syntax:
CREATE INCREMENTAL MATERIALIZED VIEW
- 0002: Add a new column relisivm to pg_class
- 0003: Add new deptype option 'm' in pg_depend
- 0004: Change trigger.c to allow to prolong life span of tupestores
containing Transition Tables generated via AFTER trigger
- 0005: Add IVM supprot for pg_dump
- 0006: Add IVM support for psql
- 0007: Add the basic IVM future:
This supports inner joins, DISTINCT, and tuple duplicates.
- 0008: Add aggregates (count, sum, avg, min, max) support for IVM
- 0009: Add regression tests for IVM
- 0010: Add documentation for IVM

We could split the patch furthermore if this would make reviews much
easer.
For example, I think 0007 could be split into the more basic part and the
part
for handling tuple duplicates. Moreover, 0008 could be split into
"min/max"
and other aggregates because handling min/max is a bit more complicated
than
others.

I also attached IVM_extra.tar.gz that contains patches for sub-quereis,
outer-join, CTE support, just for your information.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

Hi,
For v23-0007-Add-Incremental-View-Maintenance-support.patch :

bq. In this implementation, AFTER triggers are used to collecting
tuplestores

'to collecting' -> to collect

bq. are contained in a old transition table.

'a old' -> an old

bq. updates of more than one base tables

one base tables -> one base table

bq. DISTINCT and tuple duplicates in views are supported

Since distinct and duplicate have opposite meanings, it would be better to
rephrase the above sentence.

bq. The value in__ivm_count__ is updated

I searched the patch for in__ivm_count__ - there was no (second) match. I
think there should be a space between in and underscore.

+static void CreateIvmTriggersOnBaseTables_recurse(Query *qry, Node *node,
Oid matviewOid, Relids *relids, bool ex_lock);

nit: long line. please wrap.

+   if (rewritten->distinctClause)
+       rewritten->groupClause = transformDistinctClause(NULL,
&rewritten->targetList, rewritten->sortClause, false);
+
+   /* Add count(*) for counting distinct tuples in views */
+   if (rewritten->distinctClause)

It seems the body of the two if statements can be combined into one.

More to follow for this patch.

Cheers

Hi,

+ CreateIvmTriggersOnBaseTables_recurse(qry, (Node *)qry, matviewOid,
&relids, ex_lock);

Looking at existing recursive functions, e.g.

src/backend/executor/execPartition.c:find_matching_subplans_recurse(PartitionPruningData
*prunedata,

the letters in the function name are all lower case. I think following the
convention would be nice.

+               if (rte->rtekind == RTE_RELATION)
+               {
+                   if (!bms_is_member(rte->relid, *relids))

The conditions for the two if statements can be combined (saving some
indentation).

+   check_stack_depth();
+
+   if (node == NULL)
+       return false;

It seems the node check can be placed ahead of the stack depth check.

+ * CreateindexOnIMMV

CreateindexOnIMMV -> CreateIndexOnIMMV

+ (errmsg("could not create an index on materialized view
\"%s\" automatically",

It would be nice to mention the reason is the lack of primary key.

+ /* create no index, just notice that an appropriate index is
necessary for efficient, IVM */

for efficient -> for efficiency.

Cheers

#196r.takahashi_2@fujitsu.com
r.takahashi_2@fujitsu.com
In reply to: Zhihong Yu (#195)
RE: Implementing Incremental View Maintenance

Hi Nagata-san,

I'm still reading the patch.
I have additional comments.

(1)
In v23-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patch, ivm member is added to IntoClause struct.
I think it is necessary to modify _copyIntoClause() and _equalIntoClause() functions.

(2)
By executing pg_dump with v23-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patch,
the constraint which is automatically created during "CREATE INCREMENTAL MATERIALIZED VIEW" is also dumped.
This cause error during recovery as follows.

ivm=# create table t (c1 int, c2 int);
CREATE TABLE
ivm=# create incremental materialized view ivm_t as select distinct c1 from t;
NOTICE: created index "ivm_t_index" on materialized view "ivm_t"
SELECT 0

Then I executed pg_dump.

In the dump, the following SQLs appear.

CREATE INCREMENTAL MATERIALIZED VIEW public.ivm_t AS
SELECT DISTINCT t.c1
FROM public.t
WITH NO DATA;

ALTER TABLE ONLY public.ivm_t
ADD CONSTRAINT ivm_t_index UNIQUE (c1);

If I execute psql with the result of pg_dump, following error occurs.

ERROR: ALTER action ADD CONSTRAINT cannot be performed on relation "ivm_t"
DETAIL: This operation is not supported for materialized views.

Regards,
Ryohei Takahashi

#197Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Zhihong Yu (#195)
Re: Implementing Incremental View Maintenance

Hello Zhihong Yu,

Thank you for your suggestion!

I am sorry for late replay. I'll fix them and submit the
updated patch soon.

On Sat, 7 Aug 2021 00:52:24 -0700
Zhihong Yu <zyu@yugabyte.com> wrote:

Hi,
For v23-0007-Add-Incremental-View-Maintenance-support.patch :

bq. In this implementation, AFTER triggers are used to collecting
tuplestores

'to collecting' -> to collect

bq. are contained in a old transition table.

'a old' -> an old

bq. updates of more than one base tables

one base tables -> one base table

I'll fix them.

bq. DISTINCT and tuple duplicates in views are supported

Since distinct and duplicate have opposite meanings, it would be better to
rephrase the above sentence.

I'll rewrite it to
"Incrementally Maintainable Materialized Views (IMMV) can contain
duplicated tuples. Also, DISTINCT clause is supported. "

bq. The value in__ivm_count__ is updated

I searched the patch for in__ivm_count__ - there was no (second) match. I
think there should be a space between in and underscore.

Yes, the space was missing.

+static void CreateIvmTriggersOnBaseTables_recurse(Query *qry, Node *node,
Oid matviewOid, Relids *relids, bool ex_lock);

nit: long line. please wrap.

OK.

+   if (rewritten->distinctClause)
+       rewritten->groupClause = transformDistinctClause(NULL,
&rewritten->targetList, rewritten->sortClause, false);
+
+   /* Add count(*) for counting distinct tuples in views */
+   if (rewritten->distinctClause)

It seems the body of the two if statements can be combined into one.

Ok.

+ CreateIvmTriggersOnBaseTables_recurse(qry, (Node *)qry, matviewOid,
&relids, ex_lock);

Looking at existing recursive functions, e.g.

src/backend/executor/execPartition.c:find_matching_subplans_recurse(PartitionPruningData
*prunedata,

the letters in the function name are all lower case. I think following the
convention would be nice.

Ok. I'll rename this to CreateIvmTriggersOnBaseTablesRecurse since I found
DeadLockCheckRecurse, transformExprRecurse, and so on.

+               if (rte->rtekind == RTE_RELATION)
+               {
+                   if (!bms_is_member(rte->relid, *relids))

The conditions for the two if statements can be combined (saving some
indentation).

Yes. I'll fix.

+   check_stack_depth();
+
+   if (node == NULL)
+       return false;

It seems the node check can be placed ahead of the stack depth check.

OK.

+ * CreateindexOnIMMV

CreateindexOnIMMV -> CreateIndexOnIMMV

+ (errmsg("could not create an index on materialized view
\"%s\" automatically",

It would be nice to mention the reason is the lack of primary key.

+ /* create no index, just notice that an appropriate index is
necessary for efficient, IVM */

for efficient -> for efficiency.

I'll fix them. Thanks.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#198Yugo NAGATA
nagata@sraoss.co.jp
In reply to: r.takahashi_2@fujitsu.com (#193)
Re: Implementing Incremental View Maintenance

Hello Takahashi-san,

On Thu, 5 Aug 2021 08:53:47 +0000
"r.takahashi_2@fujitsu.com" <r.takahashi_2@fujitsu.com> wrote:

Hi Nagata-san,

Thank you for your reply.

I'll investigate this more, but we may have to prohibit views on partitioned
table and partitions.

I think this restriction is strict.
This feature is useful when the base table is large and partitioning is also useful in such case.

One reason of this issue is the lack of triggers on partitioned tables or partitions that
are not specified in the view definition.

However, even if we create triggers recursively on the parents or children, we would still
need more consideration. This is because we will have to convert the format of tuple of
modified table to the format of the table specified in the view for cases that the parent
and some children have different format.

I think supporting partitioned tables can be left for the next release.

I have several additional comments on the patch.

(1)
The following features are added to transition table.
- Prolong lifespan of transition table
- If table has row security policies, set them to the transition table
- Calculate pre-state of the table

Are these features only for IVM?
If there are other useful case, they should be separated from IVM patch and
should be independent patch for transition table.

Maybe. However, we don't have good idea about use cases other than IVM of
them for now...

(2)
DEPENDENCY_IMMV (m) is added to deptype of pg_depend.
What is the difference compared with existing deptype such as DEPENDENCY_INTERNAL (i)?

DEPENDENCY_IMMV was added to clear that a certain trigger is related to IMMV.
We dropped the IVM trigger and its dependencies from IMMV when REFRESH ... WITH NO DATA
is executed. Without the new deptype, we may accidentally delete a dependency created
with an intention other than the IVM trigger.

(3)
Converting from normal materialized view to IVM or from IVM to normal materialized view is not implemented yet.
Is it difficult?

I think create/drop triggers and __ivm_ columns can achieve this feature.

I think it is harder than you expected. When an IMMV is switched to a normal
materialized view, we needs to drop hidden columns (__ivm_count__ etc.), and in
the opposite case, we need to create them again. The former (IMMV->IVM) might be
easer, but for the latter (IVM->IMMV) I wonder we would need to re-create IMMV.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#199Yugo NAGATA
nagata@sraoss.co.jp
In reply to: r.takahashi_2@fujitsu.com (#196)
Re: Implementing Incremental View Maintenance

Hello Takahashi-san,

On Mon, 6 Sep 2021 10:06:37 +0000
"r.takahashi_2@fujitsu.com" <r.takahashi_2@fujitsu.com> wrote:

Hi Nagata-san,

I'm still reading the patch.
I have additional comments.

Thank you for your comments!

(1)
In v23-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patch, ivm member is added to IntoClause struct.
I think it is necessary to modify _copyIntoClause() and _equalIntoClause() functions.

Ok. I'll fix _copyIntoClause() and _equalIntoClause() as well as _readIntoClause() and _outIntoClause().

(2)
By executing pg_dump with v23-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patch,
the constraint which is automatically created during "CREATE INCREMENTAL MATERIALIZED VIEW" is also dumped.
This cause error during recovery as follows.

ivm=# create table t (c1 int, c2 int);
CREATE TABLE
ivm=# create incremental materialized view ivm_t as select distinct c1 from t;
NOTICE: created index "ivm_t_index" on materialized view "ivm_t"
SELECT 0

Then I executed pg_dump.

In the dump, the following SQLs appear.

CREATE INCREMENTAL MATERIALIZED VIEW public.ivm_t AS
SELECT DISTINCT t.c1
FROM public.t
WITH NO DATA;

ALTER TABLE ONLY public.ivm_t
ADD CONSTRAINT ivm_t_index UNIQUE (c1);

If I execute psql with the result of pg_dump, following error occurs.

ERROR: ALTER action ADD CONSTRAINT cannot be performed on relation "ivm_t"
DETAIL: This operation is not supported for materialized views.

Good catch! It was my mistake creating unique constraints on IMMV in spite of
we cannot defined them via SQL. I'll fix it to use unique indexes instead of
constraints.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#200Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#199)
1 attachment(s)
Re: Implementing Incremental View Maintenance

Hi hackers,

I attached the updated patch including fixes reported by
Zhihong Yu and Ryohei Takahashi.

Regards,
Yugo Nagata

On Wed, 22 Sep 2021 19:12:27 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hello Takahashi-san,

On Mon, 6 Sep 2021 10:06:37 +0000
"r.takahashi_2@fujitsu.com" <r.takahashi_2@fujitsu.com> wrote:

Hi Nagata-san,

I'm still reading the patch.
I have additional comments.

Thank you for your comments!

(1)
In v23-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patch, ivm member is added to IntoClause struct.
I think it is necessary to modify _copyIntoClause() and _equalIntoClause() functions.

Ok. I'll fix _copyIntoClause() and _equalIntoClause() as well as _readIntoClause() and _outIntoClause().

(2)
By executing pg_dump with v23-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patch,
the constraint which is automatically created during "CREATE INCREMENTAL MATERIALIZED VIEW" is also dumped.
This cause error during recovery as follows.

ivm=# create table t (c1 int, c2 int);
CREATE TABLE
ivm=# create incremental materialized view ivm_t as select distinct c1 from t;
NOTICE: created index "ivm_t_index" on materialized view "ivm_t"
SELECT 0

Then I executed pg_dump.

In the dump, the following SQLs appear.

CREATE INCREMENTAL MATERIALIZED VIEW public.ivm_t AS
SELECT DISTINCT t.c1
FROM public.t
WITH NO DATA;

ALTER TABLE ONLY public.ivm_t
ADD CONSTRAINT ivm_t_index UNIQUE (c1);

If I execute psql with the result of pg_dump, following error occurs.

ERROR: ALTER action ADD CONSTRAINT cannot be performed on relation "ivm_t"
DETAIL: This operation is not supported for materialized views.

Good catch! It was my mistake creating unique constraints on IMMV in spite of
we cannot defined them via SQL. I'll fix it to use unique indexes instead of
constraints.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

IVM_patches_v24.tar.gzapplication/gzip; name=IVM_patches_v24.tar.gzDownload
#201Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#200)
10 attachment(s)
Re: Implementing Incremental View Maintenance

On Wed, 22 Sep 2021 19:17:12 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi hackers,

I attached the updated patch including fixes reported by
Zhihong Yu and Ryohei Takahashi.

Cfbot seems to fail to open the tar file, so I attached
patch files instead of tar ball.

Regards,
Yugo Nagata

On Wed, 22 Sep 2021 19:12:27 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hello Takahashi-san,

On Mon, 6 Sep 2021 10:06:37 +0000
"r.takahashi_2@fujitsu.com" <r.takahashi_2@fujitsu.com> wrote:

Hi Nagata-san,

I'm still reading the patch.
I have additional comments.

Thank you for your comments!

(1)
In v23-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patch, ivm member is added to IntoClause struct.
I think it is necessary to modify _copyIntoClause() and _equalIntoClause() functions.

Ok. I'll fix _copyIntoClause() and _equalIntoClause() as well as _readIntoClause() and _outIntoClause().

(2)
By executing pg_dump with v23-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patch,
the constraint which is automatically created during "CREATE INCREMENTAL MATERIALIZED VIEW" is also dumped.
This cause error during recovery as follows.

ivm=# create table t (c1 int, c2 int);
CREATE TABLE
ivm=# create incremental materialized view ivm_t as select distinct c1 from t;
NOTICE: created index "ivm_t_index" on materialized view "ivm_t"
SELECT 0

Then I executed pg_dump.

In the dump, the following SQLs appear.

CREATE INCREMENTAL MATERIALIZED VIEW public.ivm_t AS
SELECT DISTINCT t.c1
FROM public.t
WITH NO DATA;

ALTER TABLE ONLY public.ivm_t
ADD CONSTRAINT ivm_t_index UNIQUE (c1);

If I execute psql with the result of pg_dump, following error occurs.

ERROR: ALTER action ADD CONSTRAINT cannot be performed on relation "ivm_t"
DETAIL: This operation is not supported for materialized views.

Good catch! It was my mistake creating unique constraints on IMMV in spite of
we cannot defined them via SQL. I'll fix it to use unique indexes instead of
constraints.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

v24-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patchtext/x-diff; name=v24-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patchDownload
From 8f31d4e9030bf200f96025710fc55727c79c862f Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:05:02 +0900
Subject: [PATCH v24 01/15] Add a syntax to create Incrementally Maintainable
 Materialized Views

Allow to create Incrementally Maintainable Materialized View (IMMV)
by using INCREMENTAL option in CREATE MATERIALIZED VIEW command
as follow:

     CREATE [INCREMANTAL] MATERIALIZED VIEW xxxxx AS SELECT ....;
---
 src/backend/nodes/copyfuncs.c  |  1 +
 src/backend/nodes/equalfuncs.c |  1 +
 src/backend/nodes/outfuncs.c   |  1 +
 src/backend/nodes/readfuncs.c  |  1 +
 src/backend/parser/gram.y      | 32 +++++++++++++++++++++-----------
 src/include/nodes/primnodes.h  |  1 +
 src/include/parser/kwlist.h    |  1 +
 7 files changed, 27 insertions(+), 11 deletions(-)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 228387eaee..17be377aa7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1423,6 +1423,7 @@ _copyIntoClause(const IntoClause *from)
 	COPY_STRING_FIELD(tableSpaceName);
 	COPY_NODE_FIELD(viewQuery);
 	COPY_SCALAR_FIELD(skipData);
+	COPY_SCALAR_FIELD(ivm);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 800f588b5c..ed74a5022c 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -155,6 +155,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
 	COMPARE_STRING_FIELD(tableSpaceName);
 	COMPARE_NODE_FIELD(viewQuery);
 	COMPARE_SCALAR_FIELD(skipData);
+	COMPARE_SCALAR_FIELD(ivm);
 
 	return true;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2e5ed77e18..f6166f7859 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1116,6 +1116,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
 	WRITE_STRING_FIELD(tableSpaceName);
 	WRITE_NODE_FIELD(viewQuery);
 	WRITE_BOOL_FIELD(skipData);
+	WRITE_BOOL_FIELD(ivm);
 }
 
 static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index abf08b7a2f..1e55a58f69 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -565,6 +565,7 @@ _readIntoClause(void)
 	READ_STRING_FIELD(tableSpaceName);
 	READ_NODE_FIELD(viewQuery);
 	READ_BOOL_FIELD(skipData);
+	READ_BOOL_FIELD(ivm);
 
 	READ_DONE();
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e3068a374e..46a812e550 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -439,6 +439,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target create_mv_target
+%type <boolean>	incremental
 
 %type <defelt>	createfunc_opt_item common_func_opt_item dostmt_opt_item
 %type <fun_param> func_arg func_arg_with_default table_func_column aggr_arg
@@ -668,7 +669,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
 	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
-	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+	INCLUDING INCREMENT INCREMENTAL INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
 	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
 	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -4264,30 +4265,32 @@ opt_with_data:
  *****************************************************************************/
 
 CreateMatViewStmt:
-		CREATE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
+		CREATE OptNoLog incremental MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $7;
-					ctas->into = $5;
+					ctas->query = $8;
+					ctas->into = $6;
 					ctas->objtype = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = false;
 					/* cram additional flags into the IntoClause */
-					$5->rel->relpersistence = $2;
-					$5->skipData = !($8);
+					$6->rel->relpersistence = $2;
+					$6->skipData = !($9);
+					$6->ivm = $3;
 					$$ = (Node *) ctas;
 				}
-		| CREATE OptNoLog MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
+		| CREATE OptNoLog incremental MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $10;
-					ctas->into = $8;
+					ctas->query = $11;
+					ctas->into = $9;
 					ctas->objtype = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = true;
 					/* cram additional flags into the IntoClause */
-					$8->rel->relpersistence = $2;
-					$8->skipData = !($11);
+					$9->rel->relpersistence = $2;
+					$9->skipData = !($12);
+					$9->ivm = $3;
 					$$ = (Node *) ctas;
 				}
 		;
@@ -4304,9 +4307,14 @@ create_mv_target:
 					$$->tableSpaceName = $5;
 					$$->viewQuery = NULL;		/* filled at analysis time */
 					$$->skipData = false;		/* might get changed later */
+					$$->ivm = false;
 				}
 		;
 
+incremental:	INCREMENTAL				{ $$ = true; }
+				| /*EMPTY*/				{ $$ = false; }
+		;
+
 OptNoLog:	UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
 		;
@@ -15606,6 +15614,7 @@ unreserved_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
@@ -16158,6 +16167,7 @@ bare_label_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 433437643e..7ea80c7ded 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -117,6 +117,7 @@ typedef struct IntoClause
 	char	   *tableSpaceName; /* table space to use, or NULL */
 	Node	   *viewQuery;		/* materialized view's SELECT query */
 	bool		skipData;		/* true for WITH NO DATA */
+	bool		ivm;			/* true for WITH IVM */
 } IntoClause;
 
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf876..2cafb4e7fe 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -205,6 +205,7 @@ PG_KEYWORD("in", IN_P, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("incremental", INCREMENTAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD, BARE_LABEL)
-- 
2.17.1

v24-0002-Add-relisivm-column-to-pg_class-system-catalog.patchtext/x-diff; name=v24-0002-Add-relisivm-column-to-pg_class-system-catalog.patchDownload
From 07dba8ba54ddf0ed30ae516f0fa8848d694da32f Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:07:23 +0900
Subject: [PATCH v24 02/15] Add relisivm column to pg_class system catalog

If this boolean column is true, a relations is Incrementally Maintainable
Materialized View (IMMV). This is set when IMMV is created.
---
 src/backend/catalog/heap.c          |  1 +
 src/backend/catalog/index.c         |  1 +
 src/backend/utils/cache/lsyscache.c | 24 ++++++++++++++++++++++++
 src/backend/utils/cache/relcache.c  |  2 ++
 src/include/catalog/pg_class.h      |  3 +++
 src/include/utils/lsyscache.h       |  1 +
 src/include/utils/rel.h             |  2 ++
 7 files changed, 34 insertions(+)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 83746d3fd9..ade3586779 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -963,6 +963,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
+	values[Anum_pg_class_relisivm - 1] = BoolGetDatum(rd_rel->relisivm);
 	if (relacl != (Datum) 0)
 		values[Anum_pg_class_relacl - 1] = relacl;
 	else
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 26bfa74ce7..763f442a1d 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -961,6 +961,7 @@ index_create(Relation heapRelation,
 	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
 	indexRelation->rd_rel->relam = accessMethodObjectId;
 	indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid);
+	indexRelation->rd_rel->relisivm = false;
 
 	/*
 	 * store index's pg_class entry
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 4ebaa552a2..c626df32cc 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2013,6 +2013,30 @@ get_rel_relispartition(Oid relid)
 		return false;
 }
 
+/*
+ * get_rel_relisivm
+ *
+ *		Returns the relisivm flag associated with a given relation.
+ */
+bool
+get_rel_relisivm(Oid relid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp);
+		bool		result;
+
+		result = reltup->relisivm;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return false;
+}
+
 /*
  * get_rel_tablespace
  *
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..5b130f0ed2 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1865,6 +1865,8 @@ formrdesc(const char *relationName, Oid relationReltype,
 
 	/* ... and they're always populated, too */
 	relation->rd_rel->relispopulated = true;
+	/* ... and they're always no ivm, too */
+	relation->rd_rel->relisivm = false;
 
 	relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
 	relation->rd_rel->relpages = 0;
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fef9945ed8..2058978b89 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -119,6 +119,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
 	/* is relation a partition? */
 	bool		relispartition BKI_DEFAULT(f);
 
+	/* is relation a matview with ivm? */
+	bool		relisivm BKI_DEFAULT(f);
+
 	/* link to original rel during table rewrite; otherwise 0 */
 	Oid			relrewrite BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_class);
 
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 77871aaefc..09346aaa17 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -137,6 +137,7 @@ extern Oid	get_rel_namespace(Oid relid);
 extern Oid	get_rel_type_id(Oid relid);
 extern char get_rel_relkind(Oid relid);
 extern bool get_rel_relispartition(Oid relid);
+extern bool get_rel_relisivm(Oid relid);
 extern Oid	get_rel_tablespace(Oid relid);
 extern char get_rel_persistence(Oid relid);
 extern Oid	get_transform_fromsql(Oid typid, Oid langid, List *trftypes);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b4faa1c123..b5028f3449 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -649,6 +649,8 @@ RelationGetSmgr(Relation rel)
  */
 #define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated)
 
+#define RelationIsIVM(relation) ((relation)->rd_rel->relisivm)
+
 /*
  * RelationIsAccessibleInLogicalDecoding
  *		True if we need to log enough information to have access via
-- 
2.17.1

v24-0003-Add-new-deptype-option-m-in-pg_depend-system-cat.patchtext/x-diff; name=v24-0003-Add-new-deptype-option-m-in-pg_depend-system-cat.patchDownload
From 3b06ef4601ba3238074d157a0ec57eca9af7e876 Mon Sep 17 00:00:00 2001
From: Takuma Hoshiai <takuma.hoshiai@gmail.com>
Date: Fri, 17 Jan 2020 16:04:14 +0900
Subject: [PATCH v24 03/15] Add new deptype option 'm' in pg_depend system
 catalog

The deptype option 'm' mean specific database obejects referenced Incrementally
Maintainable Materialized View(IMMV). If set NO DATA flag to IMVM, these
database objects must be dropped.
---
 src/backend/catalog/dependency.c | 2 ++
 src/include/catalog/dependency.h | 3 ++-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..0ff0cfaef0 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -596,6 +596,7 @@ findDependentObjects(const ObjectAddress *object,
 			case DEPENDENCY_NORMAL:
 			case DEPENDENCY_AUTO:
 			case DEPENDENCY_AUTO_EXTENSION:
+			case DEPENDENCY_IMMV:
 				/* no problem */
 				break;
 
@@ -913,6 +914,7 @@ findDependentObjects(const ObjectAddress *object,
 				subflags = DEPFLAG_AUTO;
 				break;
 			case DEPENDENCY_INTERNAL:
+			case DEPENDENCY_IMMV:
 				subflags = DEPFLAG_INTERNAL;
 				break;
 			case DEPENDENCY_PARTITION_PRI:
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..d89b5dd774 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -36,7 +36,8 @@ typedef enum DependencyType
 	DEPENDENCY_PARTITION_PRI = 'P',
 	DEPENDENCY_PARTITION_SEC = 'S',
 	DEPENDENCY_EXTENSION = 'e',
-	DEPENDENCY_AUTO_EXTENSION = 'x'
+	DEPENDENCY_AUTO_EXTENSION = 'x',
+	DEPENDENCY_IMMV = 'm'
 } DependencyType;
 
 /*
-- 
2.17.1

v24-0004-Allow-to-prolong-life-span-of-transition-tables-.patchtext/x-diff; name=v24-0004-Allow-to-prolong-life-span-of-transition-tables-.patchDownload
From 47df8f01157759ecec8138464ac6bd92fdf9009a Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:09:45 +0900
Subject: [PATCH v24 04/15] Allow to prolong life span of transition tables
 until transaction end

Originally, tuplestores of AFTER trigger's transition tables were
freed for each query depth. For our IVM implementation, we would like
to prolong life of the tuplestores because we have to preserve them
for a whole query assuming that some base tables might be changed
in some trigger functions.
---
 src/backend/commands/trigger.c | 80 +++++++++++++++++++++++++++++++++-
 src/include/commands/trigger.h |  2 +
 2 files changed, 80 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index d8890d2c74..c7a2bc3ed8 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3570,6 +3570,10 @@ typedef struct AfterTriggerEventList
  * end of the list, so it is relatively easy to discard them.  The event
  * list chunks themselves are stored in event_cxt.
  *
+ * prolonged_tuplestored is a list of transition table tuplestores whose
+ * life are prolonged to the end of the outmost query instead of each nested
+ * query.
+ *
  * query_depth is the current depth of nested AfterTriggerBeginQuery calls
  * (-1 when the stack is empty).
  *
@@ -3635,6 +3639,7 @@ typedef struct AfterTriggersData
 	SetConstraintState state;	/* the active S C state */
 	AfterTriggerEventList events;	/* deferred-event list */
 	MemoryContext event_cxt;	/* memory context for events, if any */
+	List   *prolonged_tuplestores;	/* list of prolonged tuplestores */
 
 	/* per-query-level data: */
 	AfterTriggersQueryData *query_stack;	/* array of structs shown below */
@@ -3670,6 +3675,7 @@ struct AfterTriggersTableData
 	bool		closed;			/* true when no longer OK to add tuples */
 	bool		before_trig_done;	/* did we already queue BS triggers? */
 	bool		after_trig_done;	/* did we already queue AS triggers? */
+	bool		prolonged;			/* are transition tables prolonged? */
 	AfterTriggerEventList after_trig_events;	/* if so, saved list pointer */
 	Tuplestorestate *old_tuplestore;	/* "old" transition table, if any */
 	Tuplestorestate *new_tuplestore;	/* "new" transition table, if any */
@@ -4448,6 +4454,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
 }
 
 
+/*
+ * SetTransitionTablePreserved
+ *
+ * Prolong lifespan of transition tables corresponding specified relid and
+ * command type to the end of the outmost query instead of each nested query.
+ * This enables to use nested AFTER trigger's transition tables from outer
+ * query's triggers.  Currently, only immediate incremental view maintenance
+ * uses this.
+ */
+void
+SetTransitionTablePreserved(Oid relid, CmdType cmdType)
+{
+	AfterTriggersTableData *table;
+	AfterTriggersQueryData *qs;
+	bool		found = false;
+	ListCell   *lc;
+
+	/* Check state, like AfterTriggerSaveEvent. */
+	if (afterTriggers.query_depth < 0)
+		elog(ERROR, "SetTransitionTablePreserved() called outside of query");
+
+	qs = &afterTriggers.query_stack[afterTriggers.query_depth];
+
+	foreach(lc, qs->tables)
+	{
+		table = (AfterTriggersTableData *) lfirst(lc);
+		if (table->relid == relid && table->cmdType == cmdType &&
+			table->closed)
+		{
+			table->prolonged = true;
+			found = true;
+		}
+	}
+
+	if (!found)
+		elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType);
+}
+
+
 /*
  * GetAfterTriggersTableData
  *
@@ -4642,6 +4687,7 @@ AfterTriggerBeginXact(void)
 	 */
 	afterTriggers.firing_counter = (CommandId) 1;	/* mustn't be 0 */
 	afterTriggers.query_depth = -1;
+	afterTriggers.prolonged_tuplestores = NIL;
 
 	/*
 	 * Verify that there is no leftover state remaining.  If these assertions
@@ -4802,11 +4848,29 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
 		ts = table->old_tuplestore;
 		table->old_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+		{
+			if (table->prolonged && afterTriggers.query_depth > 0)
+			{
+				MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext);
+				afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts);
+				MemoryContextSwitchTo(oldcxt);
+			}
+			else
+				tuplestore_end(ts);
+		}
 		ts = table->new_tuplestore;
 		table->new_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+		{
+			if (table->prolonged && afterTriggers.query_depth > 0)
+			{
+				MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext);
+				afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts);
+				MemoryContextSwitchTo(oldcxt);
+			}
+			else
+				tuplestore_end(ts);
+		}
 		if (table->storeslot)
 			ExecDropSingleTupleTableSlot(table->storeslot);
 	}
@@ -4818,6 +4882,18 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
 	 */
 	qs->tables = NIL;
 	list_free_deep(tables);
+
+	/* Release prolonged tuplestores at the end of the outmost query */
+	if (afterTriggers.query_depth == 0)
+	{
+		foreach(lc, afterTriggers.prolonged_tuplestores)
+		{
+			ts = (Tuplestorestate *) lfirst(lc);
+			if (ts)
+				tuplestore_end(ts);
+		}
+		afterTriggers.prolonged_tuplestores = NIL;
+	}
 }
 
 
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 9ef7f6d768..0cfb0b3c5c 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -251,6 +251,8 @@ extern void AfterTriggerEndSubXact(bool isCommit);
 extern void AfterTriggerSetState(ConstraintsSetStmt *stmt);
 extern bool AfterTriggerPendingOnRel(Oid relid);
 
+extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType);
+
 
 /*
  * in utils/adt/ri_triggers.c
-- 
2.17.1

v24-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patchtext/x-diff; name=v24-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patchDownload
From 49bbed9a8aaed56a5476d484201af6e03f731181 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Wed, 11 Nov 2020 17:01:25 +0900
Subject: [PATCH v24 05/15] Add Incremental View Maintenance support to pg_dump

Support CREATE INCREMENTAL MATERIALIZED VIEW syntax.
---
 src/bin/pg_dump/pg_dump.c        | 42 ++++++++++++++++++++++++--------
 src/bin/pg_dump/pg_dump.h        |  1 +
 src/bin/pg_dump/t/002_pg_dump.pl | 15 ++++++++++++
 3 files changed, 48 insertions(+), 10 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..4e3836af7c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6267,6 +6267,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_ispartition;
 	int			i_partbound;
 	int			i_amname;
+	int			i_isivm;
 
 	/*
 	 * Find all the tables and table-like objects.
@@ -6298,6 +6299,7 @@ getTables(Archive *fout, int *numTables)
 		char	   *ispartition = "false";
 		char	   *partbound = "NULL";
 		char	   *relhasoids = "c.relhasoids";
+		char	   *isivm = "false";
 
 		PQExpBuffer acl_subquery = createPQExpBuffer();
 		PQExpBuffer racl_subquery = createPQExpBuffer();
@@ -6325,6 +6327,10 @@ getTables(Archive *fout, int *numTables)
 		if (fout->remoteVersion >= 120000)
 			relhasoids = "'f'::bool";
 
+		/* The information about incremental view maintenance */
+		if (fout->remoteVersion >= 150000)
+			isivm = "c.relisivm";
+
 		/*
 		 * Left join to pick up dependency info linking sequences to their
 		 * owning column, if any (note this dependency is AUTO as of 8.2)
@@ -6383,7 +6389,8 @@ getTables(Archive *fout, int *numTables)
 						  "AS changed_acl, "
 						  "%s AS partkeydef, "
 						  "%s AS ispartition, "
-						  "%s AS partbound "
+						  "%s AS partbound, "
+						  "%s AS isivm "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -6412,6 +6419,7 @@ getTables(Archive *fout, int *numTables)
 						  partkeydef,
 						  ispartition,
 						  partbound,
+						  isivm,
 						  RELKIND_SEQUENCE,
 						  RELKIND_PARTITIONED_TABLE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
@@ -6465,7 +6473,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
-						  "NULL AS partbound "
+						  "NULL AS partbound, "
+						  "false AS isivm "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -6518,7 +6527,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
-						  "NULL AS partbound "
+						  "NULL AS partbound, "
+						  "false AS isivm "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -6571,7 +6581,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
-						  "NULL AS partbound "
+						  "NULL AS partbound, "
+						  "false AS isivm "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -6622,7 +6633,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
-						  "NULL AS partbound "
+						  "NULL AS partbound, "
+						  "false AS isivm "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -6671,7 +6683,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
-						  "NULL AS partbound "
+						  "NULL AS partbound, "
+						  "false AS isivm "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -6719,7 +6732,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
-						  "NULL AS partbound "
+						  "NULL AS partbound, "
+						  "false AS isivm "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -6767,7 +6781,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
-						  "NULL AS partbound "
+						  "NULL AS partbound, "
+						  "false AS isivm "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -6814,7 +6829,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
-						  "NULL AS partbound "
+						  "NULL AS partbound, "
+						  "false AS isivm "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -6886,6 +6902,7 @@ getTables(Archive *fout, int *numTables)
 	i_ispartition = PQfnumber(res, "ispartition");
 	i_partbound = PQfnumber(res, "partbound");
 	i_amname = PQfnumber(res, "amname");
+	i_isivm = PQfnumber(res, "isivm");
 
 	if (dopt->lockWaitTimeout)
 	{
@@ -6999,6 +7016,9 @@ getTables(Archive *fout, int *numTables)
 		/* foreign server */
 		tblinfo[i].foreign_server = atooid(PQgetvalue(res, i, i_foreignserver));
 
+		/* Incremental view maintenance information */
+		tblinfo[i].isivm = (strcmp(PQgetvalue(res, i, i_isivm), "t") == 0);
+
 		/*
 		 * Read-lock target tables to make sure they aren't DROPPED or altered
 		 * in schema before we get around to dumping them.
@@ -16014,9 +16034,11 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
-		appendPQExpBuffer(q, "CREATE %s%s %s",
+		appendPQExpBuffer(q, "CREATE %s%s%s %s",
 						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
 						  "UNLOGGED " : "",
+						  tbinfo->relkind == RELKIND_MATVIEW && tbinfo->isivm ?
+						  "INCREMENTAL " : "",
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..c271782817 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -300,6 +300,7 @@ typedef struct _tableInfo
 	bool		dummy_view;		/* view's real definition must be postponed */
 	bool		postponed_def;	/* matview must be postponed into post-data */
 	bool		ispartition;	/* is table a partition? */
+	bool		isivm;			/* is incrementally maintainable materialized view? */
 
 	/*
 	 * These fields are computed only if we decide the table is interesting
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..fb20699c8b 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2161,6 +2161,21 @@ my %tests = (
 		  { exclude_dump_test_schema => 1, no_toast_compression => 1, },
 	},
 
+	'CREATE MATERIALIZED VIEW matview_ivm' => {
+		create_order => 21,
+		create_sql   => 'CREATE INCREMENTAL MATERIALIZED VIEW dump_test.matview_ivm (col1) AS
+					   SELECT col1 FROM dump_test.test_table;',
+		regexp => qr/^
+			\QCREATE INCREMENTAL MATERIALIZED VIEW dump_test.matview_ivm AS\E
+			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QFROM dump_test.test_table\E
+			\n\s+\QWITH NO DATA;\E
+			/xm,
+		like =>
+		  { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, },
+	},
+
 	'CREATE POLICY p1 ON test_table' => {
 		create_order => 22,
 		create_sql   => 'CREATE POLICY p1 ON dump_test.test_table
-- 
2.17.1

v24-0006-Add-Incremental-View-Maintenance-support-to-psql.patchtext/x-diff; name=v24-0006-Add-Incremental-View-Maintenance-support-to-psql.patchDownload
From cd6508891794387b2388847867e2a9de855e323f Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:21:54 +0900
Subject: [PATCH v24 06/15] Add Incremental View Maintenance support to psql

Add tab completion and meta-command output for IVM.
---
 src/bin/psql/describe.c     | 32 +++++++++++++++++++++++++++++++-
 src/bin/psql/tab-complete.c | 14 +++++++++-----
 2 files changed, 40 insertions(+), 6 deletions(-)

diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 90ff649be7..48efc436cc 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1657,6 +1657,7 @@ describeOneTableDetails(const char *schemaname,
 		char		relpersistence;
 		char		relreplident;
 		char	   *relam;
+		bool		isivm;
 	}			tableinfo;
 	bool		show_column_details = false;
 
@@ -1669,7 +1670,26 @@ describeOneTableDetails(const char *schemaname,
 	initPQExpBuffer(&tmpbuf);
 
 	/* Get general table info */
-	if (pset.sversion >= 120000)
+	if (pset.sversion >= 150000)
+	{
+		printfPQExpBuffer(&buf,
+						  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+						  "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, "
+						  "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
+						  "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
+						  "c.relpersistence, c.relreplident, am.amname, "
+						  "c.relisivm\n"
+						  "FROM pg_catalog.pg_class c\n "
+						  "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+						  "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
+						  "WHERE c.oid = '%s';",
+						  (verbose ?
+						   "pg_catalog.array_to_string(c.reloptions || "
+						   "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+						   : "''"),
+						  oid);
+	}
+	else if (pset.sversion >= 120000)
 	{
 		printfPQExpBuffer(&buf,
 						  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
@@ -1853,6 +1873,10 @@ describeOneTableDetails(const char *schemaname,
 			(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
 	else
 		tableinfo.relam = NULL;
+	if (pset.sversion >= 150000)
+		tableinfo.isivm = strcmp(PQgetvalue(res, 0, 15), "t") == 0;
+	else
+		tableinfo.isivm = false;
 	PQclear(res);
 	res = NULL;
 
@@ -3630,6 +3654,12 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
 			printTableAddFooter(&cont, buf.data);
 		}
+
+		/* Incremental view maintance info */
+		if (verbose && tableinfo.relkind == RELKIND_MATVIEW && tableinfo.isivm)
+		{
+			printTableAddFooter(&cont, _("Incremental view maintenance: yes"));
+		}
 	}
 
 	/* reloptions, if verbose */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5cd5838668..ea12bbdec2 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1059,6 +1059,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"INCREMENTAL MATERIALIZED VIEW", NULL, NULL, &Query_for_list_of_matviews, THING_NO_DROP | THING_NO_ALTER},
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2757,7 +2758,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
-		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
+		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW", "INCREMENTAL MATERIALIZED VIEW");
 	/* Complete PARTITION BY with RANGE ( or LIST ( or ... */
 	else if (TailMatches("PARTITION", "BY"))
 		COMPLETE_WITH("RANGE (", "LIST (", "HASH (");
@@ -3056,13 +3057,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches("CREATE", "MATERIALIZED"))
+	else if (Matches("CREATE", "MATERIALIZED") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED"))
 		COMPLETE_WITH("VIEW");
-	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	/* Complete CREATE MATERIALIZED VIEW <name> with AS  */
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny) ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH("SELECT");
 
 /* CREATE EVENT TRIGGER */
-- 
2.17.1

v24-0007-Add-Incremental-View-Maintenance-support.patchtext/x-diff; name=v24-0007-Add-Incremental-View-Maintenance-support.patchDownload
From 2a72a60bfaa90bdff9d993e96e7a4ed6a34d3fda Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Tue, 22 Dec 2020 18:40:18 +0900
Subject: [PATCH v24 07/15] Add Incremental View Maintenance support

In this implementation, AFTER triggers are used to collect
tuplestores containing transition table contents. When multiple tables
are changed, multiple AFTER triggers are invoked, then the final AFTER
trigger performs actual update of the matview. In addition, BEFORE
triggers are also used to handle global information for view
maintenance. To calculate view deltas, we need both pre-state and
post-state of base tables. Post-update states are available in AFTER
trigger, and pre-update states can be calculated by filtering inserted
tuples using cmin/xmin system columns, and append deleted tuples which
are contained in an old transition table.

This patch also allows self-join, simultaneous updates of more than
one base table, and multiple updates of the same base table.

Incrementally Maintainable Materialized Views (IMMV) can contain
duplicated tuples. Also, DISTINCT clause is supported. When IMMV is
created with DISTINCT, multiplicity of tuples is counted and stored
in  "__ivm_count__" column, which is a hidden column of IMMV.
The value in __ivm_count__ is updated when IMMV is maintained
incrementally. A tuple in IMMV can be removed if and only if the
count becomes zero.
---
 src/backend/access/transam/xact.c   |    5 +
 src/backend/commands/createas.c     |  713 ++++++++++++
 src/backend/commands/indexcmds.c    |   40 +
 src/backend/commands/matview.c      | 1549 ++++++++++++++++++++++++++-
 src/backend/commands/tablecmds.c    |    9 +
 src/backend/nodes/copyfuncs.c       |    1 +
 src/backend/nodes/equalfuncs.c      |    1 +
 src/backend/nodes/outfuncs.c        |    1 +
 src/backend/nodes/readfuncs.c       |    1 +
 src/backend/parser/parse_relation.c |   18 +-
 src/backend/rewrite/rewriteDefine.c |    3 +-
 src/include/catalog/pg_proc.dat     |    8 +
 src/include/commands/createas.h     |    5 +
 src/include/commands/matview.h      |    8 +
 src/include/nodes/parsenodes.h      |    2 +
 15 files changed, 2324 insertions(+), 40 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 6597ec45a9..f971727255 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_enum.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/matview.h"
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "executor/spi.h"
@@ -2715,6 +2716,7 @@ AbortTransaction(void)
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
 	AtAbort_Twophase();
+	AtAbort_IVM();
 
 	/*
 	 * Advertise the fact that we aborted in pg_xact (assuming that we got as
@@ -4958,6 +4960,9 @@ AbortSubTransaction(void)
 	AbortBufferIO();
 	UnlockBuffers();
 
+	/* Clean up hash entries for incremental view maintenance */
+	AtAbort_IVM();
+
 	/* Reset WAL record construction state */
 	XLogResetInsertion();
 
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 0982851715..91888891bf 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -32,24 +32,41 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
 #include "catalog/toasting.h"
 #include "commands/createas.h"
+#include "commands/defrem.h"
 #include "commands/matview.h"
 #include "commands/prepare.h"
 #include "commands/tablecmds.h"
+#include "commands/tablespace.h"
+#include "commands/trigger.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "optimizer/clauses.h"
+#include "optimizer/optimizer.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_func.h"
+#include "parser/parse_type.h"
 #include "rewrite/rewriteHandler.h"
+#include "rewrite/rewriteManip.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
+#include "utils/syscache.h"
 
 typedef struct
 {
@@ -73,6 +90,13 @@ static bool intorel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void intorel_shutdown(DestReceiver *self);
 static void intorel_destroy(DestReceiver *self);
 
+static void CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid,
+									 Relids *relids, bool ex_lock);
+static void CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock);
+static void check_ivm_restriction(Node *node);
+static bool check_ivm_restriction_walker(Node *node, void *context);
+static void CreateIndexOnIMMV(Query *query, Relation matviewRel);
+static Bitmapset *get_primary_key_attnos_from_query(Query *qry, List **constraintList);
 
 /*
  * create_ctas_internal
@@ -108,6 +132,8 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	create->oncommit = into->onCommit;
 	create->tablespacename = into->tableSpaceName;
 	create->if_not_exists = false;
+	/* Using Materialized view only */
+	create->ivm = into->ivm;
 	create->accessMethod = into->accessMethod;
 
 	/*
@@ -238,6 +264,7 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 	List	   *rewritten;
 	PlannedStmt *plan;
 	QueryDesc  *queryDesc;
+	Query	   *query_immv = NULL;
 
 	/* Check if the relation exists or not */
 	if (CreateTableAsRelExists(stmt))
@@ -282,6 +309,22 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 		save_nestlevel = NewGUCNestLevel();
 	}
 
+	if (is_matview && into->ivm)
+	{
+		/* check if the query is supported in IMMV definition */
+		if (contain_mutable_functions((Node *) query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("mutable function is not supported on incrementally maintainable materialized view"),
+					 errhint("functions must be marked IMMUTABLE")));
+
+		check_ivm_restriction((Node *) query);
+
+		/* For IMMV, we need to rewrite matview query */
+		query = rewriteQueryForIMMV(query, into->colNames);
+		query_immv = copyObject(query);
+	}
+
 	if (into->skipData)
 	{
 		/*
@@ -358,11 +401,75 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 
 		/* Restore userid and security context */
 		SetUserIdAndSecContext(save_userid, save_sec_context);
+
+		if (into->ivm)
+		{
+			Oid matviewOid = address.objectId;
+			Relation matviewRel = table_open(matviewOid, NoLock);
+
+			/*
+			 * Mark relisivm field, if it's a matview and into->ivm is true.
+			 */
+			SetMatViewIVMState(matviewRel, true);
+
+			/* Create an index on incremental maintainable materialized view, if possible */
+			CreateIndexOnIMMV((Query *) into->viewQuery, matviewRel);
+
+			/* Create triggers on incremental maintainable materialized view */
+			if (!into->skipData)
+			{
+				Assert(query_immv != NULL);
+				CreateIvmTriggersOnBaseTables(query_immv, matviewOid, true);
+			}
+			table_close(matviewRel, NoLock);
+		}
 	}
 
 	return address;
 }
 
+/*
+ * rewriteQueryForIMMV -- rewrite view definition query for IMMV
+ *
+ * count(*) is added for counting distinct tuples in views.
+ */
+Query *
+rewriteQueryForIMMV(Query *query, List *colNames)
+{
+	Query *rewritten;
+
+	TargetEntry *tle;
+	Node *node;
+	ParseState *pstate = make_parsestate(NULL);
+	FuncCall *fn;
+
+	rewritten = copyObject(query);
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	/*
+	 * Convert DISTINCT to GROUP BY and add count(*) for counting distinct
+	 * tuples in views.
+	 */
+	if (rewritten->distinctClause)
+	{
+		rewritten->groupClause = transformDistinctClause(NULL, &rewritten->targetList, rewritten->sortClause, false);
+
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+		fn->agg_star = true;
+
+		node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+		tle = makeTargetEntry((Expr *) node,
+								list_length(rewritten->targetList) + 1,
+								pstrdup("__ivm_count__"),
+								false);
+		rewritten->targetList = lappend(rewritten->targetList, tle);
+		rewritten->hasAggs = true;
+	}
+
+	return rewritten;
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -623,3 +730,609 @@ intorel_destroy(DestReceiver *self)
 {
 	pfree(self);
 }
+
+/*
+ * CreateIvmTriggersOnBaseTables -- create IVM triggers on all base tables
+ */
+void
+CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create)
+{
+	Relids	relids = NULL;
+	bool	ex_lock = false;
+	Index	first_rtindex = is_create ? 1 : PRS2_NEW_VARNO + 1;
+	RangeTblEntry *rte;
+
+	/* Immediately return if we don't have any base tables. */
+	if (list_length(qry->rtable) < first_rtindex)
+		return;
+
+	/*
+	 * If the view has more than one base tables, we need an exclusive lock
+	 * on the view so that the view would be maintained serially to avoid
+	 * the inconsistency that occurs when two base tables are modified in
+	 * concurrent transactions. However, if the view has only one table,
+	 * we can use a weaker lock.
+	 *
+	 * The type of lock should be determined here, because if we check the
+	 * view definition at maintenance time, we need to acquire a weaker lock,
+	 * and upgrading the lock level after this increases probability of
+	 * deadlock.
+	 */
+
+	rte = list_nth(qry->rtable, first_rtindex - 1);
+	if (list_length(qry->rtable) > first_rtindex ||
+		rte->rtekind != RTE_RELATION)
+		ex_lock = true;
+
+	CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *)qry, matviewOid, &relids, ex_lock);
+
+	bms_free(relids);
+}
+
+static void
+CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid,
+									 Relids *relids, bool ex_lock)
+{
+	if (node == NULL)
+		return;
+
+	/* This can recurse, so check for excessive recursion */
+	check_stack_depth();
+
+	switch (nodeTag(node))
+	{
+		case T_Query:
+			{
+				Query *query = (Query *) node;
+
+				CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *)query->jointree, matviewOid, relids, ex_lock);
+			}
+			break;
+
+		case T_RangeTblRef:
+			{
+				int			rti = ((RangeTblRef *) node)->rtindex;
+				RangeTblEntry *rte = rt_fetch(rti, qry->rtable);
+
+				if (rte->rtekind == RTE_RELATION && !bms_is_member(rte->relid, *relids))
+				{
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_BEFORE, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_BEFORE, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_BEFORE, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_AFTER, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_AFTER, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_AFTER, ex_lock);
+
+					*relids = bms_add_member(*relids, rte->relid);
+				}
+			}
+			break;
+
+		case T_FromExpr:
+			{
+				FromExpr   *f = (FromExpr *) node;
+				ListCell   *l;
+
+				foreach(l, f->fromlist)
+					CreateIvmTriggersOnBaseTablesRecurse(qry, lfirst(l), matviewOid, relids, ex_lock);
+			}
+			break;
+
+		case T_JoinExpr:
+			{
+				JoinExpr   *j = (JoinExpr *) node;
+
+				CreateIvmTriggersOnBaseTablesRecurse(qry, j->larg, matviewOid, relids, ex_lock);
+				CreateIvmTriggersOnBaseTablesRecurse(qry, j->rarg, matviewOid, relids, ex_lock);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
+	}
+}
+
+/*
+ * CreateIvmTrigger -- create IVM trigger on a base table
+ */
+static void
+CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock)
+{
+	ObjectAddress	refaddr;
+	ObjectAddress	address;
+	CreateTrigStmt *ivm_trigger;
+	List *transitionRels = NIL;
+
+	Assert(timing == TRIGGER_TYPE_BEFORE || timing == TRIGGER_TYPE_AFTER);
+
+	refaddr.classId = RelationRelationId;
+	refaddr.objectId = viewOid;
+	refaddr.objectSubId = 0;
+
+	ivm_trigger = makeNode(CreateTrigStmt);
+	ivm_trigger->relation = NULL;
+	ivm_trigger->row = false;
+
+	ivm_trigger->timing = timing;
+	ivm_trigger->events = type;
+
+	switch (type)
+	{
+		case TRIGGER_TYPE_INSERT:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_ins_before" : "IVM_trigger_ins_after");
+			break;
+		case TRIGGER_TYPE_DELETE:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_del_before" : "IVM_trigger_del_after");
+			break;
+		case TRIGGER_TYPE_UPDATE:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_upd_before" : "IVM_trigger_upd_after");
+			break;
+		default:
+			elog(ERROR, "unsupported trigger type");
+	}
+
+	if (timing == TRIGGER_TYPE_AFTER)
+	{
+		if (type == TRIGGER_TYPE_INSERT || type == TRIGGER_TYPE_UPDATE)
+		{
+			TriggerTransition *n = makeNode(TriggerTransition);
+			n->name = "__ivm_newtable";
+			n->isNew = true;
+			n->isTable = true;
+
+			transitionRels = lappend(transitionRels, n);
+		}
+		if (type == TRIGGER_TYPE_DELETE || type == TRIGGER_TYPE_UPDATE)
+		{
+			TriggerTransition *n = makeNode(TriggerTransition);
+			n->name = "__ivm_oldtable";
+			n->isNew = false;
+			n->isTable = true;
+
+			transitionRels = lappend(transitionRels, n);
+		}
+	}
+
+	ivm_trigger->funcname =
+		(timing == TRIGGER_TYPE_BEFORE ? SystemFuncName("IVM_immediate_before") : SystemFuncName("IVM_immediate_maintenance"));
+
+	ivm_trigger->columns = NIL;
+	ivm_trigger->transitionRels = transitionRels;
+	ivm_trigger->whenClause = NULL;
+	ivm_trigger->isconstraint = false;
+	ivm_trigger->deferrable = false;
+	ivm_trigger->initdeferred = false;
+	ivm_trigger->constrrel = NULL;
+	ivm_trigger->args = list_make2(
+		makeString(DatumGetPointer(DirectFunctionCall1(oidout, ObjectIdGetDatum(viewOid)))),
+		makeString(DatumGetPointer(DirectFunctionCall1(boolout, BoolGetDatum(ex_lock))))
+		);
+
+	address = CreateTrigger(ivm_trigger, NULL, relOid, InvalidOid, InvalidOid,
+						 InvalidOid, InvalidOid, InvalidOid, NULL, true, false);
+
+	recordDependencyOn(&address, &refaddr, DEPENDENCY_IMMV);
+
+	/* Make changes-so-far visible */
+	CommandCounterIncrement();
+}
+
+/*
+ * check_ivm_restriction --- look for specify nodes in the query tree
+ */
+static void
+check_ivm_restriction(Node *node)
+{
+	check_ivm_restriction_walker(node, NULL);
+}
+
+static bool
+check_ivm_restriction_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	/*
+	 * We currently don't support Sub-Query.
+	 */
+	if (IsA(node, SubPlan) || IsA(node, SubLink))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("subquery is not supported on incrementally maintainable materialized view")));
+
+	/* This can recurse, so check for excessive recursion */
+	check_stack_depth();
+
+	switch (nodeTag(node))
+	{
+		case T_Query:
+			{
+				Query *qry = (Query *)node;
+				ListCell   *lc;
+				List       *vars;
+
+				/* if contained CTE, return error */
+				if (qry->cteList != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("CTE is not supported on incrementally maintainable materialized view")));
+				if (qry->havingQual != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg(" HAVING clause is not supported on incrementally maintainable materialized view")));
+				if (qry->sortClause != NIL)	/* There is a possibility that we don't need to return an error */
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("ORDER BY clause is not supported on incrementally maintainable materialized view")));
+				if (qry->limitOffset != NULL || qry->limitCount != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("LIMIT/OFFSET clause is not supported on incrementally maintainable materialized view")));
+				if (qry->hasDistinctOn)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DISTINCT ON is not supported on incrementally maintainable materialized view")));
+				if (qry->hasWindowFuncs)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("window functions are not supported on incrementally maintainable materialized view")));
+				if (qry->groupingSets != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("GROUPING SETS, ROLLUP, or CUBE clauses is not supported on incrementally maintainable materialized view")));
+				if (qry->setOperations != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("UNION/INTERSECT/EXCEPT statements are not supported on incrementally maintainable materialized view")));
+				if (list_length(qry->targetList) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("empty target list is not supported on incrementally maintainable materialized view")));
+				if (qry->rowMarks != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("FOR UPDATE/SHARE clause is not supported on incrementally maintainable materialized view")));
+
+				/* system column restrictions */
+				vars = pull_vars_of_level((Node *) qry, 0);
+				foreach(lc, vars)
+				{
+					if (IsA(lfirst(lc), Var))
+					{
+						Var *var = (Var *) lfirst(lc);
+						/* if system column, return error */
+						if (var->varattno < 0)
+							ereport(ERROR,
+									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									 errmsg("system column is not supported on incrementally maintainable materialized view")));
+					}
+				}
+
+				/* restrictions for rtable */
+				foreach(lc, qry->rtable)
+				{
+					RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+					if (rte->subquery)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("subquery is not supported on incrementally maintainable materialized view")));
+
+					if (rte->tablesample != NULL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("TABLESAMPLE clause is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("partitioned table is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_RELATION && has_superclass(rte->relid))
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("partitions is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_RELATION && find_inheritance_children(rte->relid, NoLock) != NIL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("inheritance parent is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_FOREIGN_TABLE)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("foreign table is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_VIEW ||
+						rte->relkind == RELKIND_MATVIEW)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view")));
+
+					if (rte->rtekind == RTE_VALUES)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("VALUES is not supported on incrementally maintainable materialized view")));
+
+				}
+
+				query_tree_walker(qry, check_ivm_restriction_walker, NULL, QTW_IGNORE_RANGE_TABLE);
+
+				break;
+			}
+		case T_TargetEntry:
+			{
+				TargetEntry *tle = (TargetEntry *)node;
+				if (isIvmName(tle->resname))
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("column name %s is not supported on incrementally maintainable materialized view", tle->resname)));
+
+				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				break;
+			}
+		case T_JoinExpr:
+			{
+				JoinExpr *joinexpr = (JoinExpr *)node;
+
+				if (joinexpr->jointype > JOIN_INNER)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("OUTER JOIN is not supported on incrementally maintainable materialized view")));
+
+				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+			}
+			break;
+			expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+			break;
+	}
+	return false;
+}
+
+/*
+ * CreateIndexOnIMMV
+ *
+ * Create a unique index on incremental maintainable materialized view.
+ * If the view definition query has a GROUP BY clause, the index is created
+ * on the columns of GROUP BY expressions. Otherwise, if the view contains
+ * all primary key attritubes of its base tables in the target list, the index
+ * is created on these attritubes. In other cases, no index is created.
+ */
+static void
+CreateIndexOnIMMV(Query *query, Relation matviewRel)
+{
+	Query *qry = (Query *) copyObject(query);
+	ListCell *lc;
+	IndexStmt  *index;
+	ObjectAddress address;
+	List *constraintList = NIL;
+	char		idxname[NAMEDATALEN];
+
+	snprintf(idxname, sizeof(idxname), "%s_index", RelationGetRelationName(matviewRel));
+
+	index = makeNode(IndexStmt);
+
+	index->unique = true;
+	index->primary = false;
+	index->isconstraint = false;
+	index->deferrable = false;
+	index->initdeferred = false;
+	index->idxname = idxname;
+	index->relation =
+		makeRangeVar(get_namespace_name(RelationGetNamespace(matviewRel)),
+					 pstrdup(RelationGetRelationName(matviewRel)),
+					 -1);
+	index->accessMethod = DEFAULT_INDEX_TYPE;
+	index->options = NIL;
+	index->tableSpace = get_tablespace_name(matviewRel->rd_rel->reltablespace);
+	index->whereClause = NULL;
+	index->indexParams = NIL;
+	index->indexIncludingParams = NIL;
+	index->excludeOpNames = NIL;
+	index->idxcomment = NULL;
+	index->indexOid = InvalidOid;
+	index->oldNode = InvalidOid;
+	index->oldCreateSubid = InvalidSubTransactionId;
+	index->oldFirstRelfilenodeSubid = InvalidSubTransactionId;
+	index->transformed = true;
+	index->concurrent = false;
+	index->if_not_exists = false;
+
+	if (qry->distinctClause)
+	{
+		/* create unique constraint on all columns */
+		foreach(lc, qry->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+			IndexElem  *iparam;
+
+			iparam = makeNode(IndexElem);
+			iparam->name = pstrdup(NameStr(attr->attname));
+			iparam->expr = NULL;
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->opclassopts = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+		}
+	}
+	else
+	{
+		Bitmapset *key_attnos;
+
+		/* create index on the base tables' primary key columns */
+		key_attnos = get_primary_key_attnos_from_query(qry, &constraintList);
+		if (key_attnos)
+		{
+			foreach(lc, qry->targetList)
+			{
+				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+				Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+
+				if (bms_is_member(tle->resno - FirstLowInvalidHeapAttributeNumber, key_attnos))
+				{
+					IndexElem  *iparam;
+
+					iparam = makeNode(IndexElem);
+					iparam->name = pstrdup(NameStr(attr->attname));
+					iparam->expr = NULL;
+					iparam->indexcolname = NULL;
+					iparam->collation = NIL;
+					iparam->opclass = NIL;
+					iparam->opclassopts = NIL;
+					iparam->ordering = SORTBY_DEFAULT;
+					iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+					index->indexParams = lappend(index->indexParams, iparam);
+				}
+			}
+
+		}
+		else
+		{
+			/* create no index, just notice that an appropriate index is necessary for efficient IVM */
+			ereport(NOTICE,
+					(errmsg("could not create an index on materialized view \"%s\" automatically",
+							RelationGetRelationName(matviewRel)),
+					 errdetail("This target list does not have all the primary key columns, "
+							   "or this view does not contain DISTINCT clause."),
+					 errhint("Create an index on the materialized view for efficient incremental maintenance.")));
+			return;
+		}
+	}
+
+	address = DefineIndex(RelationGetRelid(matviewRel),
+						  index,
+						  InvalidOid,
+						  InvalidOid,
+						  InvalidOid,
+						  false, true, false, false, true);
+
+	ereport(NOTICE,
+			(errmsg("created index \"%s\" on materialized view \"%s\"",
+					idxname, RelationGetRelationName(matviewRel))));
+
+
+	/*
+	 * Make dependencies so that the index is dropped if any base tables's
+	 * primary key is dropped.
+	 */
+	foreach(lc, constraintList)
+	{
+		Oid constraintOid = lfirst_oid(lc);
+		ObjectAddress	refaddr;
+
+		refaddr.classId = ConstraintRelationId;
+		refaddr.objectId = constraintOid;
+		refaddr.objectSubId = 0;
+
+		recordDependencyOn(&address, &refaddr, DEPENDENCY_NORMAL);
+	}
+}
+
+
+/*
+ * get_primary_key_attnos_from_query
+ *
+ * Identify the columns in base tables' primary keys in the target list.
+ *
+ * Returns a Bitmapset of the column attnos of the primary key's columns of
+ * tables that used in the query.  The attnos are offset by
+ * FirstLowInvalidHeapAttributeNumber as same as get_primary_key_attnos.
+ *
+ * If any table has no primary key or any primary key's columns is not in
+ * the target list, return NULL.  We also return NULL if any pkey constraint
+ * is deferrable.
+ *
+ * constraintList is set to a list of the OIDs of the pkey constraints.
+ */
+static Bitmapset *
+get_primary_key_attnos_from_query(Query *query, List **constraintList)
+{
+	List *key_attnos_list = NIL;
+	ListCell *lc;
+	int i;
+	Bitmapset *keys = NULL;
+	Relids	rels_in_from;
+	PlannerInfo root;
+
+
+	/*
+	 * Collect primary key attributes from all tables used in query. The key attributes
+	 * sets for each table are stored in key_attnos_list in order by RTE index.
+	 */
+	i = 1;
+	foreach(lc, query->rtable)
+	{
+		RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+		Bitmapset *key_attnos;
+		bool	has_pkey = true;
+
+		/* for tables, call get_primary_key_attnos */
+		if (r->rtekind == RTE_RELATION)
+		{
+			Oid constraintOid;
+			key_attnos = get_primary_key_attnos(r->relid, false, &constraintOid);
+			*constraintList = lappend_oid(*constraintList, constraintOid);
+			has_pkey = (key_attnos != NULL);
+		}
+		/* for other RTEs, store NULL into key_attnos_list */
+		else
+			key_attnos = NULL;
+
+		/*
+		 * If any table or subquery has no primary key or its pkey constraint is deferrable,
+		 * we cannot get key attributes for this query, so return NULL.
+		 */
+		if (!has_pkey)
+			return NULL;
+
+		key_attnos_list = lappend(key_attnos_list, key_attnos);
+	}
+
+	/* Collect key attributes appearing in the target list */
+	i = 1;
+	foreach(lc, query->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) flatten_join_alias_vars(query, lfirst(lc));
+
+		if (IsA(tle->expr, Var))
+		{
+			Var *var = (Var*) tle->expr;
+			Bitmapset *attnos = list_nth(key_attnos_list, var->varno - 1);
+
+			/* check if this attribute is from a base table's primary key */
+			if (bms_is_member(var->varattno - FirstLowInvalidHeapAttributeNumber, attnos))
+			{
+				/*
+				 * Remove found key attributes from key_attnos_list, and add this
+				 * to the result list.
+				 */
+				bms_del_member(attnos, var->varattno - FirstLowInvalidHeapAttributeNumber);
+				keys = bms_add_member(keys, i - FirstLowInvalidHeapAttributeNumber);
+			}
+		}
+		i++;
+	}
+
+	/* Collect relations appearing in the FROM clause */
+	rels_in_from = pull_varnos_of_level(&root, (Node *)query->jointree, 0);
+
+	/*
+	 * Check if all key attributes of relations in FROM are appearing in the target
+	 * list.  If an attribute remains in key_attnos_list in spite of the table is used
+	 * in FROM clause, the target is missing this key attribute, so we return NULL.
+	 */
+	i = 1;
+	foreach(lc, key_attnos_list)
+	{
+		Bitmapset *bms = (Bitmapset *)lfirst(lc);
+		if (!bms_is_empty(bms) && bms_is_member(i, rels_in_from))
+			return NULL;
+		i++;
+	}
+
+	return keys;
+}
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5e..5581c21298 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -36,6 +36,7 @@
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
+#include "commands/matview.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
@@ -1054,6 +1055,45 @@ DefineIndex(Oid relationId,
 	safe_index = indexInfo->ii_Expressions == NIL &&
 		indexInfo->ii_Predicate == NIL;
 
+	/*
+	 * We disallow unique indexes on IVM columns of IMMVs.
+	 */
+	if (RelationIsIVM(rel) && stmt->unique)
+	{
+		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
+		{
+			AttrNumber	attno = indexInfo->ii_IndexAttrNumbers[i];
+			if (attno > 0)
+			{
+				char *name = NameStr(TupleDescAttr(rel->rd_att, attno - 1)->attname);
+				if (name && isIvmName(name))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique index creation on IVM columns is not supported")));
+			}
+		}
+
+		if (indexInfo->ii_Expressions)
+		{
+			Bitmapset  *indexattrs = NULL;
+			int			varno = -1;
+
+			pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
+
+			while ((varno = bms_next_member(indexattrs, varno)) >= 0)
+			{
+				int attno = varno + FirstLowInvalidHeapAttributeNumber;
+				char *name = NameStr(TupleDescAttr(rel->rd_att, attno - 1)->attname);
+				if (name && isIvmName(name))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique index creation on IVM columns is not supported")));
+			}
+
+		}
+	}
+
+
 	/*
 	 * Report index creation if appropriate (delay this till after most of the
 	 * error checks)
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 512b00bc65..70e35e5a63 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -25,26 +25,47 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_type.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "commands/cluster.h"
+#include "commands/defrem.h"
 #include "commands/matview.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
+#include "commands/createas.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "executor/tstoreReceiver.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_func.h"
 #include "parser/parse_relation.h"
+#include "parser/parse_type.h"
 #include "pgstat.h"
 #include "rewrite/rewriteHandler.h"
+#include "rewrite/rewriteManip.h"
+#include "rewrite/rowsecurity.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
 
 
 typedef struct
@@ -58,6 +79,50 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_transientrel;
 
+#define MV_INIT_QUERYHASHSIZE	16
+
+/*
+ * MV_TriggerHashEntry
+ *
+ * Hash entry for base tables on which IVM trigger is invoked
+ */
+typedef struct MV_TriggerHashEntry
+{
+	Oid	matview_id;			/* OID of the materialized view */
+	int	before_trig_count;	/* count of before triggers invoked */
+	int	after_trig_count;	/* count of after triggers invoked */
+
+	TransactionId	xid;	/* Transaction id before the first table is modified*/
+	CommandId		cid;	/* Command id before the first table is modified */
+
+	List   *tables;		/* List of MV_TriggerTable */
+	bool	has_old;	/* tuples are deleted from any table? */
+	bool	has_new;	/* tuples are inserted into any table? */
+} MV_TriggerHashEntry;
+
+/*
+ * MV_TriggerTable
+ *
+ * IVM related data for tables on which the trigger is invoked.
+ */
+typedef struct MV_TriggerTable
+{
+	Oid		table_id;			/* OID of the modified table */
+	List   *old_tuplestores;	/* tuplestores for deleted tuples */
+	List   *new_tuplestores;	/* tuplestores for inserted tuples */
+	List   *old_rtes;			/* RTEs of ENRs for old_tuplestores*/
+	List   *new_rtes;			/* RTEs of ENRs for new_tuplestores */
+
+	List   *rte_indexes;		/* List of RTE index of the modified table */
+	RangeTblEntry *original_rte;	/* the original RTE saved before rewriting query */
+} MV_TriggerTable;
+
+static HTAB *mv_trigger_info = NULL;
+
+/* ENR name for materialized view delta */
+#define NEW_DELTA_ENRNAME "new_delta"
+#define OLD_DELTA_ENRNAME "old_delta"
+
 static int	matview_maintenance_depth = 0;
 
 static void transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
@@ -65,7 +130,9 @@ static bool transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void transientrel_shutdown(DestReceiver *self);
 static void transientrel_destroy(DestReceiver *self);
 static uint64 refresh_matview_datafill(DestReceiver *dest, Query *query,
-									   const char *queryString);
+						 QueryEnvironment *queryEnv,
+						 TupleDesc *resultTupleDesc,
+						 const char *queryString);
 static char *make_temptable_name_n(char *tempname, int n);
 static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 								   int save_sec_context);
@@ -73,6 +140,45 @@ static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersist
 static bool is_usable_unique_index(Relation indexRel);
 static void OpenMatViewIncrementalMaintenance(void);
 static void CloseMatViewIncrementalMaintenance(void);
+static Query *get_matview_query(Relation matviewRel);
+
+static Query *rewrite_query_for_preupdate_state(Query *query, List *tables,
+								  TransactionId xid, CommandId cid,
+								  ParseState *pstate);
+static void register_delta_ENRs(ParseState *pstate, Query *query, List *tables);
+static char *make_delta_enr_name(const char *prefix, Oid relid, int count);
+static RangeTblEntry *get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
+				 TransactionId xid, CommandId cid,
+				 QueryEnvironment *queryEnv);
+static RangeTblEntry *union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
+		   QueryEnvironment *queryEnv);
+static Query *rewrite_query_for_distinct(Query *query, ParseState *pstate);
+
+static void calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
+			DestReceiver *dest_old, DestReceiver *dest_new,
+			TupleDesc *tupdesc_old, TupleDesc *tupdesc_new,
+			QueryEnvironment *queryEnv);
+static Query *rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte_index);
+
+static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
+			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
+			Query *query, bool use_count, char *count_colname);
+static void apply_old_delta(const char *matviewname, const char *deltaname_old,
+				List *keys);
+static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
+				List *keys, const char *count_colname);
+static void apply_new_delta(const char *matviewname, const char *deltaname_new,
+				StringInfo target_list);
+static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
+				List *keys, StringInfo target_list, const char* count_colname);
+static char *get_matching_condition_string(List *keys);
+static void generate_equal(StringInfo querybuf, Oid opttype,
+			   const char *leftop, const char *rightop);
+
+static void mv_InitHashTables(void);
+static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry);
+
+static List *get_securityQuals(Oid relId, int rt_index, Query *query);
 
 /*
  * SetMatViewPopulatedState
@@ -114,6 +220,46 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
 	CommandCounterIncrement();
 }
 
+/*
+ * SetMatViewIVMState
+ *		Mark a materialized view as IVM, or not.
+ *
+ * NOTE: caller must be holding an appropriate lock on the relation.
+ */
+void
+SetMatViewIVMState(Relation relation, bool newstate)
+{
+	Relation	pgrel;
+	HeapTuple	tuple;
+
+	Assert(relation->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Update relation's pg_class entry.  Crucial side-effect: other backends
+	 * (and this one too!) are sent SI message to make them rebuild relcache
+	 * entries.
+	 */
+	pgrel = table_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(relation)));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u",
+			 RelationGetRelid(relation));
+
+	((Form_pg_class) GETSTRUCT(tuple))->relisivm = newstate;
+
+	CatalogTupleUpdate(pgrel, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	table_close(pgrel, RowExclusiveLock);
+
+	/*
+	 * Advance command counter to make the updated pg_class row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+}
+
 /*
  * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command
  *
@@ -140,8 +286,6 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 {
 	Oid			matviewOid;
 	Relation	matviewRel;
-	RewriteRule *rule;
-	List	   *actions;
 	Query	   *dataQuery;
 	Oid			tableSpace;
 	Oid			relowner;
@@ -155,6 +299,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 	int			save_sec_context;
 	int			save_nestlevel;
 	ObjectAddress address;
+	bool oldPopulated;
 
 	/* Determine strength of lock needed. */
 	concurrent = stmt->concurrent;
@@ -167,6 +312,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										  lockmode, 0,
 										  RangeVarCallbackOwnsTable, NULL);
 	matviewRel = table_open(matviewOid, NoLock);
+	oldPopulated = RelationIsPopulated(matviewRel);
 
 	/* Make sure it is a materialized view. */
 	if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW)
@@ -187,32 +333,12 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("CONCURRENTLY and WITH NO DATA options cannot be used together")));
 
-	/*
-	 * Check that everything is correct for a refresh. Problems at this point
-	 * are internal errors, so elog is sufficient.
-	 */
-	if (matviewRel->rd_rel->relhasrules == false ||
-		matviewRel->rd_rules->numLocks < 1)
-		elog(ERROR,
-			 "materialized view \"%s\" is missing rewrite information",
-			 RelationGetRelationName(matviewRel));
-
-	if (matviewRel->rd_rules->numLocks > 1)
-		elog(ERROR,
-			 "materialized view \"%s\" has too many rules",
-			 RelationGetRelationName(matviewRel));
 
-	rule = matviewRel->rd_rules->rules[0];
-	if (rule->event != CMD_SELECT || !(rule->isInstead))
-		elog(ERROR,
-			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
-			 RelationGetRelationName(matviewRel));
+	dataQuery = get_matview_query(matviewRel);
 
-	actions = rule->actions;
-	if (list_length(actions) != 1)
-		elog(ERROR,
-			 "the rule for materialized view \"%s\" is not a single action",
-			 RelationGetRelationName(matviewRel));
+	/* For IMMV, we need to rewrite matview query */
+	if (!stmt->skipData && RelationIsIVM(matviewRel))
+		dataQuery = rewriteQueryForIMMV(dataQuery,NIL);
 
 	/*
 	 * Check that there is a unique index with no WHERE clause on one or more
@@ -247,12 +373,6 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 					 errhint("Create a unique index with no WHERE clause on one or more columns of the materialized view.")));
 	}
 
-	/*
-	 * The stored query was rewritten at the time of the MV definition, but
-	 * has not been scribbled on by the planner.
-	 */
-	dataQuery = linitial_node(Query, actions);
-
 	/*
 	 * Check for active uses of the relation in the current transaction, such
 	 * as open scans.
@@ -293,6 +413,52 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 		relpersistence = matviewRel->rd_rel->relpersistence;
 	}
 
+	/* delete immv triggers */
+	if (RelationIsIVM(matviewRel) && stmt->skipData )
+	{
+		/* use deleted trigger */
+		Relation	depRel;
+		ScanKeyData key;
+		SysScanDesc scan;
+		HeapTuple	tup;
+		ObjectAddresses *immv_triggers;
+
+		immv_triggers = new_object_addresses();
+
+		/*
+		 * We save some cycles by opening pg_depend just once and passing the
+		 * Relation pointer down to all the recursive deletion steps.
+		 */
+		depRel = table_open(DependRelationId, RowExclusiveLock);
+
+		ScanKeyInit(&key,
+					Anum_pg_depend_refobjid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(matviewOid));
+
+		scan = systable_beginscan(depRel, DependReferenceIndexId, true,
+								  NULL, 1, &key);
+		while ((tup = systable_getnext(scan)) != NULL)
+		{
+			ObjectAddress obj;
+			Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
+
+			if (foundDep->deptype == DEPENDENCY_IMMV)
+			{
+				obj.classId = foundDep->classid;
+				obj.objectId = foundDep->objid;
+				obj.objectSubId = foundDep->refobjsubid;
+				add_exact_object_address(&obj, immv_triggers);
+			}
+		}
+		systable_endscan(scan);
+
+		performMultipleDeletions(immv_triggers, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
+
+		table_close(depRel, RowExclusiveLock);
+		free_object_addresses(immv_triggers);
+	}
+
 	/*
 	 * Create the transient table that will receive the regenerated data. Lock
 	 * it against access by any other process until commit (by which time it
@@ -312,7 +478,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 
 	/* Generate the data, if wanted. */
 	if (!stmt->skipData)
-		processed = refresh_matview_datafill(dest, dataQuery, queryString);
+		processed = refresh_matview_datafill(dest, dataQuery, NULL, NULL, queryString);
 
 	/* Make the matview match the newly generated data. */
 	if (concurrent)
@@ -347,6 +513,9 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 			pgstat_count_heap_insert(matviewRel, processed);
 	}
 
+	if (!stmt->skipData && RelationIsIVM(matviewRel) && !oldPopulated)
+		CreateIvmTriggersOnBaseTables(dataQuery, matviewOid, false);
+
 	table_close(matviewRel, NoLock);
 
 	/* Roll back any GUC changes */
@@ -381,6 +550,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  */
 static uint64
 refresh_matview_datafill(DestReceiver *dest, Query *query,
+						 QueryEnvironment *queryEnv,
+						 TupleDesc *resultTupleDesc,
 						 const char *queryString)
 {
 	List	   *rewritten;
@@ -417,7 +588,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 	/* Create a QueryDesc, redirecting output to our tuple receiver */
 	queryDesc = CreateQueryDesc(plan, queryString,
 								GetActiveSnapshot(), InvalidSnapshot,
-								dest, NULL, NULL, 0);
+								dest, NULL, queryEnv ? queryEnv: NULL, 0);
 
 	/* call ExecutorStart to prepare the plan for execution */
 	ExecutorStart(queryDesc, 0);
@@ -427,6 +598,9 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 
 	processed = queryDesc->estate->es_processed;
 
+	if (resultTupleDesc)
+		*resultTupleDesc = CreateTupleDescCopy(queryDesc->tupDesc);
+
 	/* and clean up */
 	ExecutorFinish(queryDesc);
 	ExecutorEnd(queryDesc);
@@ -941,3 +1115,1308 @@ CloseMatViewIncrementalMaintenance(void)
 	matview_maintenance_depth--;
 	Assert(matview_maintenance_depth >= 0);
 }
+
+/*
+ * get_matview_query - get the Query from a matview's _RETURN rule.
+ */
+static Query *
+get_matview_query(Relation matviewRel)
+{
+	RewriteRule *rule;
+	List * actions;
+
+	/*
+	 * Check that everything is correct for a refresh. Problems at this point
+	 * are internal errors, so elog is sufficient.
+	 */
+	if (matviewRel->rd_rel->relhasrules == false ||
+		matviewRel->rd_rules->numLocks < 1)
+		elog(ERROR,
+			 "materialized view \"%s\" is missing rewrite information",
+			 RelationGetRelationName(matviewRel));
+
+	if (matviewRel->rd_rules->numLocks > 1)
+		elog(ERROR,
+			 "materialized view \"%s\" has too many rules",
+			 RelationGetRelationName(matviewRel));
+
+	rule = matviewRel->rd_rules->rules[0];
+	if (rule->event != CMD_SELECT || !(rule->isInstead))
+		elog(ERROR,
+			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
+			 RelationGetRelationName(matviewRel));
+
+	actions = rule->actions;
+	if (list_length(actions) != 1)
+		elog(ERROR,
+			 "the rule for materialized view \"%s\" is not a single action",
+			 RelationGetRelationName(matviewRel));
+
+	/*
+	 * The stored query was rewritten at the time of the MV definition, but
+	 * has not been scribbled on by the planner.
+	 */
+	return linitial_node(Query, actions);
+}
+
+
+/* ----------------------------------------------------
+ *		Incremental View Maintenance routines
+ * ---------------------------------------------------
+ */
+
+/*
+ * IVM_immediate_before
+ *
+ * IVM trigger function invoked before base table is modified. If this is
+ * invoked firstly in the same statement, we save the transaction id and the
+ * command id at that time.
+ */
+Datum
+IVM_immediate_before(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	char	   *matviewOid_text = trigdata->tg_trigger->tgargs[0];
+	char	   *ex_lock_text = trigdata->tg_trigger->tgargs[1];
+	Oid			matviewOid;
+	MV_TriggerHashEntry *entry;
+	bool	found;
+	bool	ex_lock;
+
+	matviewOid = DatumGetObjectId(DirectFunctionCall1(oidin, CStringGetDatum(matviewOid_text)));
+	ex_lock = DatumGetBool(DirectFunctionCall1(boolin, CStringGetDatum(ex_lock_text)));
+
+	/* If the view has more than one tables, we have to use an exclusive lock. */
+	if (ex_lock)
+	{
+		/*
+		 * Wait for concurrent transactions which update this materialized view at
+		 * READ COMMITED. This is needed to see changes committed in other
+		 * transactions. No wait and raise an error at REPEATABLE READ or
+		 * SERIALIZABLE to prevent update anomalies of matviews.
+		 * XXX: dead-lock is possible here.
+		 */
+		if (!IsolationUsesXactSnapshot())
+			LockRelationOid(matviewOid, ExclusiveLock);
+		else if (!ConditionalLockRelationOid(matviewOid, ExclusiveLock))
+		{
+			/* try to throw error by name; relation could be deleted... */
+			char	   *relname = get_rel_name(matviewOid);
+
+			if (!relname)
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						errmsg("could not obtain lock on materialized view during incremental maintenance")));
+
+			ereport(ERROR,
+					(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+					errmsg("could not obtain lock on materialized view \"%s\" during incremental maintenance",
+							relname)));
+		}
+	}
+	else
+		LockRelationOid(matviewOid, RowExclusiveLock);
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_trigger_info)
+		mv_InitHashTables();
+
+	entry = (MV_TriggerHashEntry *) hash_search(mv_trigger_info,
+											  (void *) &matviewOid,
+											  HASH_ENTER, &found);
+
+	/* On the first BEFORE to update the view, initialize trigger data */
+	if (!found)
+	{
+		Snapshot snapshot = GetActiveSnapshot();
+
+		entry->matview_id = matviewOid;
+		entry->before_trig_count = 0;
+		entry->after_trig_count = 0;
+		entry->xid = GetCurrentTransactionId();
+		entry->cid = snapshot->curcid;
+		entry->tables = NIL;
+		entry->has_old = false;
+		entry->has_new = false;
+	}
+
+	entry->before_trig_count++;
+
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * IVM_immediate_before
+ *
+ * IVM trigger function invoked after base table is modified.
+ * For each table, tuplestores of transition tables are collected.
+ * and after the last modification
+ */
+Datum
+IVM_immediate_maintenance(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	Relation	rel;
+	Oid			relid;
+	Oid			matviewOid;
+	Query	   *query;
+	Query	   *rewritten = NULL;
+	char	   *matviewOid_text = trigdata->tg_trigger->tgargs[0];
+	Relation	matviewRel;
+	int old_depth = matview_maintenance_depth;
+
+	Oid			relowner;
+	Tuplestorestate *old_tuplestore = NULL;
+	Tuplestorestate *new_tuplestore = NULL;
+	DestReceiver *dest_new = NULL, *dest_old = NULL;
+	Oid			save_userid;
+	int			save_sec_context;
+	int			save_nestlevel;
+
+	MV_TriggerHashEntry *entry;
+	MV_TriggerTable		*table;
+	bool	found;
+
+	ParseState		 *pstate;
+	QueryEnvironment *queryEnv = create_queryEnv();
+	MemoryContext	oldcxt;
+	ListCell   *lc;
+	int			i;
+
+
+	/* Create a ParseState for rewriting the view definition query */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	rel = trigdata->tg_relation;
+	relid = rel->rd_id;
+
+	matviewOid = DatumGetObjectId(DirectFunctionCall1(oidin, CStringGetDatum(matviewOid_text)));
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_trigger_info)
+		mv_InitHashTables();
+
+	/* get the entry for this materialized view */
+	entry = (MV_TriggerHashEntry *) hash_search(mv_trigger_info,
+											  (void *) &matviewOid,
+											  HASH_FIND, &found);
+	Assert (found && entry != NULL);
+	entry->after_trig_count++;
+
+	/* search the entry for the modified table and create new entry if not found */
+	found = false;
+	foreach(lc, entry->tables)
+	{
+		table = (MV_TriggerTable *) lfirst(lc);
+		if (table->table_id == relid)
+		{
+			found = true;
+			break;
+		}
+	}
+	if (!found)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		table = (MV_TriggerTable *) palloc0(sizeof(MV_TriggerTable));
+		table->table_id = relid;
+		table->old_tuplestores = NIL;
+		table->new_tuplestores = NIL;
+		table->old_rtes = NIL;
+		table->new_rtes = NIL;
+		table->rte_indexes = NIL;
+		entry->tables = lappend(entry->tables, table);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* Save the transition tables and make a request to not free immediately */
+	if (trigdata->tg_oldtable)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+		table->old_tuplestores = lappend(table->old_tuplestores, trigdata->tg_oldtable);
+		entry->has_old = true;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (trigdata->tg_newtable)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+		table->new_tuplestores = lappend(table->new_tuplestores, trigdata->tg_newtable);
+		entry->has_new = true;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (entry->has_new || entry->has_old)
+	{
+		CmdType cmd;
+
+		if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+			cmd = CMD_INSERT;
+		else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+			cmd = CMD_DELETE;
+		else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+			cmd = CMD_UPDATE;
+		else
+			elog(ERROR,"unsupported trigger type");
+
+		/* Prolong lifespan of transition tables to the end of the last AFTER trigger */
+		SetTransitionTablePreserved(relid, cmd);
+	}
+
+
+	/* If this is not the last AFTER trigger call, immediately exit. */
+	Assert (entry->before_trig_count >= entry->after_trig_count);
+	if (entry->before_trig_count != entry->after_trig_count)
+		return PointerGetDatum(NULL);
+
+	/*
+	 * If this is the last AFTER trigger call, continue and update the view.
+	 */
+
+	/*
+	 * Advance command counter to make the updated base table row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+
+	matviewRel = table_open(matviewOid, NoLock);
+
+	/* get view query*/
+	query = get_matview_query(matviewRel);
+
+	/* Make sure it is a materialized view. */
+	Assert(matviewRel->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Get and push the latast snapshot to see any changes which is committed
+	 * during waiting in other transactions at READ COMMITTED level.
+	 */
+	PushActiveSnapshot(GetTransactionSnapshot());
+
+	/*
+	 * Check for active uses of the relation in the current transaction, such
+	 * as open scans.
+	 *
+	 * NB: We count on this to protect us against problems with refreshing the
+	 * data using TABLE_INSERT_FROZEN.
+	 */
+	CheckTableNotInUse(matviewRel, "refresh a materialized view incrementally");
+
+	/*
+	 * Switch to the owner's userid, so that any functions are run as that
+	 * user.  Also arrange to make GUC variable changes local to this command.
+	 * We will switch modes when we are about to execute user code.
+	 */
+	relowner = matviewRel->rd_rel->relowner;
+	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+	save_nestlevel = NewGUCNestLevel();
+
+	/*
+	 * rewrite query for calculating deltas
+	 */
+
+	rewritten = copyObject(query);
+
+	/* Replace resnames in a target list with materialized view's attnames */
+	i = 0;
+	foreach (lc, rewritten->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char *resname = NameStr(attr->attname);
+
+		tle->resname = pstrdup(resname);
+		i++;
+	}
+
+	/* Set all tables in the query to pre-update state */
+	rewritten = rewrite_query_for_preupdate_state(rewritten, entry->tables,
+												  entry->xid, entry->cid,
+												  pstate);
+	/* Rewrite for DISTINCT clause */
+	rewritten = rewrite_query_for_distinct(rewritten, pstate);
+
+	/* Create tuplestores to store view deltas */
+	if (entry->has_old)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		old_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+		dest_old = CreateDestReceiver(DestTuplestore);
+		SetTuplestoreDestReceiverParams(dest_old,
+									old_tuplestore,
+									TopTransactionContext,
+									false,
+									NULL,
+									NULL);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (entry->has_new)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		new_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+		dest_new = CreateDestReceiver(DestTuplestore);
+		SetTuplestoreDestReceiverParams(dest_new,
+									new_tuplestore,
+									TopTransactionContext,
+									false,
+									NULL,
+									NULL);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* for all modified tables */
+	foreach(lc, entry->tables)
+	{
+		ListCell *lc2;
+
+		table = (MV_TriggerTable *) lfirst(lc);
+
+		/* loop for self-join */
+		foreach(lc2, table->rte_indexes)
+		{
+			int	rte_index = lfirst_int(lc2);
+			TupleDesc		tupdesc_old;
+			TupleDesc		tupdesc_new;
+			bool	use_count = false;
+			char   *count_colname = NULL;
+
+			count_colname = pstrdup("__ivm_count__");
+
+			if (query->distinctClause)
+				use_count = true;
+
+			/* calculate delta tables */
+			calc_delta(table, rte_index, rewritten, dest_old, dest_new,
+					   &tupdesc_old, &tupdesc_new, queryEnv);
+
+			/* Set the table in the query to post-update state */
+			rewritten = rewrite_query_for_postupdate_state(rewritten, table, rte_index);
+
+			PG_TRY();
+			{
+				/* apply the delta tables to the materialized view */
+				apply_delta(matviewOid, old_tuplestore, new_tuplestore,
+							tupdesc_old, tupdesc_new, query, use_count,
+							count_colname);
+			}
+			PG_CATCH();
+			{
+				matview_maintenance_depth = old_depth;
+				PG_RE_THROW();
+			}
+			PG_END_TRY();
+
+			/* clear view delta tuplestores */
+			if (old_tuplestore)
+				tuplestore_clear(old_tuplestore);
+			if (new_tuplestore)
+				tuplestore_clear(new_tuplestore);
+		}
+	}
+
+	/* Clean up hash entry and delete tuplestores */
+	clean_up_IVM_hash_entry(entry);
+	if (old_tuplestore)
+	{
+		dest_old->rDestroy(dest_old);
+		tuplestore_end(old_tuplestore);
+	}
+	if (new_tuplestore)
+	{
+		dest_new->rDestroy(dest_new);
+		tuplestore_end(new_tuplestore);
+	}
+
+	/* Pop the original snapshot. */
+	PopActiveSnapshot();
+
+	table_close(matviewRel, NoLock);
+
+	/* Roll back any GUC changes */
+	AtEOXact_GUC(false, save_nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(save_userid, save_sec_context);
+
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * rewrite_query_for_preupdate_state
+ *
+ * Rewrite the query so that base tables' RTEs will represent "pre-update"
+ * state of tables. This is necessary to calculate view delta after multiple
+ * tables are modified. xid and cid are the transaction id and command id
+ * before the first table was modified.
+ */
+static Query*
+rewrite_query_for_preupdate_state(Query *query, List *tables,
+								  TransactionId xid, CommandId cid,
+								  ParseState *pstate)
+{
+	ListCell *lc;
+	int num_rte = list_length(query->rtable);
+	int i;
+
+
+	/* register delta ENRs */
+	register_delta_ENRs(pstate, query, tables);
+
+	/* XXX: Is necessary? Is this right timing? */
+	AcquireRewriteLocks(query, true, false);
+
+	i = 1;
+	foreach(lc, query->rtable)
+	{
+		RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+
+		ListCell *lc2;
+		foreach(lc2, tables)
+		{
+			MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc2);
+			/*
+			 * if the modified table is found then replace the original RTE with
+			 * "pre-state" RTE and append its index to the list.
+			 */
+			if (r->relid == table->table_id)
+			{
+				lfirst(lc) = get_prestate_rte(r, table, xid, cid, pstate->p_queryEnv);
+				table->rte_indexes = lappend_int(table->rte_indexes, i);
+				break;
+			}
+		}
+
+		/* finish the loop if we processed all RTE included in the original query */
+		if (i++ >= num_rte)
+			break;
+	}
+
+	return query;
+}
+
+/*
+ * register_delta_ENRs
+ *
+ * For all modified tables, make ENRs for their transition tables
+ * and register them to the queryEnv. ENR's RTEs are also appended
+ * into the list in query tree.
+ */
+static void
+register_delta_ENRs(ParseState *pstate, Query *query, List *tables)
+{
+	QueryEnvironment *queryEnv = pstate->p_queryEnv;
+	ListCell *lc;
+	RangeTblEntry	*rte;
+
+	foreach(lc, tables)
+	{
+		MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc);
+		ListCell *lc2;
+		int count;
+
+		count = 0;
+		foreach(lc2, table->old_tuplestores)
+		{
+			Tuplestorestate *oldtable = (Tuplestorestate *) lfirst(lc2);
+			EphemeralNamedRelation enr =
+				palloc(sizeof(EphemeralNamedRelationData));
+			ParseNamespaceItem *nsitem;
+
+			enr->md.name = make_delta_enr_name("old", table->table_id, count);
+			enr->md.reliddesc = table->table_id;
+			enr->md.tupdesc = NULL;
+			enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+			enr->md.enrtuples = tuplestore_tuple_count(oldtable);
+			enr->reldata = oldtable;
+			register_ENR(queryEnv, enr);
+
+			nsitem = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+			rte = nsitem->p_rte;
+			/* if base table has RLS, set security condition to enr */
+			rte->securityQuals = get_securityQuals(table->table_id, list_length(query->rtable) + 1, query);
+
+			query->rtable = lappend(query->rtable, rte);
+			table->old_rtes = lappend(table->old_rtes, rte);
+
+			count++;
+		}
+
+		count = 0;
+		foreach(lc2, table->new_tuplestores)
+		{
+			Tuplestorestate *newtable = (Tuplestorestate *) lfirst(lc2);
+			EphemeralNamedRelation enr =
+				palloc(sizeof(EphemeralNamedRelationData));
+			ParseNamespaceItem *nsitem;
+
+			enr->md.name = make_delta_enr_name("new", table->table_id, count);
+			enr->md.reliddesc = table->table_id;
+			enr->md.tupdesc = NULL;
+			enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+			enr->md.enrtuples = tuplestore_tuple_count(newtable);
+			enr->reldata = newtable;
+			register_ENR(queryEnv, enr);
+
+			nsitem = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+			rte = nsitem->p_rte;
+			/* if base table has RLS, set security condition to enr*/
+			rte->securityQuals = get_securityQuals(table->table_id, list_length(query->rtable) + 1, query);
+
+			query->rtable = lappend(query->rtable, rte);
+			table->new_rtes = lappend(table->new_rtes, rte);
+
+			count++;
+		}
+	}
+}
+
+/*
+ * get_prestate_rte
+ *
+ * Rewrite RTE of the modified table to a subquery which represents
+ * "pre-state" table. The original RTE is saved in table->rte_original.
+ */
+static RangeTblEntry*
+get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
+				 TransactionId xid, CommandId cid,
+				 QueryEnvironment *queryEnv)
+{
+	StringInfoData str;
+	RawStmt *raw;
+	Query *sub;
+	Relation rel;
+	ParseState *pstate;
+	char *relname;
+	int i;
+
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	/*
+	 * We can use NoLock here since AcquireRewriteLocks should
+	 * have locked the rel already.
+	 */
+	rel = table_open(table->table_id, NoLock);
+	relname = quote_qualified_identifier(
+					get_namespace_name(RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+	table_close(rel, NoLock);
+
+	initStringInfo(&str);
+	appendStringInfo(&str,
+		"SELECT t.* FROM %s t"
+		" WHERE (age(t.xmin) - age(%u::text::xid) > 0) OR"
+		" (t.xmin = %u AND t.cmin::text::int < %u)",
+			relname, xid, xid, cid);
+
+	for (i = 0; i < list_length(table->old_tuplestores); i++)
+	{
+		appendStringInfo(&str, " UNION ALL ");
+		appendStringInfo(&str," SELECT * FROM %s",
+			make_delta_enr_name("old", table->table_id, i));
+	}
+
+	raw = (RawStmt*)linitial(raw_parser(str.data, RAW_PARSE_DEFAULT));
+	sub = transformStmt(pstate, raw->stmt);
+
+	/* If this query has setOperations, RTEs in rtables has a subquery which contains ENR */
+	if (sub->setOperations != NULL)
+	{
+		ListCell *lc;
+
+		/* add securityQuals for tuplestores */
+		foreach (lc, sub->rtable)
+		{
+			RangeTblEntry *rte;
+			RangeTblEntry *sub_rte;
+
+			rte = (RangeTblEntry *)lfirst(lc);
+			Assert(rte->subquery != NULL);
+
+			sub_rte = (RangeTblEntry *)linitial(rte->subquery->rtable);
+			if (sub_rte->rtekind == RTE_NAMEDTUPLESTORE)
+				/* rt_index is always 1, bacause subquery has enr_rte only */
+				sub_rte->securityQuals = get_securityQuals(sub_rte->relid, 1, sub);
+		}
+	}
+
+	/* save the original RTE */
+	table->original_rte = copyObject(rte);
+
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = sub;
+	rte->security_barrier = false;
+	/* Clear fields that should not be set in a subquery RTE */
+	rte->relid = InvalidOid;
+	rte->relkind = 0;
+	rte->rellockmode = 0;
+	rte->tablesample = NULL;
+	rte->inh = false;			/* must not be set for a subquery */
+
+	rte->requiredPerms = 0;		/* no permission check on subquery itself */
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->insertedCols = NULL;
+	rte->updatedCols = NULL;
+	rte->extraUpdatedCols = NULL;
+
+	return rte;
+}
+
+/*
+ * make_delta_enr_name
+ *
+ * Make a name for ENR of a transition table from the base table's oid.
+ * prefix will be "new" or "old" depending on its transition table kind..
+ */
+static char*
+make_delta_enr_name(const char *prefix, Oid relid, int count)
+{
+	char buf[NAMEDATALEN];
+	char *name;
+
+	snprintf(buf, NAMEDATALEN, "__ivm_%s_%u_%u", prefix, relid, count);
+	name = pstrdup(buf);
+
+	return name;
+}
+
+/*
+ * union_ENRs
+ *
+ * Make a single table delta by unionning all transition tables of the modified table
+ * whose RTE is specified by
+ */
+static RangeTblEntry*
+union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
+		   QueryEnvironment *queryEnv)
+{
+	StringInfoData str;
+	ParseState	*pstate;
+	RawStmt *raw;
+	Query *sub;
+	int	i;
+	RangeTblEntry *enr_rte;
+
+	/* Create a ParseState for rewriting the view definition query */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	initStringInfo(&str);
+
+	for (i = 0; i < list_length(enr_rtes); i++)
+	{
+		if (i > 0)
+			appendStringInfo(&str, " UNION ALL ");
+
+		appendStringInfo(&str,
+			" SELECT * FROM %s",
+			make_delta_enr_name(prefix, relid, i));
+	}
+
+	raw = (RawStmt*)linitial(raw_parser(str.data, RAW_PARSE_DEFAULT));
+	sub = transformStmt(pstate, raw->stmt);
+
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = sub;
+	rte->security_barrier = false;
+	/* Clear fields that should not be set in a subquery RTE */
+	rte->relid = InvalidOid;
+	rte->relkind = 0;
+	rte->rellockmode = 0;
+	rte->tablesample = NULL;
+	rte->inh = false;			/* must not be set for a subquery */
+
+	rte->requiredPerms = 0;		/* no permission check on subquery itself */
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->insertedCols = NULL;
+	rte->updatedCols = NULL;
+	rte->extraUpdatedCols = NULL;
+	/* if base table has RLS, set security condition to enr*/
+	enr_rte = (RangeTblEntry *)linitial(sub->rtable);
+	/* rt_index is always 1, bacause subquery has enr_rte only */
+	enr_rte->securityQuals = get_securityQuals(relid, 1, sub);
+
+	return rte;
+}
+
+/*
+ * rewrite_query_for_distinct
+ *
+ * Rewrite query for counting DISTINCT clause.
+ */
+static Query *
+rewrite_query_for_distinct(Query *query, ParseState *pstate)
+{
+	TargetEntry *tle_count;
+	FuncCall *fn;
+	Node *node;
+
+	/* Add count(*) for counting distinct tuples in views */
+	fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+	fn->agg_star = true;
+	if (!query->groupClause && !query->hasAggs)
+		query->groupClause = transformDistinctClause(NULL, &query->targetList, query->sortClause, false);
+
+	node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+	tle_count = makeTargetEntry((Expr *) node,
+								list_length(query->targetList) + 1,
+								pstrdup("__ivm_count__"),
+								false);
+	query->targetList = lappend(query->targetList, tle_count);
+	query->hasAggs = true;
+
+	return query;
+}
+
+/*
+ * calc_delta
+ *
+ * Calculate view deltas generated under the modification of a table specified
+ * by the RTE index.
+ */
+static void
+calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
+			DestReceiver *dest_old, DestReceiver *dest_new,
+			TupleDesc *tupdesc_old, TupleDesc *tupdesc_new,
+			QueryEnvironment *queryEnv)
+{
+	ListCell *lc = list_nth_cell(query->rtable, rte_index - 1);
+	RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+	/* Generate old delta */
+	if (list_length(table->old_rtes) > 0)
+	{
+		/* Replace the modified table with the old delta table and calculate the old view delta. */
+		lfirst(lc) = union_ENRs(rte, table->table_id, table->old_rtes, "old", queryEnv);
+		refresh_matview_datafill(dest_old, query, queryEnv, tupdesc_old, "");
+	}
+
+	/* Generate new delta */
+	if (list_length(table->new_rtes) > 0)
+	{
+		/* Replace the modified table with the new delta table and calculate the new view delta*/
+		lfirst(lc) = union_ENRs(rte, table->table_id, table->new_rtes, "new", queryEnv);
+		refresh_matview_datafill(dest_new, query, queryEnv, tupdesc_new, "");
+	}
+}
+
+/*
+ * rewrite_query_for_postupdate_state
+ *
+ * Rewrite the query so that the specified base table's RTEs will represent
+ * "post-update" state of tables. This is called after the view delta
+ * calculation due to changes on this table finishes.
+ */
+static Query*
+rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte_index)
+{
+	ListCell *lc = list_nth_cell(query->rtable, rte_index - 1);
+
+	/* Retore the original RTE */
+	lfirst(lc) = table->original_rte;
+
+	return query;
+}
+
+/*
+ * apply_delta
+ *
+ * Apply deltas to the materialized view. In outer join cases, this requires
+ * the view maintenance graph.
+ */
+static void
+apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
+			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
+			Query *query, bool use_count, char *count_colname)
+{
+	StringInfoData querybuf;
+	StringInfoData target_list_buf;
+	Relation	matviewRel;
+	char	   *matviewname;
+	ListCell	*lc;
+	int			i;
+	List	   *keys = NIL;
+
+
+	/*
+	 * get names of the materialized view and delta tables
+	 */
+
+	matviewRel = table_open(matviewOid, NoLock);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	/*
+	 * Build parts of the maintenance queries
+	 */
+
+	initStringInfo(&querybuf);
+	initStringInfo(&target_list_buf);
+
+	/* build string of target list */
+	for (i = 0; i < matviewRel->rd_att->natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char   *resname = NameStr(attr->attname);
+
+		if (i != 0)
+			appendStringInfo(&target_list_buf, ", ");
+		appendStringInfo(&target_list_buf, "%s", quote_qualified_identifier(NULL, resname));
+	}
+
+	i = 0;
+	foreach (lc, query->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char *resname = NameStr(attr->attname);
+
+		i++;
+
+		if (tle->resjunk)
+			continue;
+
+		keys = lappend(keys, resname);
+	}
+
+	/* Start maintaining the materialized view. */
+	OpenMatViewIncrementalMaintenance();
+
+	/* Open SPI context. */
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	/* For tuple deletion */
+	if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
+	{
+		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		int				rc;
+
+		/* convert tuplestores to ENR, and register for SPI */
+		enr->md.name = pstrdup(OLD_DELTA_ENRNAME);
+		enr->md.reliddesc = InvalidOid;
+		enr->md.tupdesc = tupdesc_old;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(old_tuplestores);
+		enr->reldata = old_tuplestores;
+
+		rc = SPI_register_relation(enr);
+		if (rc != SPI_OK_REL_REGISTER)
+			elog(ERROR, "SPI_register failed");
+
+		if (use_count)
+			/* apply old delta and get rows to be recalculated */
+			apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME,
+									   keys, count_colname);
+		else
+			apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys);
+
+	}
+	/* For tuple insertion */
+	if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
+	{
+		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		int rc;
+
+		/* convert tuplestores to ENR, and register for SPI */
+		enr->md.name = pstrdup(NEW_DELTA_ENRNAME);
+		enr->md.reliddesc = InvalidOid;
+		enr->md.tupdesc = tupdesc_new;;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(new_tuplestores);
+		enr->reldata = new_tuplestores;
+
+		rc = SPI_register_relation(enr);
+		if (rc != SPI_OK_REL_REGISTER)
+			elog(ERROR, "SPI_register failed");
+
+		/* apply new delta */
+		if (use_count)
+			apply_new_delta_with_count(matviewname, NEW_DELTA_ENRNAME,
+								keys, &target_list_buf, count_colname);
+		else
+			apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf);
+	}
+
+	/* We're done maintaining the materialized view. */
+	CloseMatViewIncrementalMaintenance();
+
+	table_close(matviewRel, NoLock);
+
+	/* Close SPI context. */
+	if (SPI_finish() != SPI_OK_FINISH)
+		elog(ERROR, "SPI_finish failed");
+}
+
+/*
+ * apply_old_delta_with_count
+ *
+ * Execute a query for applying a delta table given by deltname_old
+ * which contains tuples to be deleted from to a materialized view given by
+ * matviewname.  This is used when counting is required, that is, the view
+ * has aggregate or distinct.
+ */
+static void
+apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
+				List *keys, const char *count_colname)
+{
+	StringInfoData	querybuf;
+	char   *match_cond;
+
+	/* build WHERE condition for searching tuples to be deleted */
+	match_cond = get_matching_condition_string(keys);
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"WITH t AS ("			/* collecting tid of target tuples in the view */
+						"SELECT diff.%s, "			/* count column */
+								"(diff.%s OPERATOR(pg_catalog.=) mv.%s) AS for_dlt, "
+								"mv.ctid "
+						"FROM %s AS mv, %s AS diff "
+						"WHERE %s"					/* tuple matching condition */
+					"), updt AS ("			/* update a tuple if this is not to be deleted */
+						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.-) t.%s "
+						"FROM t WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND NOT for_dlt "
+					"), dlt AS ("			/* delete a tuple if this is to be deleted */
+						"DELETE FROM %s AS mv USING t "
+						"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt"
+					")",
+					count_colname,
+					count_colname, count_colname,
+					matviewname, deltaname_old,
+					match_cond,
+					matviewname, count_colname, count_colname, count_colname,
+					matviewname);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_old_delta
+ *
+ * Execute a query for applying a delta table given by deltname_old
+ * which contains tuples to be deleted from to a materialized view given by
+ * matviewname.  This is used when counting is not required.
+ */
+static void
+apply_old_delta(const char *matviewname, const char *deltaname_old,
+				List *keys)
+{
+	StringInfoData	querybuf;
+	StringInfoData	keysbuf;
+	char   *match_cond;
+	ListCell *lc;
+
+	/* build WHERE condition for searching tuples to be deleted */
+	match_cond = get_matching_condition_string(keys);
+
+	/* build string of keys list */
+	initStringInfo(&keysbuf);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char   *resname = NameStr(attr->attname);
+		appendStringInfo(&keysbuf, "%s", quote_qualified_identifier("mv", resname));
+		if (lnext(keys, lc))
+			appendStringInfo(&keysbuf, ", ");
+	}
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+	"DELETE FROM %s WHERE ctid IN ("
+		"SELECT tid FROM (SELECT row_number() over (partition by %s) AS \"__ivm_row_number__\","
+								  "mv.ctid AS tid,"
+								  "diff.\"__ivm_count__\""
+						 "FROM %s AS mv, %s AS diff "
+						 "WHERE %s) v "
+					"WHERE v.\"__ivm_row_number__\" OPERATOR(pg_catalog.<=) v.\"__ivm_count__\")",
+					matviewname,
+					keysbuf.data,
+					matviewname, deltaname_old,
+					match_cond);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_new_delta_with_count
+ *
+ * Execute a query for applying a delta table given by deltname_new
+ * which contains tuples to be inserted into a materialized view given by
+ * matviewname.  This is used when counting is required, that is, the view
+ * has aggregate or distinct. Also, when a table in EXISTS sub queries
+ * is modified.
+ */
+static void
+apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
+				List *keys, StringInfo target_list, const char* count_colname)
+{
+	StringInfoData	querybuf;
+	StringInfoData	returning_keys;
+	ListCell	*lc;
+	char	*match_cond = "";
+
+	/* build WHERE condition for searching tuples to be updated */
+	match_cond = get_matching_condition_string(keys);
+
+	/* build string of keys list */
+	initStringInfo(&returning_keys);
+	if (keys)
+	{
+		foreach (lc, keys)
+		{
+			Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+			char   *resname = NameStr(attr->attname);
+			appendStringInfo(&returning_keys, "%s", quote_qualified_identifier("mv", resname));
+			if (lnext(keys, lc))
+				appendStringInfo(&returning_keys, ", ");
+		}
+	}
+	else
+		appendStringInfo(&returning_keys, "NULL");
+
+	/* Search for matching tuples from the view and update if found or insert if not. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"WITH updt AS ("		/* update a tuple if this exists in the view */
+						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.+) diff.%s "
+						"FROM %s AS diff "
+						"WHERE %s "					/* tuple matching condition */
+						"RETURNING %s"				/* returning keys of updated tuples */
+					") INSERT INTO %s (%s)"	/* insert a new tuple if this doesn't existw */
+						"SELECT %s FROM %s AS diff "
+						"WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE %s);",
+					matviewname, count_colname, count_colname, count_colname,
+					deltaname_new,
+					match_cond,
+					returning_keys.data,
+					matviewname, target_list->data,
+					target_list->data, deltaname_new,
+					match_cond);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_new_delta
+ *
+ * Execute a query for applying a delta table given by deltname_new
+ * which contains tuples to be inserted into a materialized view given by
+ * matviewname.  This is used when counting is not required.
+ */
+static void
+apply_new_delta(const char *matviewname, const char *deltaname_new,
+				StringInfo target_list)
+{
+	StringInfoData	querybuf;
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"INSERT INTO %s (%s) SELECT %s FROM ("
+						"SELECT diff.*, generate_series(1, diff.\"__ivm_count__\") "
+						"FROM %s AS diff) AS v",
+					matviewname, target_list->data, target_list->data,
+					deltaname_new);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * get_matching_condition_string
+ *
+ * Build a predicate string for looking for a tuple with given keys.
+ */
+static char *
+get_matching_condition_string(List *keys)
+{
+	StringInfoData match_cond;
+	ListCell	*lc;
+
+	/* If there is no key columns, the condition is always true. */
+	if (keys == NIL)
+		return "true";
+
+	initStringInfo(&match_cond);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char   *resname = NameStr(attr->attname);
+		char   *mv_resname = quote_qualified_identifier("mv", resname);
+		char   *diff_resname = quote_qualified_identifier("diff", resname);
+		Oid		typid = attr->atttypid;
+
+		/* Considering NULL values, we can not use simple = operator. */
+		appendStringInfo(&match_cond, "(");
+		generate_equal(&match_cond, typid, mv_resname, diff_resname);
+		appendStringInfo(&match_cond, " OR (%s IS NULL AND %s IS NULL))",
+						 mv_resname, diff_resname);
+
+		if (lnext(keys, lc))
+			appendStringInfo(&match_cond, " AND ");
+	}
+
+	return match_cond.data;
+}
+
+/*
+ * generate_equals
+ *
+ * Generate an equality clause using given operands' default equality
+ * operator.
+ */
+static void
+generate_equal(StringInfo querybuf, Oid opttype,
+			   const char *leftop, const char *rightop)
+{
+	TypeCacheEntry *typentry;
+
+	typentry = lookup_type_cache(opttype, TYPECACHE_EQ_OPR);
+	if (!OidIsValid(typentry->eq_opr))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_FUNCTION),
+				 errmsg("could not identify an equality operator for type %s",
+						format_type_be(opttype))));
+
+	generate_operator_clause(querybuf,
+							 leftop, opttype,
+							 typentry->eq_opr,
+							 rightop, opttype);
+}
+
+/*
+ * mv_InitHashTables
+ */
+static void
+mv_InitHashTables(void)
+{
+	HASHCTL		ctl;
+
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(MV_TriggerHashEntry);
+	mv_trigger_info = hash_create("MV trigger info",
+								 MV_INIT_QUERYHASHSIZE,
+								 &ctl, HASH_ELEM | HASH_BLOBS);
+}
+
+/*
+ * AtAbort_IVM
+ *
+ * Clean up hash entries for all materialized views. This is called at
+ * transaction abort.
+ */
+void
+AtAbort_IVM()
+{
+	HASH_SEQ_STATUS seq;
+	MV_TriggerHashEntry *entry;
+
+	if (mv_trigger_info)
+	{
+		hash_seq_init(&seq, mv_trigger_info);
+		while ((entry = hash_seq_search(&seq)) != NULL)
+			clean_up_IVM_hash_entry(entry);
+	}
+}
+
+/*
+ * clean_up_IVM_hash_entry
+ *
+ * Clean up tuple stores and hash entries for a materialized view after its
+ * maintenance finished.
+ */
+static void
+clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry)
+{
+	bool found;
+	ListCell *lc;
+
+	foreach(lc, entry->tables)
+	{
+		MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc);
+
+		list_free(table->old_tuplestores);
+		list_free(table->new_tuplestores);
+	}
+	list_free(entry->tables);
+
+	hash_search(mv_trigger_info, (void *) &entry->matview_id, HASH_REMOVE, &found);
+}
+
+/*
+ * isIvmName
+ *
+ * Check if this is a IVM hidden column from the name.
+ */
+bool
+isIvmName(const char *s)
+{
+	if (s)
+		return (strncmp(s, "__ivm_", 6) == 0);
+	return false;
+}
+
+/*
+ * get_securityQuals
+ *
+ * Get row security policy on a relation.
+ * This is used by IVM for copying RLS from base table to enr.
+ */
+static List *
+get_securityQuals(Oid relId, int rt_index, Query *query)
+{
+	ParseState *pstate;
+	Relation rel;
+	ParseNamespaceItem *nsitem;
+	RangeTblEntry *rte;
+	List *securityQuals;
+	List *withCheckOptions;
+	bool  hasRowSecurity;
+	bool  hasSubLinks;
+
+	securityQuals = NIL;
+	pstate = make_parsestate(NULL);
+
+	rel = table_open(relId, NoLock);
+	nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, false);
+	rte = nsitem->p_rte;
+
+	get_row_security_policies(query, rte, rt_index,
+							  &securityQuals, &withCheckOptions,
+							  &hasRowSecurity, &hasSubLinks);
+
+	/*
+	 * Make sure the query is marked correctly if row level security
+	 * applies, or if the new quals had sublinks.
+	 */
+	if (hasRowSecurity)
+		query->hasRowSecurity = true;
+	if (hasSubLinks)
+		query->hasSubLinks = true;
+
+	table_close(rel, NoLock);
+
+	return securityQuals;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..a91a0e9ca1 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -50,6 +50,7 @@
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
+#include "commands/matview.h"
 #include "commands/event_trigger.h"
 #include "commands/policy.h"
 #include "commands/sequence.h"
@@ -3394,6 +3395,14 @@ renameatt_internal(Oid myrelid,
 	targetrelation = relation_open(myrelid, AccessExclusiveLock);
 	renameatt_check(myrelid, RelationGetForm(targetrelation), recursing);
 
+	/*
+	 * Don't rename IVM columns.
+	 */
+	if (RelationIsIVM(targetrelation) && isIvmName(oldattname))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("IVM column can not be renamed")));
+
 	/*
 	 * if the 'recurse' flag is set then we are supposed to rename this
 	 * attribute in all classes that inherit from 'relname' (as well as in
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 17be377aa7..5235f2169e 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2460,6 +2460,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(relisivm);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index ed74a5022c..c60a5f9bee 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2744,6 +2744,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(relisivm);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index f6166f7859..8133850e98 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3253,6 +3253,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 1e55a58f69..96fa85758f 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1443,6 +1443,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index c5c3f26ecf..4a52035371 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -36,6 +36,7 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 #include "utils/varlena.h"
+#include "commands/matview.h"
 
 
 /*
@@ -79,7 +80,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							int count, int offset,
 							int rtindex, int sublevels_up,
 							int location, bool include_dropped,
-							List **colnames, List **colvars);
+							List **colnames, List **colvars, bool is_ivm);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
 
@@ -1433,6 +1434,7 @@ addRangeTableEntry(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -1521,6 +1523,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -2676,7 +2679,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 						expandTupleDesc(tupdesc, rte->eref,
 										rtfunc->funccolcount, atts_done,
 										rtindex, sublevels_up, location,
-										include_dropped, colnames, colvars);
+										include_dropped, colnames, colvars, false);
 					}
 					else if (functypclass == TYPEFUNC_SCALAR)
 					{
@@ -2944,7 +2947,7 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
 	expandTupleDesc(rel->rd_att, eref, rel->rd_att->natts, 0,
 					rtindex, sublevels_up,
 					location, include_dropped,
-					colnames, colvars);
+					colnames, colvars, RelationIsIVM(rel));
 	relation_close(rel, AccessShareLock);
 }
 
@@ -2961,7 +2964,7 @@ static void
 expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 				int rtindex, int sublevels_up,
 				int location, bool include_dropped,
-				List **colnames, List **colvars)
+				List **colnames, List **colvars, bool is_ivm)
 {
 	ListCell   *aliascell;
 	int			varattno;
@@ -2974,6 +2977,9 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 	{
 		Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
 
+		if (is_ivm && isIvmName(NameStr(attr->attname)) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		if (attr->attisdropped)
 		{
 			if (include_dropped)
@@ -3127,6 +3133,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 		Var		   *varnode = (Var *) lfirst(var);
 		TargetEntry *te;
 
+		/* if transform * into columnlist with IMMV, remove IVM columns */
+		if (rte->relisivm && isIvmName(label) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		te = makeTargetEntry((Expr *) varnode,
 							 (AttrNumber) pstate->p_next_resno++,
 							 label,
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 6589345ac4..02f33d404b 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -776,7 +776,8 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 														attr->atttypmod))));
 	}
 
-	if (i != resultDesc->natts)
+	/* No check for materialized views since this could have special columns for IVM */
+	if ((!isSelect || requireColumnNameMatch) && i != resultDesc->natts)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 isSelect ?
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d068d6532e..4845e71898 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11689,4 +11689,12 @@
   prorettype => 'bytea', proargtypes => 'pg_brin_minmax_multi_summary',
   prosrc => 'brin_minmax_multi_summary_send' },
 
+# IVM
+{ oid => '786', descr => 'ivm trigger (before)',
+  proname => 'IVM_immediate_before', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_before' },
+{ oid => '787', descr => 'ivm trigger (after)',
+  proname => 'IVM_immediate_maintenance', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_maintenance' },
+
 ]
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index ad5054d116..a57ce463e1 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -16,6 +16,7 @@
 
 #include "catalog/objectaddress.h"
 #include "nodes/params.h"
+#include "nodes/pathnodes.h"
 #include "parser/parse_node.h"
 #include "tcop/dest.h"
 #include "utils/queryenvironment.h"
@@ -25,6 +26,10 @@ extern ObjectAddress ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *st
 									   ParamListInfo params, QueryEnvironment *queryEnv,
 									   QueryCompletion *qc);
 
+extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create);
+
+extern Query *rewriteQueryForIMMV(Query *query, List *colNames);
+
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
 
 extern DestReceiver *CreateIntoRelDestReceiver(IntoClause *intoClause);
diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h
index 214b1c1df6..13a5722f17 100644
--- a/src/include/commands/matview.h
+++ b/src/include/commands/matview.h
@@ -15,6 +15,7 @@
 #define MATVIEW_H
 
 #include "catalog/objectaddress.h"
+#include "fmgr.h"
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
 #include "tcop/dest.h"
@@ -23,6 +24,8 @@
 
 extern void SetMatViewPopulatedState(Relation relation, bool newstate);
 
+extern void SetMatViewIVMState(Relation relation, bool newstate);
+
 extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										ParamListInfo params, QueryCompletion *qc);
 
@@ -30,4 +33,9 @@ extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
 
 extern bool MatViewIncrementalMaintenanceIsEnabled(void);
 
+extern Datum IVM_immediate_before(PG_FUNCTION_ARGS);
+extern Datum IVM_immediate_maintenance(PG_FUNCTION_ARGS);
+extern void AtAbort_IVM(void);
+extern bool isIvmName(const char *s);
+
 #endif							/* MATVIEW_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..e7d3b00971 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1035,6 +1035,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		relisivm;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -2193,6 +2194,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		ivm;			/* incremental view maintenance is used by materialized view */
 } CreateStmt;
 
 /* ----------
-- 
2.17.1

v24-0008-Add-aggregates-support-in-IVM.patchtext/x-diff; name=v24-0008-Add-aggregates-support-in-IVM.patchDownload
From 0a70e7daae290278ada2d8bb5856372ee42783c7 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Mon, 2 Aug 2021 14:59:27 +0900
Subject: [PATCH v24 08/15] Add aggregates support in IVM
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Currently, count, sum, avg, min and max are supported.

As a restriction, expressions specified in GROUP BY must appear in
the target list because tuples to be updated in IMMV are identified
by using this group key. However, in the case of aggregates without
GROUP BY, there is only one tuple in the view, so keys are not uses
to identify tuples.

When creating a IMMV, in addition to __ivm_count column, some hidden
columns for each aggregate are added to the target list. For example,
names of these hidden columns are ivm_count_avg and ivm_sum_avg for
the average function, and so on.

In the case of views without aggregate functions, only the number of
tuple multiplicities in __ivm_count__ column are updated at incremental
maintenance. On the other hand, in the case of view with aggregates,
the aggregated values and related hidden columns are also updated. The
way of update depends the kind of aggregate function. Specifically,
sum and count are updated by simply adding or subtracting delta value
calculated from delta tables. avg is updated by using values of sum
and count stored in views as hidden columns and deltas calculated
from delta tables.

In min or max cases, it becomes more complicated. For an example of min,
when tuples are inserted, the smaller value between the current min value
in the view and the value calculated from the new delta table is used.
When tuples are deleted, if the current min value in the view equals to
the min in the old delta table, we need re-computation the latest min
value from base tables. Otherwise, the current value in the view remains.

As to sum, avg, min, and max (any aggregate functions except count),
NULL in input values is ignored, and this returns a null value when no
rows are selected. To support this specification, the number of not-NULL
input values is counted and stored in views as a hidden column. In the
case of count(), count(x) returns zero when no rows are selected, and
count(*) doesn't ignore NULL input. These specification are also supported.
---
 src/backend/commands/createas.c |  299 ++++++++-
 src/backend/commands/matview.c  | 1016 ++++++++++++++++++++++++++++++-
 src/include/commands/createas.h |    1 +
 3 files changed, 1281 insertions(+), 35 deletions(-)

diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 91888891bf..ce6e6ffe33 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/regproc.h"
 #include "utils/rel.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
@@ -80,6 +81,11 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_intorel;
 
+typedef struct
+{
+	bool	has_agg;
+} check_ivm_restriction_context;
+
 /* utility functions for CTAS definition creation */
 static ObjectAddress create_ctas_internal(List *attrList, IntoClause *into);
 static ObjectAddress create_ctas_nodata(List *tlist, IntoClause *into);
@@ -94,9 +100,10 @@ static void CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid mat
 									 Relids *relids, bool ex_lock);
 static void CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock);
 static void check_ivm_restriction(Node *node);
-static bool check_ivm_restriction_walker(Node *node, void *context);
+static bool check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context);
 static void CreateIndexOnIMMV(Query *query, Relation matviewRel);
 static Bitmapset *get_primary_key_attnos_from_query(Query *qry, List **constraintList);
+static bool check_aggregate_supports_ivm(Oid aggfnoid);
 
 /*
  * create_ctas_internal
@@ -432,6 +439,7 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
  * rewriteQueryForIMMV -- rewrite view definition query for IMMV
  *
  * count(*) is added for counting distinct tuples in views.
+ * Also, additional hidden columns are added for aggregate values.
  */
 Query *
 rewriteQueryForIMMV(Query *query, List *colNames)
@@ -446,14 +454,46 @@ rewriteQueryForIMMV(Query *query, List *colNames)
 	rewritten = copyObject(query);
 	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
 
-	/*
-	 * Convert DISTINCT to GROUP BY and add count(*) for counting distinct
-	 * tuples in views.
-	 */
-	if (rewritten->distinctClause)
+	/* group keys must be in targetlist */
+	if (rewritten->groupClause)
 	{
+		ListCell *lc;
+		foreach(lc, rewritten->groupClause)
+		{
+			SortGroupClause *scl = (SortGroupClause *) lfirst(lc);
+			TargetEntry *tle = get_sortgroupclause_tle(scl, rewritten->targetList);
+
+			if (tle->resjunk)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("GROUP BY expression not appearing in select list is not supported on incrementally maintainable materialized view")));
+		}
+	}
+	/* Convert DISTINCT to GROUP BY.  count(*) will be added afterward. */
+	else if (!rewritten->hasAggs && rewritten->distinctClause)
 		rewritten->groupClause = transformDistinctClause(NULL, &rewritten->targetList, rewritten->sortClause, false);
 
+	/* Add additional columns for aggregate values */
+	if (rewritten->hasAggs)
+	{
+		ListCell *lc;
+		List *aggs = NIL;
+		AttrNumber next_resno = list_length(rewritten->targetList) + 1;
+
+		foreach(lc, rewritten->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+			char *resname = (colNames == NIL ? tle->resname : strVal(list_nth(colNames, tle->resno - 1)));
+
+			if (IsA(tle->expr, Aggref))
+				makeIvmAggColumn(pstate, (Aggref *)tle->expr, resname, &next_resno, &aggs);
+		}
+		rewritten->targetList = list_concat(rewritten->targetList, aggs);
+	}
+
+	/* Add count(*) for counting distinct tuples in views */
+	if (rewritten->distinctClause || rewritten->hasAggs)
+	{
 		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
 		fn->agg_star = true;
 
@@ -470,6 +510,91 @@ rewriteQueryForIMMV(Query *query, List *colNames)
 	return rewritten;
 }
 
+/*
+ * makeIvmAggColumn -- make additional aggregate columns for IVM
+ *
+ * For an aggregate column specified by aggref, additional aggregate columns
+ * are added, which are used to calculate the new aggregate value in IMMV.
+ * An additional aggregate columns has a name based on resname
+ * (ex. ivm_count_resname), and resno specified by next_resno. The created
+ * columns are returned to aggs, and the resno for the next column is also
+ * returned to next_resno.
+ *
+ * Currently, an additional count() is created for aggref other than count.
+ * In addition, sum() is created for avg aggregate column.
+ */
+void
+makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber *next_resno, List **aggs)
+{
+	TargetEntry *tle_count;
+	Node *node;
+	FuncCall *fn;
+	Const	*dmy_arg = makeConst(INT4OID,
+								 -1,
+								 InvalidOid,
+								 sizeof(int32),
+								 Int32GetDatum(1),
+								 false,
+								 true); /* pass by value */
+	const char *aggname = get_func_name(aggref->aggfnoid);
+
+	/*
+	 * For aggregate functions except count, add count() func with the same arg parameters.
+	 * This count result is used for determining if the aggregate value should be NULL or not.
+	 * Also, add sum() func for avg because we need to calculate an average value as sum/count.
+	 *
+	 * XXX: If there are same expressions explicitly in the target list, we can use this instead
+	 * of adding new duplicated one.
+	 */
+	if (strcmp(aggname, "count") != 0)
+	{
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+
+		/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+		node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+		((Aggref *)node)->args = aggref->args;
+
+		tle_count = makeTargetEntry((Expr *) node,
+									*next_resno,
+									pstrdup(makeObjectName("__ivm_count",resname, "_")),
+									false);
+		*aggs = lappend(*aggs, tle_count);
+		(*next_resno)++;
+	}
+	if (strcmp(aggname, "avg") == 0)
+	{
+		List *dmy_args = NIL;
+		ListCell *lc;
+		foreach(lc, aggref->aggargtypes)
+		{
+			Oid		typeid = lfirst_oid(lc);
+			Type	type = typeidType(typeid);
+
+			Const *con = makeConst(typeid,
+								   -1,
+								   typeTypeCollation(type),
+								   typeLen(type),
+								   (Datum) 0,
+								   true,
+								   typeByVal(type));
+			dmy_args = lappend(dmy_args, con);
+			ReleaseSysCache(type);
+		}
+		fn = makeFuncCall(list_make1(makeString("sum")), NIL, COERCE_EXPLICIT_CALL, -1);
+
+		/* Make a Func with dummy args, and then override this by the original agg's args. */
+		node = ParseFuncOrColumn(pstate, fn->funcname, dmy_args, NULL, fn, false, -1);
+		((Aggref *)node)->args = aggref->args;
+
+		tle_count = makeTargetEntry((Expr *) node,
+									*next_resno,
+									pstrdup(makeObjectName("__ivm_sum",resname, "_")),
+									false);
+		*aggs = lappend(*aggs, tle_count);
+		(*next_resno)++;
+	}
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -923,11 +1048,13 @@ CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock
 static void
 check_ivm_restriction(Node *node)
 {
-	check_ivm_restriction_walker(node, NULL);
+	check_ivm_restriction_context context = {false};
+
+	check_ivm_restriction_walker(node, &context);
 }
 
 static bool
-check_ivm_restriction_walker(Node *node, void *context)
+check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context)
 {
 	if (node == NULL)
 		return false;
@@ -1008,6 +1135,8 @@ check_ivm_restriction_walker(Node *node, void *context)
 					}
 				}
 
+				context->has_agg |= qry->hasAggs;
+
 				/* restrictions for rtable */
 				foreach(lc, qry->rtable)
 				{
@@ -1056,7 +1185,7 @@ check_ivm_restriction_walker(Node *node, void *context)
 
 				}
 
-				query_tree_walker(qry, check_ivm_restriction_walker, NULL, QTW_IGNORE_RANGE_TABLE);
+				query_tree_walker(qry, check_ivm_restriction_walker, (void *) context, QTW_IGNORE_RANGE_TABLE);
 
 				break;
 			}
@@ -1067,8 +1196,12 @@ check_ivm_restriction_walker(Node *node, void *context)
 						ereport(ERROR,
 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								 errmsg("column name %s is not supported on incrementally maintainable materialized view", tle->resname)));
+				if (context->has_agg && !IsA(tle->expr, Aggref) && contain_aggs_of_level((Node *) tle->expr, 0))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("expression containing an aggregate in it is not supported on incrementally maintainable materialized view")));
 
-				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
 				break;
 			}
 		case T_JoinExpr:
@@ -1080,15 +1213,128 @@ check_ivm_restriction_walker(Node *node, void *context)
 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								 errmsg("OUTER JOIN is not supported on incrementally maintainable materialized view")));
 
-				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
+				break;
 			}
-			break;
-			expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+		case T_Aggref:
+			{
+				/* Check if this supports IVM */
+				Aggref *aggref = (Aggref *) node;
+				const char *aggname = format_procedure(aggref->aggfnoid);
+
+				if (aggref->aggfilter != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with FILTER clause is not supported on incrementally maintainable materialized view")));
+
+				if (aggref->aggdistinct != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with DISTINCT arguments is not supported on incrementally maintainable materialized view")));
+
+				if (aggref->aggorder != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with ORDER clause is not supported on incrementally maintainable materialized view")));
+
+				if (!check_aggregate_supports_ivm(aggref->aggfnoid))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function %s is not supported on incrementally maintainable materialized view", aggname)));
+				break;
+			}
+		default:
+			expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
 			break;
 	}
 	return false;
 }
 
+/*
+ * check_aggregate_supports_ivm
+ *
+ * Check if the given aggregate function is supporting IVM
+ */
+static bool
+check_aggregate_supports_ivm(Oid aggfnoid)
+{
+	switch (aggfnoid)
+	{
+		/* count */
+		case F_COUNT_ANY:
+		case F_COUNT_:
+
+		/* sum */
+		case F_SUM_INT8:
+		case F_SUM_INT4:
+		case F_SUM_INT2:
+		case F_SUM_FLOAT4:
+		case F_SUM_FLOAT8:
+		case F_SUM_MONEY:
+		case F_SUM_INTERVAL:
+		case F_SUM_NUMERIC:
+
+		/* avg */
+		case F_AVG_INT8:
+		case F_AVG_INT4:
+		case F_AVG_INT2:
+		case F_AVG_NUMERIC:
+		case F_AVG_FLOAT4:
+		case F_AVG_FLOAT8:
+		case F_AVG_INTERVAL:
+
+		/* min */
+		case F_MIN_ANYARRAY:
+		case F_MIN_INT8:
+		case F_MIN_INT4:
+		case F_MIN_INT2:
+		case F_MIN_OID:
+		case F_MIN_FLOAT4:
+		case F_MIN_FLOAT8:
+		case F_MIN_DATE:
+		case F_MIN_TIME:
+		case F_MIN_TIMETZ:
+		case F_MIN_MONEY:
+		case F_MIN_TIMESTAMP:
+		case F_MIN_TIMESTAMPTZ:
+		case F_MIN_INTERVAL:
+		case F_MIN_TEXT:
+		case F_MIN_NUMERIC:
+		case F_MIN_BPCHAR:
+		case F_MIN_TID:
+		case F_MIN_ANYENUM:
+		case F_MIN_INET:
+		case F_MIN_PG_LSN:
+
+		/* max */
+		case F_MAX_ANYARRAY:
+		case F_MAX_INT8:
+		case F_MAX_INT4:
+		case F_MAX_INT2:
+		case F_MAX_OID:
+		case F_MAX_FLOAT4:
+		case F_MAX_FLOAT8:
+		case F_MAX_DATE:
+		case F_MAX_TIME:
+		case F_MAX_TIMETZ:
+		case F_MAX_MONEY:
+		case F_MAX_TIMESTAMP:
+		case F_MAX_TIMESTAMPTZ:
+		case F_MAX_INTERVAL:
+		case F_MAX_TEXT:
+		case F_MAX_NUMERIC:
+		case F_MAX_BPCHAR:
+		case F_MAX_TID:
+		case F_MAX_ANYENUM:
+		case F_MAX_INET:
+		case F_MAX_PG_LSN:
+			return true;
+
+		default:
+			return false;
+	}
+}
+
 /*
  * CreateIndexOnIMMV
  *
@@ -1138,7 +1384,30 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel)
 	index->concurrent = false;
 	index->if_not_exists = false;
 
-	if (qry->distinctClause)
+
+	if (qry->groupClause)
+	{
+		/* create unique constraint on GROUP BY expression columns */
+		foreach(lc, qry->groupClause)
+		{
+			SortGroupClause *scl = (SortGroupClause *) lfirst(lc);
+			TargetEntry *tle = get_sortgroupclause_tle(scl, qry->targetList);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+			IndexElem  *iparam;
+
+			iparam = makeNode(IndexElem);
+			iparam->name = pstrdup(NameStr(attr->attname));
+			iparam->expr = NULL;
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->opclassopts = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+		}
+	}
+	else if (qry->distinctClause)
 	{
 		/* create unique constraint on all columns */
 		foreach(lc, qry->targetList)
@@ -1197,7 +1466,7 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel)
 					(errmsg("could not create an index on materialized view \"%s\" automatically",
 							RelationGetRelationName(matviewRel)),
 					 errdetail("This target list does not have all the primary key columns, "
-							   "or this view does not contain DISTINCT clause."),
+							   "or this view does not contain GROUP BY or DISTINCT clause."),
 					 errhint("Create an index on the materialized view for efficient incremental maintenance.")));
 			return;
 		}
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 70e35e5a63..8a06b4e799 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -81,6 +81,32 @@ typedef struct
 
 #define MV_INIT_QUERYHASHSIZE	16
 
+/* MV query type codes */
+#define MV_PLAN_RECALC			1
+#define MV_PLAN_SET_VALUE		2
+
+/*
+ * MI_QueryKey
+ *
+ * The key identifying a prepared SPI plan in our query hashtable
+ */
+typedef struct MV_QueryKey
+{
+	Oid			matview_id;	/* OID of materialized view */
+	int32		query_type;	/* query type ID, see MV_PLAN_XXX above */
+} MV_QueryKey;
+
+/*
+ * MV_QueryHashEntry
+ *
+ * Hash entry for cached plans used to maintain materialized views.
+ */
+typedef struct MV_QueryHashEntry
+{
+	MV_QueryKey key;
+	SPIPlanPtr	plan;
+} MV_QueryHashEntry;
+
 /*
  * MV_TriggerHashEntry
  *
@@ -117,8 +143,16 @@ typedef struct MV_TriggerTable
 	RangeTblEntry *original_rte;	/* the original RTE saved before rewriting query */
 } MV_TriggerTable;
 
+static HTAB *mv_query_cache = NULL;
 static HTAB *mv_trigger_info = NULL;
 
+/* kind of IVM operation for the view */
+typedef enum
+{
+	IVM_ADD,
+	IVM_SUB
+} IvmOp;
+
 /* ENR name for materialized view delta */
 #define NEW_DELTA_ENRNAME "new_delta"
 #define OLD_DELTA_ENRNAME "old_delta"
@@ -152,7 +186,7 @@ static RangeTblEntry *get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *tabl
 				 QueryEnvironment *queryEnv);
 static RangeTblEntry *union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
 		   QueryEnvironment *queryEnv);
-static Query *rewrite_query_for_distinct(Query *query, ParseState *pstate);
+static Query *rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate);
 
 static void calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
 			DestReceiver *dest_old, DestReceiver *dest_new,
@@ -163,19 +197,48 @@ static Query *rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *
 static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
 			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
 			Query *query, bool use_count, char *count_colname);
+static void append_set_clause_for_count(const char *resname, StringInfo buf_old,
+							StringInfo buf_new,StringInfo aggs_list);
+static void append_set_clause_for_sum(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list);
+static void append_set_clause_for_avg(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list,
+						  const char *aggtype);
+static void append_set_clause_for_minmax(const char *resname, StringInfo buf_old,
+							 StringInfo buf_new, StringInfo aggs_list,
+							 bool is_min);
+static char *get_operation_string(IvmOp op, const char *col, const char *arg1, const char *arg2,
+					 const char* count_col, const char *castType);
+static char *get_null_condition_string(IvmOp op, const char *arg1, const char *arg2,
+						  const char* count_col);
 static void apply_old_delta(const char *matviewname, const char *deltaname_old,
 				List *keys);
 static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
-				List *keys, const char *count_colname);
+				List *keys, StringInfo aggs_list, StringInfo aggs_set,
+				List *minmax_list, List *is_min_list,
+				const char *count_colname,
+				SPITupleTable **tuptable_recalc, uint64 *num_recalc);
 static void apply_new_delta(const char *matviewname, const char *deltaname_new,
 				StringInfo target_list);
 static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
-				List *keys, StringInfo target_list, const char* count_colname);
+				List *keys, StringInfo target_list, StringInfo aggs_set,
+				const char* count_colname);
 static char *get_matching_condition_string(List *keys);
+static char *get_returning_string(List *minmax_list, List *is_min_list, List *keys);
+static char *get_minmax_recalc_condition_string(List *minmax_list, List *is_min_list);
+static char *get_select_for_recalc_string(List *keys);
+static void recalc_and_set_values(SPITupleTable *tuptable_recalc, int64 num_tuples,
+					  List *namelist, List *keys, Relation matviewRel);
+static SPIPlanPtr get_plan_for_recalc(Oid matviewOid, List *namelist, List *keys, Oid *keyTypes);
+static SPIPlanPtr get_plan_for_set_values(Oid matviewOid, char *matviewname, List *namelist,
+						Oid *valTypes);
 static void generate_equal(StringInfo querybuf, Oid opttype,
 			   const char *leftop, const char *rightop);
 
 static void mv_InitHashTables(void);
+static SPIPlanPtr mv_FetchPreparedPlan(MV_QueryKey *key);
+static void mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan);
+static void mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type);
 static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry);
 
 static List *get_securityQuals(Oid relId, int rt_index, Query *query);
@@ -1440,8 +1503,8 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
 	rewritten = rewrite_query_for_preupdate_state(rewritten, entry->tables,
 												  entry->xid, entry->cid,
 												  pstate);
-	/* Rewrite for DISTINCT clause */
-	rewritten = rewrite_query_for_distinct(rewritten, pstate);
+	/* Rewrite for DISTINCT clause and aggregates functions */
+	rewritten = rewrite_query_for_distinct_and_aggregates(rewritten, pstate);
 
 	/* Create tuplestores to store view deltas */
 	if (entry->has_old)
@@ -1492,7 +1555,7 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
 
 			count_colname = pstrdup("__ivm_count__");
 
-			if (query->distinctClause)
+			if (query->hasAggs || query->distinctClause)
 				use_count = true;
 
 			/* calculate delta tables */
@@ -1854,17 +1917,34 @@ union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
 }
 
 /*
- * rewrite_query_for_distinct
+ * rewrite_query_for_distinct_and_aggregates
  *
- * Rewrite query for counting DISTINCT clause.
+ * Rewrite query for counting DISTINCT clause and aggregate functions.
  */
 static Query *
-rewrite_query_for_distinct(Query *query, ParseState *pstate)
+rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate)
 {
 	TargetEntry *tle_count;
 	FuncCall *fn;
 	Node *node;
 
+	/* For aggregate views */
+	if (query->hasAggs)
+	{
+		ListCell *lc;
+		List *aggs = NIL;
+		AttrNumber next_resno = list_length(query->targetList) + 1;
+
+		foreach(lc, query->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+			if (IsA(tle->expr, Aggref))
+				makeIvmAggColumn(pstate, (Aggref *)tle->expr, tle->resname, &next_resno, &aggs);
+		}
+		query->targetList = list_concat(query->targetList, aggs);
+	}
+
 	/* Add count(*) for counting distinct tuples in views */
 	fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
 	fn->agg_star = true;
@@ -1933,6 +2013,8 @@ rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte
 	return query;
 }
 
+#define IVM_colname(type, col) makeObjectName("__ivm_" type, col, "_")
+
 /*
  * apply_delta
  *
@@ -1946,11 +2028,16 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 {
 	StringInfoData querybuf;
 	StringInfoData target_list_buf;
+	StringInfo	aggs_list_buf = NULL;
+	StringInfo	aggs_set_old = NULL;
+	StringInfo	aggs_set_new = NULL;
 	Relation	matviewRel;
 	char	   *matviewname;
 	ListCell	*lc;
 	int			i;
 	List	   *keys = NIL;
+	List	   *minmax_list = NIL;
+	List	   *is_min_list = NIL;
 
 
 	/*
@@ -1968,6 +2055,15 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 	initStringInfo(&querybuf);
 	initStringInfo(&target_list_buf);
 
+	if (query->hasAggs)
+	{
+		if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
+			aggs_set_old = makeStringInfo();
+		if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
+			aggs_set_new = makeStringInfo();
+		aggs_list_buf = makeStringInfo();
+	}
+
 	/* build string of target list */
 	for (i = 0; i < matviewRel->rd_att->natts; i++)
 	{
@@ -1991,7 +2087,65 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		if (tle->resjunk)
 			continue;
 
-		keys = lappend(keys, resname);
+		/*
+		 * For views without aggregates, all attributes are used as keys to identify a
+		 * tuple in a view.
+		 */
+		if (!query->hasAggs)
+			keys = lappend(keys, attr);
+
+		/* For views with aggregates, we need to build SET clause for updating aggregate
+		 * values. */
+		if (query->hasAggs && IsA(tle->expr, Aggref))
+		{
+			Aggref *aggref = (Aggref *) tle->expr;
+			const char *aggname = get_func_name(aggref->aggfnoid);
+
+			/*
+			 * We can use function names here because it is already checked if these
+			 * can be used in IMMV by its OID at the definition time.
+			 */
+
+			/* count */
+			if (!strcmp(aggname, "count"))
+				append_set_clause_for_count(resname, aggs_set_old, aggs_set_new, aggs_list_buf);
+
+			/* sum */
+			else if (!strcmp(aggname, "sum"))
+				append_set_clause_for_sum(resname, aggs_set_old, aggs_set_new, aggs_list_buf);
+
+			/* avg */
+			else if (!strcmp(aggname, "avg"))
+				append_set_clause_for_avg(resname, aggs_set_old, aggs_set_new, aggs_list_buf,
+										  format_type_be(aggref->aggtype));
+
+			/* min/max */
+			else if (!strcmp(aggname, "min") || !strcmp(aggname, "max"))
+			{
+				bool	is_min = (!strcmp(aggname, "min"));
+
+				append_set_clause_for_minmax(resname, aggs_set_old, aggs_set_new, aggs_list_buf, is_min);
+
+				/* make a resname list of min and max aggregates */
+				minmax_list = lappend(minmax_list, resname);
+				is_min_list = lappend_int(is_min_list, is_min);
+			}
+			else
+				elog(ERROR, "unsupported aggregate function: %s", aggname);
+		}
+	}
+
+	/* If we have GROUP BY clause, we use its entries as keys. */
+	if (query->hasAggs && query->groupClause)
+	{
+		foreach (lc, query->groupClause)
+		{
+			SortGroupClause *sgcl = (SortGroupClause *) lfirst(lc);
+			TargetEntry		*tle = get_sortgroupclause_tle(sgcl, query->targetList);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+
+			keys = lappend(keys, attr);
+		}
 	}
 
 	/* Start maintaining the materialized view. */
@@ -2005,6 +2159,8 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 	if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
 	{
 		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		SPITupleTable  *tuptable_recalc = NULL;
+		uint64			num_recalc;
 		int				rc;
 
 		/* convert tuplestores to ENR, and register for SPI */
@@ -2022,10 +2178,19 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		if (use_count)
 			/* apply old delta and get rows to be recalculated */
 			apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME,
-									   keys, count_colname);
+									   keys, aggs_list_buf, aggs_set_old,
+									   minmax_list, is_min_list,
+									   count_colname, &tuptable_recalc, &num_recalc);
 		else
 			apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys);
 
+		/*
+		 * If we have min or max, we might have to recalculate aggregate values from base tables
+		 * on some tuples. TIDs and keys such tuples are returned as a result of the above query.
+		 */
+		if (minmax_list && tuptable_recalc)
+			recalc_and_set_values(tuptable_recalc, num_recalc, minmax_list, keys, matviewRel);
+
 	}
 	/* For tuple insertion */
 	if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
@@ -2048,7 +2213,7 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		/* apply new delta */
 		if (use_count)
 			apply_new_delta_with_count(matviewname, NEW_DELTA_ENRNAME,
-								keys, &target_list_buf, count_colname);
+								keys, aggs_set_new, &target_list_buf, count_colname);
 		else
 			apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf);
 	}
@@ -2063,49 +2228,410 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		elog(ERROR, "SPI_finish failed");
 }
 
+/*
+ * append_set_clause_for_count
+ *
+ * Append SET clause string for count aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_count(const char *resname, StringInfo buf_old,
+							StringInfo buf_new,StringInfo aggs_list)
+{
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* resname = mv.resname - t.resname */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, resname, "mv", "t", NULL, NULL));
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* resname = mv.resname + diff.resname */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, resname, "mv", "diff", NULL, NULL));
+	}
+
+	appendStringInfo(aggs_list, ", %s",
+		quote_qualified_identifier("diff", resname)
+	);
+}
+
+/*
+ * append_set_clause_for_sum
+ *
+ * Append SET clause string for sum aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_sum(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list)
+{
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* sum = mv.sum - t.sum */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, resname, "mv", "t", count_col, NULL)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* sum = mv.sum + diff.sum */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, resname, "mv", "diff", count_col, NULL)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * append_set_clause_for_avg
+ *
+ * Append SET clause string for avg aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_avg(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list,
+						  const char *aggtype)
+{
+	char *sum_col = IVM_colname("sum", resname);
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* avg = (mv.sum - t.sum)::aggtype / (mv.count - t.count) */
+		appendStringInfo(buf_old,
+			", %s = %s OPERATOR(pg_catalog./) %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, aggtype),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+		/* sum = mv.sum - t.sum */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, sum_col),
+			get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, NULL)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* avg = (mv.sum + diff.sum)::aggtype / (mv.count + diff.count) */
+		appendStringInfo(buf_new,
+			", %s = %s OPERATOR(pg_catalog./) %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, aggtype),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+		/* sum = mv.sum + diff.sum */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, sum_col),
+			get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, NULL)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("sum", resname)),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * append_set_clause_for_minmax
+ *
+ * Append SET clause string for min or max aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ * is_min is true if this is min, false if not.
+ */
+static void
+append_set_clause_for_minmax(const char *resname, StringInfo buf_old,
+							 StringInfo buf_new, StringInfo aggs_list,
+							 bool is_min)
+{
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/*
+		 * If the new value doesn't became NULL then use the value remaining
+		 * in the view although this will be recomputated afterwords.
+		 */
+		appendStringInfo(buf_old,
+			", %s = CASE WHEN %s THEN NULL ELSE %s END",
+			quote_qualified_identifier(NULL, resname),
+			get_null_condition_string(IVM_SUB, "mv", "t", count_col),
+			quote_qualified_identifier("mv", resname)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/*
+		 * min = LEAST(mv.min, diff.min)
+		 * max = GREATEST(mv.max, diff.max)
+		 */
+		appendStringInfo(buf_new,
+			", %s = CASE WHEN %s THEN NULL ELSE %s(%s,%s) END",
+			quote_qualified_identifier(NULL, resname),
+			get_null_condition_string(IVM_ADD, "mv", "diff", count_col),
+
+			is_min ? "LEAST" : "GREATEST",
+			quote_qualified_identifier("mv", resname),
+			quote_qualified_identifier("diff", resname)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * get_operation_string
+ *
+ * Build a string to calculate the new aggregate values.
+ */
+static char *
+get_operation_string(IvmOp op, const char *col, const char *arg1, const char *arg2,
+					 const char* count_col, const char *castType)
+{
+	StringInfoData buf;
+	StringInfoData castString;
+	char   *col1 = quote_qualified_identifier(arg1, col);
+	char   *col2 = quote_qualified_identifier(arg2, col);
+	char	op_char = (op == IVM_SUB ? '-' : '+');
+
+	initStringInfo(&buf);
+	initStringInfo(&castString);
+
+	if (castType)
+		appendStringInfo(&castString, "::%s", castType);
+
+	if (!count_col)
+	{
+		/*
+		 * If the attributes don't have count columns then calc the result
+		 * by using the operator simply.
+		 */
+		appendStringInfo(&buf, "(%s OPERATOR(pg_catalog.%c) %s)%s",
+			col1, op_char, col2, castString.data);
+	}
+	else
+	{
+		/*
+		 * If the attributes have count columns then consider the condition
+		 * where the result becomes NULL.
+		 */
+		char *null_cond = get_null_condition_string(op, arg1, arg2, count_col);
+
+		appendStringInfo(&buf,
+			"(CASE WHEN %s THEN NULL "
+				"WHEN %s IS NULL THEN %s "
+				"WHEN %s IS NULL THEN %s "
+				"ELSE (%s OPERATOR(pg_catalog.%c) %s)%s END)",
+			null_cond,
+			col1, col2,
+			col2, col1,
+			col1, op_char, col2, castString.data
+		);
+	}
+
+	return buf.data;
+}
+
+/*
+ * get_null_condition_string
+ *
+ * Build a predicate string for CASE clause to check if an aggregate value
+ * will became NULL after the given operation is applied.
+ */
+static char *
+get_null_condition_string(IvmOp op, const char *arg1, const char *arg2,
+						  const char* count_col)
+{
+	StringInfoData null_cond;
+	initStringInfo(&null_cond);
+
+	switch (op)
+	{
+		case IVM_ADD:
+			appendStringInfo(&null_cond,
+				"%s OPERATOR(pg_catalog.=) 0 AND %s OPERATOR(pg_catalog.=) 0",
+				quote_qualified_identifier(arg1, count_col),
+				quote_qualified_identifier(arg2, count_col)
+			);
+			break;
+		case IVM_SUB:
+			appendStringInfo(&null_cond,
+				"%s OPERATOR(pg_catalog.=) %s",
+				quote_qualified_identifier(arg1, count_col),
+				quote_qualified_identifier(arg2, count_col)
+			);
+			break;
+		default:
+			elog(ERROR,"unknown operation");
+	}
+
+	return null_cond.data;
+}
+
+
 /*
  * apply_old_delta_with_count
  *
  * Execute a query for applying a delta table given by deltname_old
  * which contains tuples to be deleted from to a materialized view given by
  * matviewname.  This is used when counting is required, that is, the view
- * has aggregate or distinct.
+ * has aggregate or distinct. Also, when a table in EXISTS sub queries
+ * is modified.
+ *
+ * If the view desn't have aggregates or has GROUP BY, this requires a keys
+ * list to identify a tuple in the view. If the view has aggregates, this
+ * requires strings representing resnames of aggregates and SET clause for
+ * updating aggregate values.
+ *
+ * If the view has min or max aggregate, this requires a list of resnames of
+ * min/max aggregates and a list of boolean which represents which entries in
+ * minmax_list is min. These are necessary to check if we need to recalculate
+ * min or max aggregate values. In this case, this query returns TID and keys
+ * of tuples which need to be recalculated.  This result and the number of rows
+ * are stored in tuptables and num_recalc repectedly.
+ *
  */
 static void
 apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
-				List *keys, const char *count_colname)
+				List *keys, StringInfo aggs_list, StringInfo aggs_set,
+				List *minmax_list, List *is_min_list,
+				const char *count_colname,
+				SPITupleTable **tuptable_recalc, uint64 *num_recalc)
 {
 	StringInfoData	querybuf;
 	char   *match_cond;
+	char   *updt_returning = "";
+	char   *select_for_recalc = "SELECT";
+	bool	agg_without_groupby = (list_length(keys) == 0);
+
+	Assert(tuptable_recalc != NULL);
+	Assert(num_recalc != NULL);
 
 	/* build WHERE condition for searching tuples to be deleted */
 	match_cond = get_matching_condition_string(keys);
 
+	/*
+	 * We need a special RETURNING clause and SELECT statement for min/max to
+	 * check which tuple needs re-calculation from base tables.
+	 */
+	if (minmax_list)
+	{
+		updt_returning = get_returning_string(minmax_list, is_min_list, keys);
+		select_for_recalc = get_select_for_recalc_string(keys);
+	}
+
 	/* Search for matching tuples from the view and update or delete if found. */
 	initStringInfo(&querybuf);
 	appendStringInfo(&querybuf,
 					"WITH t AS ("			/* collecting tid of target tuples in the view */
 						"SELECT diff.%s, "			/* count column */
-								"(diff.%s OPERATOR(pg_catalog.=) mv.%s) AS for_dlt, "
+								"(diff.%s OPERATOR(pg_catalog.=) mv.%s AND %s) AS for_dlt, "
 								"mv.ctid "
+								"%s "				/* aggregate columns */
 						"FROM %s AS mv, %s AS diff "
 						"WHERE %s"					/* tuple matching condition */
 					"), updt AS ("			/* update a tuple if this is not to be deleted */
 						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.-) t.%s "
+											"%s"	/* SET clauses for aggregates */
 						"FROM t WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND NOT for_dlt "
+						"%s"						/* RETURNING clause for recalc infomation */
 					"), dlt AS ("			/* delete a tuple if this is to be deleted */
 						"DELETE FROM %s AS mv USING t "
 						"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt"
-					")",
+					") %s",							/* SELECT returning which tuples need to be recalculated */
 					count_colname,
-					count_colname, count_colname,
+					count_colname, count_colname, (agg_without_groupby ? "false" : "true"),
+					(aggs_list != NULL ? aggs_list->data : ""),
 					matviewname, deltaname_old,
 					match_cond,
 					matviewname, count_colname, count_colname, count_colname,
-					matviewname);
+					(aggs_set != NULL ? aggs_set->data : ""),
+					updt_returning,
+					matviewname,
+					select_for_recalc);
 
-	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_SELECT)
 		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+
+
+	/* Return tuples to be recalculated. */
+	if (minmax_list)
+	{
+		*tuptable_recalc = SPI_tuptable;
+		*num_recalc = SPI_processed;
+	}
+	else
+	{
+		*tuptable_recalc = NULL;
+		*num_recalc = 0;
+	}
 }
 
 /*
@@ -2165,10 +2691,15 @@ apply_old_delta(const char *matviewname, const char *deltaname_old,
  * matviewname.  This is used when counting is required, that is, the view
  * has aggregate or distinct. Also, when a table in EXISTS sub queries
  * is modified.
+ *
+ * If the view desn't have aggregates or has GROUP BY, this requires a keys
+ * list to identify a tuple in the view. If the view has aggregates, this
+ * requires strings representing SET clause for updating aggregate values.
  */
 static void
 apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
-				List *keys, StringInfo target_list, const char* count_colname)
+				List *keys, StringInfo aggs_set, StringInfo target_list,
+				const char* count_colname)
 {
 	StringInfoData	querybuf;
 	StringInfoData	returning_keys;
@@ -2199,6 +2730,7 @@ apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
 	appendStringInfo(&querybuf,
 					"WITH updt AS ("		/* update a tuple if this exists in the view */
 						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.+) diff.%s "
+											"%s "	/* SET clauses for aggregates */
 						"FROM %s AS diff "
 						"WHERE %s "					/* tuple matching condition */
 						"RETURNING %s"				/* returning keys of updated tuples */
@@ -2206,6 +2738,7 @@ apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
 						"SELECT %s FROM %s AS diff "
 						"WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE %s);",
 					matviewname, count_colname, count_colname, count_colname,
+					(aggs_set != NULL ? aggs_set->data : ""),
 					deltaname_new,
 					match_cond,
 					returning_keys.data,
@@ -2280,6 +2813,349 @@ get_matching_condition_string(List *keys)
 	return match_cond.data;
 }
 
+/*
+ * get_returning_string
+ *
+ * Build a string for RETURNING clause of UPDATE used in apply_old_delta_with_count.
+ * This clause returns ctid and a boolean value that indicates if we need to
+ * recalculate min or max value, for each updated row.
+ */
+static char *
+get_returning_string(List *minmax_list, List *is_min_list, List *keys)
+{
+	StringInfoData returning;
+	char		*recalc_cond;
+	ListCell	*lc;
+
+	Assert(minmax_list != NIL && is_min_list != NIL);
+	recalc_cond = get_minmax_recalc_condition_string(minmax_list, is_min_list);
+
+	initStringInfo(&returning);
+
+	appendStringInfo(&returning, "RETURNING mv.ctid AS tid, (%s) AS recalc", recalc_cond);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char *resname = NameStr(attr->attname);
+		appendStringInfo(&returning, ", %s", quote_qualified_identifier("mv", resname));
+	}
+
+	return returning.data;
+}
+
+/*
+ * get_minmax_recalc_condition_string
+ *
+ * Build a predicate string for checking if any min/max aggregate
+ * value needs to be recalculated.
+ */
+static char *
+get_minmax_recalc_condition_string(List *minmax_list, List *is_min_list)
+{
+	StringInfoData recalc_cond;
+	ListCell	*lc1, *lc2;
+
+	initStringInfo(&recalc_cond);
+
+	Assert (list_length(minmax_list) == list_length(is_min_list));
+
+	forboth (lc1, minmax_list, lc2, is_min_list)
+	{
+		char   *resname = (char *) lfirst(lc1);
+		bool	is_min = (bool) lfirst_int(lc2);
+		char   *op_str = (is_min ? ">=" : "<=");
+
+		appendStringInfo(&recalc_cond, "%s OPERATOR(pg_catalog.%s) %s",
+			quote_qualified_identifier("mv", resname),
+			op_str,
+			quote_qualified_identifier("t", resname)
+		);
+
+		if (lnext(minmax_list, lc1))
+			appendStringInfo(&recalc_cond, " OR ");
+	}
+
+	return recalc_cond.data;
+}
+
+/*
+ * get_select_for_recalc_string
+ *
+ * Build a query to return tid and keys of tuples which need
+ * recalculation. This is used as the result of the query
+ * built by apply_old_delta.
+ */
+static char *
+get_select_for_recalc_string(List *keys)
+{
+	StringInfoData qry;
+	ListCell	*lc;
+
+	initStringInfo(&qry);
+
+	appendStringInfo(&qry, "SELECT tid");
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		appendStringInfo(&qry, ", %s", NameStr(attr->attname));
+	}
+
+	appendStringInfo(&qry, " FROM updt WHERE recalc");
+
+	return qry.data;
+}
+
+/*
+ * recalc_and_set_values
+ *
+ * Recalculate tuples in a materialized from base tables and update these.
+ * The tuples which needs recalculation are specified by keys, and resnames
+ * of columns to be updated are specified by namelist. TIDs and key values
+ * are given by tuples in tuptable_recalc. Its first attribute must be TID
+ * and key values must be following this.
+ */
+static void
+recalc_and_set_values(SPITupleTable *tuptable_recalc, int64 num_tuples,
+					  List *namelist, List *keys, Relation matviewRel)
+{
+	TupleDesc   tupdesc_recalc = tuptable_recalc->tupdesc;
+	Oid		   *keyTypes = NULL, *types = NULL;
+	char	   *keyNulls = NULL, *nulls = NULL;
+	Datum	   *keyVals = NULL, *vals = NULL;
+	int			num_vals = list_length(namelist);
+	int			num_keys = list_length(keys);
+	uint64      i;
+	Oid			matviewOid;
+	char	   *matviewname;
+
+	matviewOid = RelationGetRelid(matviewRel);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	/* If we have keys, initialize arrays for them. */
+	if (keys)
+	{
+		keyTypes = palloc(sizeof(Oid) * num_keys);
+		keyNulls = palloc(sizeof(char) * num_keys);
+		keyVals = palloc(sizeof(Datum) * num_keys);
+		/* a tuple contains keys to be recalculated and ctid to be updated*/
+		Assert(tupdesc_recalc->natts == num_keys + 1);
+
+		/* Types of key attributes  */
+		for (i = 0; i < num_keys; i++)
+			keyTypes[i] = TupleDescAttr(tupdesc_recalc, i + 1)->atttypid;
+	}
+
+	/* allocate memory for all attribute names and tid */
+	types = palloc(sizeof(Oid) * (num_vals + 1));
+	nulls = palloc(sizeof(char) * (num_vals + 1));
+	vals = palloc(sizeof(Datum) * (num_vals + 1));
+
+	/* For each tuple which needs recalculation */
+	for (i = 0; i < num_tuples; i++)
+	{
+		int j;
+		bool isnull;
+		SPIPlanPtr plan;
+		SPITupleTable *tuptable_newvals;
+		TupleDesc   tupdesc_newvals;
+
+		/* Set group key values as parameters if needed. */
+		if (keys)
+		{
+			for (j = 0; j < num_keys; j++)
+			{
+				keyVals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, j + 2, &isnull);
+				if (isnull)
+					keyNulls[j] = 'n';
+				else
+					keyNulls[j] = ' ';
+			}
+		}
+
+		/*
+		 * Get recalculated values from base tables. The result must be
+		 * only one tuple thich contains the new values for specified keys.
+		 */
+		plan = get_plan_for_recalc(matviewOid, namelist, keys, keyTypes);
+		if (SPI_execute_plan(plan, keyVals, keyNulls, false, 0) != SPI_OK_SELECT)
+			elog(ERROR, "SPI_execute_plan");
+		if (SPI_processed != 1)
+			elog(ERROR, "SPI_execute_plan returned zero or more than one rows");
+
+		tuptable_newvals = SPI_tuptable;
+		tupdesc_newvals = tuptable_newvals->tupdesc;
+
+		Assert(tupdesc_newvals->natts == num_vals);
+
+		/* Set the new values as parameters */
+		for (j = 0; j < tupdesc_newvals->natts; j++)
+		{
+			if (i == 0)
+				types[j] = TupleDescAttr(tupdesc_newvals, j)->atttypid;
+
+			vals[j] = SPI_getbinval(tuptable_newvals->vals[0], tupdesc_newvals, j + 1, &isnull);
+			if (isnull)
+				nulls[j] = 'n';
+			else
+				nulls[j] = ' ';
+		}
+		/* Set TID of the view tuple to be updated as a parameter */
+		types[j] = TIDOID;
+		vals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, 1, &isnull);
+		nulls[j] = ' ';
+
+		/* Update the view tuple to the new values */
+		plan = get_plan_for_set_values(matviewOid, matviewname, namelist, types);
+		if (SPI_execute_plan(plan, vals, nulls, false, 0) != SPI_OK_UPDATE)
+			elog(ERROR, "SPI_execute_plan");
+	}
+}
+
+
+/*
+ * get_plan_for_recalc
+ *
+ * Create or fetch a plan for recalculating value in the view's target list
+ * from base tables using the definition query of materialized view specified
+ * by matviewOid. namelist is a list of resnames of values to be recalculated.
+ *
+ * keys is a list of keys to identify tuples to be recalculated if this is not
+ * empty. KeyTypes is an array of types of keys.
+ */
+static SPIPlanPtr
+get_plan_for_recalc(Oid matviewOid, List *namelist, List *keys, Oid *keyTypes)
+{
+	MV_QueryKey hash_key;
+	SPIPlanPtr	plan;
+
+	/* Fetch or prepare a saved plan for the recalculation */
+	mv_BuildQueryKey(&hash_key, matviewOid, MV_PLAN_RECALC);
+	if ((plan = mv_FetchPreparedPlan(&hash_key)) == NULL)
+	{
+		ListCell	   *lc;
+		StringInfoData	str;
+		char   *viewdef;
+
+		/* get view definition of matview */
+		viewdef = text_to_cstring((text *) DatumGetPointer(
+					DirectFunctionCall1(pg_get_viewdef, ObjectIdGetDatum(matviewOid))));
+		/* get rid of trailing semi-colon */
+		viewdef[strlen(viewdef)-1] = '\0';
+
+		/*
+		 * Build a query string for recalculating values. This is like
+		 *
+		 *  SELECT x1, x2, x3, ... FROM ( ... view definition query ...) mv
+		 *   WHERE (key1, key2, ...) = ($1, $2, ...);
+		 */
+
+		initStringInfo(&str);
+		appendStringInfo(&str, "SELECT ");
+		foreach (lc, namelist)
+		{
+			appendStringInfo(&str, "%s", (char *) lfirst(lc));
+			if (lnext(namelist, lc))
+				appendStringInfoString(&str, ", ");
+		}
+		appendStringInfo(&str, " FROM (%s) mv", viewdef);
+
+		if (keys)
+		{
+			int		i = 1;
+			char	paramname[16];
+
+			appendStringInfo(&str, " WHERE (");
+			foreach (lc, keys)
+			{
+				Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+				char   *resname = NameStr(attr->attname);
+				Oid		typid = attr->atttypid;
+
+				sprintf(paramname, "$%d", i);
+				appendStringInfo(&str, "(");
+				generate_equal(&str, typid, resname, paramname);
+				appendStringInfo(&str, " OR (%s IS NULL AND %s IS NULL))",
+								 resname, paramname);
+
+				if (lnext(keys, lc))
+					appendStringInfoString(&str, " AND ");
+				i++;
+			}
+			appendStringInfo(&str, ")");
+		}
+		else
+			keyTypes = NULL;
+
+		plan = SPI_prepare(str.data, list_length(keys), keyTypes);
+		if (plan == NULL)
+			elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), str.data);
+
+		SPI_keepplan(plan);
+		mv_HashPreparedPlan(&hash_key, plan);
+	}
+
+	return plan;
+}
+
+/*
+ * get_plan_for_set_values
+ *
+ * Create or fetch a plan for applying new values calculated by
+ * get_plan_for_recalc to a materialized view specified by matviewOid.
+ * matviewname is the name of the view.  namelist is a list of resnames
+ * of attributes to be updated, and valTypes is an array of types of the
+ * values.
+ */
+static SPIPlanPtr
+get_plan_for_set_values(Oid matviewOid, char *matviewname, List *namelist,
+						Oid *valTypes)
+{
+	MV_QueryKey	key;
+	SPIPlanPtr	plan;
+
+	/* Fetch or prepare a saved plan for the real check */
+	mv_BuildQueryKey(&key, matviewOid, MV_PLAN_SET_VALUE);
+	if ((plan = mv_FetchPreparedPlan(&key)) == NULL)
+	{
+		ListCell	  *lc;
+		StringInfoData str;
+		int		i;
+
+		/*
+		 * Build a query string for applying min/max values. This is like
+		 *
+		 *  UPDATE matviewname AS mv
+		 *   SET (x1, x2, x3, x4) = ($1, $2, $3, $4)
+		 *   WHERE ctid = $5;
+		 */
+
+		initStringInfo(&str);
+		appendStringInfo(&str, "UPDATE %s AS mv SET (", matviewname);
+		foreach (lc, namelist)
+		{
+			appendStringInfo(&str, "%s", (char *) lfirst(lc));
+			if (lnext(namelist, lc))
+				appendStringInfoString(&str, ", ");
+		}
+		appendStringInfo(&str, ") = ROW(");
+
+		for (i = 1; i <= list_length(namelist); i++)
+			appendStringInfo(&str, "%s$%d", (i==1 ? "" : ", "), i);
+
+		appendStringInfo(&str, ") WHERE ctid OPERATOR(pg_catalog.=) $%d", i);
+
+		plan = SPI_prepare(str.data, list_length(namelist) + 1, valTypes);
+		if (plan == NULL)
+			elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), str.data);
+
+		SPI_keepplan(plan);
+		mv_HashPreparedPlan(&key, plan);
+	}
+
+	return plan;
+}
+
 /*
  * generate_equals
  *
@@ -2313,6 +3189,13 @@ mv_InitHashTables(void)
 {
 	HASHCTL		ctl;
 
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(MV_QueryKey);
+	ctl.entrysize = sizeof(MV_QueryHashEntry);
+	mv_query_cache = hash_create("MV query cache",
+								 MV_INIT_QUERYHASHSIZE,
+								 &ctl, HASH_ELEM | HASH_BLOBS);
+
 	memset(&ctl, 0, sizeof(ctl));
 	ctl.keysize = sizeof(Oid);
 	ctl.entrysize = sizeof(MV_TriggerHashEntry);
@@ -2321,6 +3204,99 @@ mv_InitHashTables(void)
 								 &ctl, HASH_ELEM | HASH_BLOBS);
 }
 
+/*
+ * mv_FetchPreparedPlan
+ */
+static SPIPlanPtr
+mv_FetchPreparedPlan(MV_QueryKey *key)
+{
+	MV_QueryHashEntry *entry;
+	SPIPlanPtr	plan;
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_query_cache)
+		mv_InitHashTables();
+
+	/*
+	 * Lookup for the key
+	 */
+	entry = (MV_QueryHashEntry *) hash_search(mv_query_cache,
+											  (void *) key,
+											  HASH_FIND, NULL);
+	if (entry == NULL)
+		return NULL;
+
+	/*
+	 * Check whether the plan is still valid.  If it isn't, we don't want to
+	 * simply rely on plancache.c to regenerate it; rather we should start
+	 * from scratch and rebuild the query text too.  This is to cover cases
+	 * such as table/column renames.  We depend on the plancache machinery to
+	 * detect possible invalidations, though.
+	 *
+	 * CAUTION: this check is only trustworthy if the caller has already
+	 * locked both materialized views and base tables.
+	 */
+	plan = entry->plan;
+	if (plan && SPI_plan_is_valid(plan))
+		return plan;
+
+	/*
+	 * Otherwise we might as well flush the cached plan now, to free a little
+	 * memory space before we make a new one.
+	 */
+	entry->plan = NULL;
+	if (plan)
+		SPI_freeplan(plan);
+
+	return NULL;
+}
+
+/*
+ * mv_HashPreparedPlan
+ *
+ * Add another plan to our private SPI query plan hashtable.
+ */
+static void
+mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan)
+{
+	MV_QueryHashEntry *entry;
+	bool		found;
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_query_cache)
+		mv_InitHashTables();
+
+	/*
+	 * Add the new plan.  We might be overwriting an entry previously found
+	 * invalid by mv_FetchPreparedPlan.
+	 */
+	entry = (MV_QueryHashEntry *) hash_search(mv_query_cache,
+											  (void *) key,
+											  HASH_ENTER, &found);
+	Assert(!found || entry->plan == NULL);
+	entry->plan = plan;
+}
+
+/*
+ * mv_BuildQueryKey
+ *
+ * Construct a hashtable key for a prepared SPI plan for IVM.
+ */
+static void
+mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type)
+{
+	/*
+	 * We assume struct MV_QueryKey contains no padding bytes, else we'd need
+	 * to use memset to clear them.
+	 */
+	key->matview_id = matview_id;
+	key->query_type = query_type;
+}
+
 /*
  * AtAbort_IVM
  *
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index a57ce463e1..702b097079 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -29,6 +29,7 @@ extern ObjectAddress ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *st
 extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create);
 
 extern Query *rewriteQueryForIMMV(Query *query, List *colNames);
+extern void makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber *next_resno, List **aggs);
 
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
 
-- 
2.17.1

v24-0009-Add-regression-tests-for-Incremental-View-Mainte.patchtext/x-diff; name=v24-0009-Add-regression-tests-for-Incremental-View-Mainte.patchDownload
From 1e299b584853df4d52914a58b0dbc177c168159f Mon Sep 17 00:00:00 2001
From: Takuma Hoshiai <takuma.hoshiai@gmail.com>
Date: Wed, 10 Mar 2021 11:11:13 +0900
Subject: [PATCH v24 09/15] Add regression tests for Incremental View
 Maintenance

---
 .../regress/expected/incremental_matview.out  | 812 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/incremental_matview.sql  | 400 +++++++++
 3 files changed, 1213 insertions(+), 1 deletion(-)
 create mode 100644 src/test/regress/expected/incremental_matview.out
 create mode 100644 src/test/regress/sql/incremental_matview.sql

diff --git a/src/test/regress/expected/incremental_matview.out b/src/test/regress/expected/incremental_matview.out
new file mode 100644
index 0000000000..4b14100acd
--- /dev/null
+++ b/src/test/regress/expected/incremental_matview.out
@@ -0,0 +1,812 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) WITH NO DATA;
+NOTICE:  could not create an index on materialized view "mv_ivm_1" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ERROR:  materialized view "mv_ivm_1" has not been populated
+HINT:  Use the REFRESH MATERIALIZED VIEW command.
+REFRESH MATERIALIZED VIEW mv_ivm_1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- immediate maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- rename of IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_rename AS SELECT DISTINCT * FROM mv_base_a;
+NOTICE:  created index "mv_ivm_rename_index" on materialized view "mv_ivm_rename"
+ALTER MATERIALIZED VIEW mv_ivm_rename RENAME COLUMN __ivm_count__ TO xxx;
+ERROR:  IVM column can not be renamed
+DROP MATERIALIZED VIEW mv_ivm_rename;
+-- unique index on IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_unique AS SELECT DISTINCT * FROM mv_base_a;
+NOTICE:  created index "mv_ivm_unique_index" on materialized view "mv_ivm_unique"
+CREATE UNIQUE INDEX ON mv_ivm_unique(__ivm_count__);
+ERROR:  unique index creation on IVM columns is not supported
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__));
+ERROR:  unique index creation on IVM columns is not supported
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__ + 1));
+ERROR:  unique index creation on IVM columns is not supported
+DROP MATERIALIZED VIEW mv_ivm_unique;
+-- some query syntax
+BEGIN;
+CREATE FUNCTION ivm_func() RETURNS int LANGUAGE 'sql'
+       AS 'SELECT 1' IMMUTABLE;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_func AS SELECT * FROM ivm_func();
+NOTICE:  could not create an index on materialized view "mv_ivm_func" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_no_tbl AS SELECT 1;
+NOTICE:  could not create an index on materialized view "mv_ivm_no_tbl" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+ROLLBACK;
+-- result of materialized view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_duplicate" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+NOTICE:  created index "mv_ivm_distinct_index" on materialized view "mv_ivm_distinct"
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 20
+ 30
+ 40
+ 50
+(6 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+ROLLBACK;
+-- support SUM(), COUNT() and AVG() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i), AVG(j) FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_agg_index" on materialized view "mv_ivm_agg"
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 | 120 |     2 | 60.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg          
+---+-----+-------+----------------------
+ 1 |  10 |     1 |  10.0000000000000000
+ 2 | 220 |     2 | 110.0000000000000000
+ 3 |  30 |     1 |  30.0000000000000000
+ 4 |  40 |     1 |  40.0000000000000000
+ 5 |  50 |     1 |  50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support COUNT(*) aggregate function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(*) FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_agg_index" on materialized view "mv_ivm_agg"
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 |  20 |     1
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 | 120 |     2
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+ROLLBACK;
+-- support aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j), COUNT(j), AVG(j) FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_group" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count |         avg         
+-----+-------+---------------------
+ 150 |     5 | 30.0000000000000000
+(1 row)
+
+INSERT INTO mv_base_a VALUES(6,60);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count |         avg         
+-----+-------+---------------------
+ 210 |     6 | 35.0000000000000000
+(1 row)
+
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count | avg 
+-----+-------+-----
+     |     0 |    
+(1 row)
+
+ROLLBACK;
+-- resolved issue: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+NOTICE:  created index "mv_ivm_avg_bug_index" on materialized view "mv_ivm_avg_bug"
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     3 |  3.3333333333333333
+ 2 |  80 |     3 | 26.6666666666666667
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support MIN(), MAX() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT i, MIN(j), MAX(j)  FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_min_max_index" on materialized view "mv_ivm_min_max"
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  10 |  10
+ 2 |  20 |  20
+ 3 |  30 |  30
+ 4 |  40 |  40
+ 5 |  50 |  50
+(5 rows)
+
+INSERT INTO mv_base_a VALUES
+  (1,11), (1,12),
+  (2,21), (2,22),
+  (3,31), (3,32),
+  (4,41), (4,42),
+  (5,51), (5,52);
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  10 |  12
+ 2 |  20 |  22
+ 3 |  30 |  32
+ 4 |  40 |  42
+ 5 |  50 |  52
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  11 |  12
+ 2 |  20 |  22
+ 3 |  30 |  31
+ 4 |  40 |  42
+ 5 |  50 |  52
+(5 rows)
+
+ROLLBACK;
+-- support MIN(), MAX() aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT MIN(j), MAX(j)  FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_min_max" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+  10 |  50
+(1 row)
+
+INSERT INTO mv_base_a VALUES
+  (0,0), (6,60), (7,70);
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+   0 |  70
+(1 row)
+
+DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+  10 |  60
+(1 row)
+
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+     |    
+(1 row)
+
+ROLLBACK;
+-- support self join view and multiple change on the same table
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1, 10), (2, 20), (3, 30);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_self(v1, v2) AS
+ SELECT t1.v, t2.v FROM base_t AS t1 JOIN base_t AS t2 ON t1.i = t2.i;
+NOTICE:  could not create an index on materialized view "mv_self" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_self ORDER BY v1;
+ v1 | v2 
+----+----
+ 10 | 10
+ 20 | 20
+ 30 | 30
+(3 rows)
+
+INSERT INTO base_t VALUES (4,40);
+DELETE FROM base_t WHERE i = 1;
+UPDATE base_t SET v = v*10 WHERE i=2;
+SELECT * FROM mv_self ORDER BY v1;
+ v1  | v2  
+-----+-----
+  30 |  30
+  40 |  40
+ 200 | 200
+(3 rows)
+
+WITH
+ ins_t1 AS (INSERT INTO base_t VALUES (5,50) RETURNING 1),
+ ins_t2 AS (INSERT INTO base_t VALUES (6,60) RETURNING 1),
+ upd_t AS (UPDATE base_t SET v = v + 100  RETURNING 1),
+ dlt_t AS (DELETE FROM base_t WHERE i IN (4,5)  RETURNING 1)
+SELECT NULL;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT * FROM mv_self ORDER BY v1;
+ v1  | v2  
+-----+-----
+  50 |  50
+  60 |  60
+ 130 | 130
+ 300 | 300
+(4 rows)
+
+ROLLBACK;
+-- support simultaneous table changes
+BEGIN;
+CREATE TABLE base_r (i int, v int);
+CREATE TABLE base_s (i int, v int);
+INSERT INTO base_r VALUES (1, 10), (2, 20), (3, 30);
+INSERT INTO base_s VALUES (1, 100), (2, 200), (3, 300);
+CREATE INCREMENTAL MATERIALIZED VIEW mv(v1, v2) AS
+ SELECT r.v, s.v FROM base_r AS r JOIN base_s AS s USING(i);
+NOTICE:  could not create an index on materialized view "mv" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY v1;
+ v1 | v2  
+----+-----
+ 10 | 100
+ 20 | 200
+ 30 | 300
+(3 rows)
+
+WITH
+ ins_r AS (INSERT INTO base_r VALUES (1,11) RETURNING 1),
+ ins_r2 AS (INSERT INTO base_r VALUES (3,33) RETURNING 1),
+ ins_s AS (INSERT INTO base_s VALUES (2,222) RETURNING 1),
+ upd_r AS (UPDATE base_r SET v = v + 1000 WHERE i = 2 RETURNING 1),
+ dlt_s AS (DELETE FROM base_s WHERE i = 3 RETURNING 1)
+SELECT NULL;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT * FROM mv ORDER BY v1;
+  v1  | v2  
+------+-----
+   10 | 100
+   11 | 100
+ 1020 | 200
+ 1020 | 222
+(4 rows)
+
+ROLLBACK;
+-- support foreign reference constraints
+BEGIN;
+CREATE TABLE ri1 (i int PRIMARY KEY);
+CREATE TABLE ri2 (i int PRIMARY KEY REFERENCES ri1(i) ON UPDATE CASCADE ON DELETE CASCADE, v int);
+INSERT INTO ri1 VALUES (1),(2),(3);
+INSERT INTO ri2 VALUES (1),(2),(3);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ri(i1, i2) AS
+ SELECT ri1.i, ri2.i FROM ri1 JOIN ri2 USING(i);
+NOTICE:  created index "mv_ri_index" on materialized view "mv_ri"
+SELECT * FROM mv_ri ORDER BY i1;
+ i1 | i2 
+----+----
+  1 |  1
+  2 |  2
+  3 |  3
+(3 rows)
+
+UPDATE ri1 SET i=10 where i=1;
+DELETE FROM ri1 WHERE i=2;
+SELECT * FROM mv_ri ORDER BY i2;
+ i1 | i2 
+----+----
+  3 |  3
+ 10 | 10
+(2 rows)
+
+ROLLBACK;
+-- views including NULL
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1,10),(2, NULL);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+NOTICE:  could not create an index on materialized view "mv" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY i;
+ i | v  
+---+----
+ 1 | 10
+ 2 |   
+(2 rows)
+
+UPDATE base_t SET v = 20 WHERE i = 2;
+SELECT * FROM mv ORDER BY i;
+ i | v  
+---+----
+ 1 | 10
+ 2 | 20
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+NOTICE:  could not create an index on materialized view "mv" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY i;
+ i 
+---
+(0 rows)
+
+INSERT INTO base_t VALUES (1),(NULL);
+SELECT * FROM mv ORDER BY i;
+ i 
+---
+ 1
+  
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (1, 10), (1, 20);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, sum(v) FROM base_t GROUP BY i;
+NOTICE:  created index "mv_index" on materialized view "mv"
+SELECT * FROM mv ORDER BY i;
+ i | sum 
+---+-----
+ 1 |  30
+   |   3
+(2 rows)
+
+UPDATE base_t SET v = v * 10;
+SELECT * FROM mv ORDER BY i;
+ i | sum 
+---+-----
+ 1 | 300
+   |  30
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (NULL, 3), (NULL, 4), (NULL, 5);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, min(v), max(v) FROM base_t GROUP BY i;
+NOTICE:  created index "mv_index" on materialized view "mv"
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   1 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 1;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 3;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 5;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   4
+(1 row)
+
+ROLLBACK;
+-- IMMV containing user defined type
+BEGIN;
+CREATE TYPE mytype;
+CREATE FUNCTION mytype_in(cstring)
+ RETURNS mytype AS 'int4in'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+NOTICE:  return type mytype is only a shell
+CREATE FUNCTION mytype_out(mytype)
+ RETURNS cstring AS 'int4out'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+NOTICE:  argument type mytype is only a shell
+CREATE TYPE mytype (
+ LIKE = int4,
+ INPUT = mytype_in,
+ OUTPUT = mytype_out
+);
+CREATE FUNCTION mytype_eq(mytype, mytype)
+ RETURNS bool AS 'int4eq'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_lt(mytype, mytype)
+ RETURNS bool AS 'int4lt'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_cmp(mytype, mytype)
+ RETURNS integer AS 'btint4cmp'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE OPERATOR = (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_eq);
+CREATE OPERATOR < (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_lt);
+CREATE OPERATOR CLASS mytype_ops
+ DEFAULT FOR TYPE mytype USING btree AS
+ OPERATOR        1       <,
+ OPERATOR        3       = ,
+ FUNCTION		1		mytype_cmp(mytype,mytype);
+CREATE TABLE t_mytype (x mytype);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_mytype AS
+ SELECT * FROM t_mytype;
+NOTICE:  could not create an index on materialized view "mv_mytype" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+INSERT INTO t_mytype VALUES ('1'::mytype);
+SELECT * FROM mv_mytype;
+ x 
+---
+ 1
+(1 row)
+
+ROLLBACK;
+-- outer join is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i;
+ERROR:  OUTER JOIN is not supported on incrementally maintainable materialized view
+-- CTE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS
+    WITH b AS ( SELECT * FROM mv_base_b) SELECT a.i,a.j FROM mv_base_a a, b WHERE a.i = b.i;
+ERROR:  CTE is not supported on incrementally maintainable materialized view
+-- contain system column
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm01 AS SELECT i,j,xmin FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT i,j,xmin::text AS x_min FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm06 AS SELECT i,j,xidsend(xmin) AS x_min FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+-- contain subquery
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm03 AS SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT * FROM mv_base_b) b WHERE a.i = b.i;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm05 AS SELECT i,j, (SELECT k FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+-- contain ORDER BY
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm07 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) ORDER BY i,j,k;
+ERROR:  ORDER BY clause is not supported on incrementally maintainable materialized view
+-- contain HAVING
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm08 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) GROUP BY i,j,k HAVING SUM(i) > 5;
+ERROR:   HAVING clause is not supported on incrementally maintainable materialized view
+-- contain view or materialized view
+CREATE VIEW b_view AS SELECT i,k FROM mv_base_b;
+CREATE MATERIALIZED VIEW b_mview AS SELECT i,k FROM mv_base_b;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm07 AS SELECT a.i,a.j FROM mv_base_a a,b_view b WHERE a.i = b.i;
+ERROR:  VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm08 AS SELECT a.i,a.j FROM mv_base_a a,b_mview b WHERE a.i = b.i;
+ERROR:  VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm09 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm10 AS SELECT a.i,a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) OR a.i > 5;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+-- contain mutable functions
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+ERROR:  mutable function is not supported on incrementally maintainable materialized view
+HINT:  functions must be marked IMMUTABLE
+-- LIMIT/OFFSET is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm13 AS SELECT i,j FROM mv_base_a LIMIT 10 OFFSET 5;
+ERROR:  LIMIT/OFFSET clause is not supported on incrementally maintainable materialized view
+-- DISTINCT ON is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm14 AS SELECT DISTINCT ON(i) i, j FROM mv_base_a;
+ERROR:  DISTINCT ON is not supported on incrementally maintainable materialized view
+-- TABLESAMPLE clause is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm15 AS SELECT i, j FROM mv_base_a TABLESAMPLE SYSTEM(50);
+ERROR:  TABLESAMPLE clause is not supported on incrementally maintainable materialized view
+-- window functions are not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm16 AS SELECT *, cume_dist() OVER (ORDER BY i) AS rank FROM mv_base_a;
+ERROR:  window functions are not supported on incrementally maintainable materialized view
+-- aggregate function with some options is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm17 AS SELECT COUNT(*) FILTER(WHERE i < 3) FROM mv_base_a;
+ERROR:  aggregate function with FILTER clause is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm18 AS SELECT COUNT(DISTINCT i)  FROM mv_base_a;
+ERROR:  aggregate function with DISTINCT arguments is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm19 AS SELECT array_agg(j ORDER BY i DESC) FROM mv_base_a;
+ERROR:  aggregate function with ORDER clause is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm20 AS SELECT i,SUM(j) FROM mv_base_a GROUP BY GROUPING SETS((i),());
+ERROR:  GROUPING SETS, ROLLUP, or CUBE clauses is not supported on incrementally maintainable materialized view
+-- inheritance parent is not supported
+BEGIN;
+CREATE TABLE parent (i int, v int);
+CREATE TABLE child_a(options text) INHERITS(parent);
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm21 AS SELECT * FROM parent;
+ERROR:  inheritance parent is not supported on incrementally maintainable materialized view
+ROLLBACK;
+-- UNION statement is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm22 AS SELECT i,j FROM mv_base_a UNION ALL SELECT i,k FROM mv_base_b;;
+ERROR:  UNION/INTERSECT/EXCEPT statements are not supported on incrementally maintainable materialized view
+-- empty target list is not allowed with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm25 AS SELECT FROM mv_base_a;
+ERROR:  empty target list is not supported on incrementally maintainable materialized view
+-- FOR UPDATE/SHARE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm26 AS SELECT i,j FROM mv_base_a FOR UPDATE;
+ERROR:  FOR UPDATE/SHARE clause is not supported on incrementally maintainable materialized view
+-- tartget list cannot contain ivm column that start with '__ivm'
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm28 AS SELECT i AS "__ivm_count__" FROM mv_base_a;
+ERROR:  column name __ivm_count__ is not supported on incrementally maintainable materialized view
+-- expressions specified in GROUP BY must appear in the target list.
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm29 AS SELECT COUNT(i) FROM mv_base_a GROUP BY i;
+ERROR:  GROUP BY expression not appearing in select list is not supported on incrementally maintainable materialized view
+-- experssions containing an aggregate is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm30 AS SELECT sum(i)*0.5 FROM mv_base_a;
+ERROR:  expression containing an aggregate in it is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm31 AS SELECT sum(i)/sum(j) FROM mv_base_a;
+ERROR:  expression containing an aggregate in it is not supported on incrementally maintainable materialized view
+-- VALUES is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_only_values1 AS values(1);
+ERROR:  VALUES is not supported on incrementally maintainable materialized view
+-- base table which has row level security
+DROP USER IF EXISTS ivm_admin;
+NOTICE:  role "ivm_admin" does not exist, skipping
+DROP USER IF EXISTS ivm_user;
+NOTICE:  role "ivm_user" does not exist, skipping
+CREATE USER ivm_admin;
+CREATE USER ivm_user;
+SET SESSION AUTHORIZATION ivm_admin;
+CREATE TABLE rls_tbl(id int, data text, owner name);
+INSERT INTO rls_tbl VALUES
+  (1,'foo','ivm_user'),
+  (2,'bar','postgres');
+CREATE TABLE num_tbl(id int, num text);
+INSERT INTO num_tbl VALUES
+  (1,'one'),
+  (2,'two'),
+  (3,'three'),
+  (4,'four');
+CREATE POLICY rls_tbl_policy ON rls_tbl FOR SELECT TO PUBLIC USING(owner = current_user);
+CREATE POLICY rls_tbl_policy2 ON rls_tbl FOR INSERT TO PUBLIC WITH CHECK(current_user LIKE 'ivm_%');
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+GRANT ALL on rls_tbl TO PUBLIC;
+GRANT ALL on num_tbl TO PUBLIC;
+SET SESSION AUTHORIZATION ivm_user;
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls AS SELECT * FROM rls_tbl;
+NOTICE:  could not create an index on materialized view "ivm_rls" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+ id | data |  owner   
+----+------+----------
+  1 | foo  | ivm_user
+(1 row)
+
+INSERT INTO rls_tbl VALUES
+  (3,'baz','ivm_user'),
+  (4,'qux','postgres');
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+ id | data |  owner   
+----+------+----------
+  1 | foo  | ivm_user
+  3 | baz  | ivm_user
+(2 rows)
+
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls2 AS SELECT * FROM rls_tbl JOIN num_tbl USING(id);
+NOTICE:  could not create an index on materialized view "ivm_rls2" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+RESET SESSION AUTHORIZATION;
+WITH
+ x AS (UPDATE rls_tbl SET data = data || '_2' where id in (3,4)),
+ y AS (UPDATE num_tbl SET num = num || '_2' where id in (3,4))
+SELECT;
+--
+(1 row)
+
+SELECT * FROM ivm_rls2 ORDER BY 1,2,3;
+ id | data  |  owner   |   num   
+----+-------+----------+---------
+  1 | foo   | ivm_user | one
+  3 | baz_2 | ivm_user | three_2
+(2 rows)
+
+DROP TABLE rls_tbl CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to materialized view ivm_rls
+drop cascades to materialized view ivm_rls2
+DROP TABLE num_tbl CASCADE;
+DROP USER ivm_user;
+DROP USER ivm_admin;
+DROP TABLE mv_base_b CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to materialized view mv_ivm_1
+drop cascades to view b_view
+drop cascades to materialized view b_mview
+DROP TABLE mv_base_a CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0..b0350f47dd 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -86,7 +86,7 @@ test: brin_bloom brin_multi
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort incremental_matview
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/sql/incremental_matview.sql b/src/test/regress/sql/incremental_matview.sql
new file mode 100644
index 0000000000..311e9b96fb
--- /dev/null
+++ b/src/test/regress/sql/incremental_matview.sql
@@ -0,0 +1,400 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) WITH NO DATA;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+REFRESH MATERIALIZED VIEW mv_ivm_1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+-- immediate maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+
+-- rename of IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_rename AS SELECT DISTINCT * FROM mv_base_a;
+ALTER MATERIALIZED VIEW mv_ivm_rename RENAME COLUMN __ivm_count__ TO xxx;
+DROP MATERIALIZED VIEW mv_ivm_rename;
+
+-- unique index on IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_unique AS SELECT DISTINCT * FROM mv_base_a;
+CREATE UNIQUE INDEX ON mv_ivm_unique(__ivm_count__);
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__));
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__ + 1));
+DROP MATERIALIZED VIEW mv_ivm_unique;
+
+-- some query syntax
+BEGIN;
+CREATE FUNCTION ivm_func() RETURNS int LANGUAGE 'sql'
+       AS 'SELECT 1' IMMUTABLE;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_func AS SELECT * FROM ivm_func();
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_no_tbl AS SELECT 1;
+ROLLBACK;
+
+-- result of materialized view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ROLLBACK;
+
+-- support SUM(), COUNT() and AVG() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i), AVG(j) FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ROLLBACK;
+
+-- support COUNT(*) aggregate function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(*) FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j), COUNT(j), AVG(j) FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+INSERT INTO mv_base_a VALUES(6,60);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ROLLBACK;
+
+-- resolved issue: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support MIN(), MAX() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT i, MIN(j), MAX(j)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES
+  (1,11), (1,12),
+  (2,21), (2,22),
+  (3,31), (3,32),
+  (4,41), (4,42),
+  (5,51), (5,52);
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support MIN(), MAX() aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT MIN(j), MAX(j)  FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+INSERT INTO mv_base_a VALUES
+  (0,0), (6,60), (7,70);
+SELECT * FROM mv_ivm_min_max;
+DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
+SELECT * FROM mv_ivm_min_max;
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+ROLLBACK;
+
+-- support self join view and multiple change on the same table
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1, 10), (2, 20), (3, 30);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_self(v1, v2) AS
+ SELECT t1.v, t2.v FROM base_t AS t1 JOIN base_t AS t2 ON t1.i = t2.i;
+SELECT * FROM mv_self ORDER BY v1;
+INSERT INTO base_t VALUES (4,40);
+DELETE FROM base_t WHERE i = 1;
+UPDATE base_t SET v = v*10 WHERE i=2;
+SELECT * FROM mv_self ORDER BY v1;
+WITH
+ ins_t1 AS (INSERT INTO base_t VALUES (5,50) RETURNING 1),
+ ins_t2 AS (INSERT INTO base_t VALUES (6,60) RETURNING 1),
+ upd_t AS (UPDATE base_t SET v = v + 100  RETURNING 1),
+ dlt_t AS (DELETE FROM base_t WHERE i IN (4,5)  RETURNING 1)
+SELECT NULL;
+SELECT * FROM mv_self ORDER BY v1;
+ROLLBACK;
+
+-- support simultaneous table changes
+BEGIN;
+CREATE TABLE base_r (i int, v int);
+CREATE TABLE base_s (i int, v int);
+INSERT INTO base_r VALUES (1, 10), (2, 20), (3, 30);
+INSERT INTO base_s VALUES (1, 100), (2, 200), (3, 300);
+CREATE INCREMENTAL MATERIALIZED VIEW mv(v1, v2) AS
+ SELECT r.v, s.v FROM base_r AS r JOIN base_s AS s USING(i);
+SELECT * FROM mv ORDER BY v1;
+WITH
+ ins_r AS (INSERT INTO base_r VALUES (1,11) RETURNING 1),
+ ins_r2 AS (INSERT INTO base_r VALUES (3,33) RETURNING 1),
+ ins_s AS (INSERT INTO base_s VALUES (2,222) RETURNING 1),
+ upd_r AS (UPDATE base_r SET v = v + 1000 WHERE i = 2 RETURNING 1),
+ dlt_s AS (DELETE FROM base_s WHERE i = 3 RETURNING 1)
+SELECT NULL;
+SELECT * FROM mv ORDER BY v1;
+ROLLBACK;
+
+-- support foreign reference constraints
+BEGIN;
+CREATE TABLE ri1 (i int PRIMARY KEY);
+CREATE TABLE ri2 (i int PRIMARY KEY REFERENCES ri1(i) ON UPDATE CASCADE ON DELETE CASCADE, v int);
+INSERT INTO ri1 VALUES (1),(2),(3);
+INSERT INTO ri2 VALUES (1),(2),(3);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ri(i1, i2) AS
+ SELECT ri1.i, ri2.i FROM ri1 JOIN ri2 USING(i);
+SELECT * FROM mv_ri ORDER BY i1;
+UPDATE ri1 SET i=10 where i=1;
+DELETE FROM ri1 WHERE i=2;
+SELECT * FROM mv_ri ORDER BY i2;
+ROLLBACK;
+
+-- views including NULL
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1,10),(2, NULL);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+SELECT * FROM mv ORDER BY i;
+UPDATE base_t SET v = 20 WHERE i = 2;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+SELECT * FROM mv ORDER BY i;
+INSERT INTO base_t VALUES (1),(NULL);
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (1, 10), (1, 20);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, sum(v) FROM base_t GROUP BY i;
+SELECT * FROM mv ORDER BY i;
+UPDATE base_t SET v = v * 10;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (NULL, 3), (NULL, 4), (NULL, 5);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, min(v), max(v) FROM base_t GROUP BY i;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 1;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 3;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 5;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+-- IMMV containing user defined type
+BEGIN;
+
+CREATE TYPE mytype;
+CREATE FUNCTION mytype_in(cstring)
+ RETURNS mytype AS 'int4in'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_out(mytype)
+ RETURNS cstring AS 'int4out'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE TYPE mytype (
+ LIKE = int4,
+ INPUT = mytype_in,
+ OUTPUT = mytype_out
+);
+
+CREATE FUNCTION mytype_eq(mytype, mytype)
+ RETURNS bool AS 'int4eq'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_lt(mytype, mytype)
+ RETURNS bool AS 'int4lt'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_cmp(mytype, mytype)
+ RETURNS integer AS 'btint4cmp'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+
+CREATE OPERATOR = (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_eq);
+CREATE OPERATOR < (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_lt);
+
+CREATE OPERATOR CLASS mytype_ops
+ DEFAULT FOR TYPE mytype USING btree AS
+ OPERATOR        1       <,
+ OPERATOR        3       = ,
+ FUNCTION		1		mytype_cmp(mytype,mytype);
+
+CREATE TABLE t_mytype (x mytype);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_mytype AS
+ SELECT * FROM t_mytype;
+INSERT INTO t_mytype VALUES ('1'::mytype);
+SELECT * FROM mv_mytype;
+
+ROLLBACK;
+
+-- outer join is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i;
+-- CTE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS
+    WITH b AS ( SELECT * FROM mv_base_b) SELECT a.i,a.j FROM mv_base_a a, b WHERE a.i = b.i;
+-- contain system column
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm01 AS SELECT i,j,xmin FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT i,j,xmin::text AS x_min FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm06 AS SELECT i,j,xidsend(xmin) AS x_min FROM mv_base_a;
+-- contain subquery
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm03 AS SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT * FROM mv_base_b) b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm05 AS SELECT i,j, (SELECT k FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+-- contain ORDER BY
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm07 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) ORDER BY i,j,k;
+-- contain HAVING
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm08 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) GROUP BY i,j,k HAVING SUM(i) > 5;
+
+-- contain view or materialized view
+CREATE VIEW b_view AS SELECT i,k FROM mv_base_b;
+CREATE MATERIALIZED VIEW b_mview AS SELECT i,k FROM mv_base_b;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm07 AS SELECT a.i,a.j FROM mv_base_a a,b_view b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm08 AS SELECT a.i,a.j FROM mv_base_a a,b_mview b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm09 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm10 AS SELECT a.i,a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) OR a.i > 5;
+
+-- contain mutable functions
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+
+-- LIMIT/OFFSET is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm13 AS SELECT i,j FROM mv_base_a LIMIT 10 OFFSET 5;
+
+-- DISTINCT ON is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm14 AS SELECT DISTINCT ON(i) i, j FROM mv_base_a;
+
+-- TABLESAMPLE clause is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm15 AS SELECT i, j FROM mv_base_a TABLESAMPLE SYSTEM(50);
+
+-- window functions are not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm16 AS SELECT *, cume_dist() OVER (ORDER BY i) AS rank FROM mv_base_a;
+
+-- aggregate function with some options is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm17 AS SELECT COUNT(*) FILTER(WHERE i < 3) FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm18 AS SELECT COUNT(DISTINCT i)  FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm19 AS SELECT array_agg(j ORDER BY i DESC) FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm20 AS SELECT i,SUM(j) FROM mv_base_a GROUP BY GROUPING SETS((i),());
+
+-- inheritance parent is not supported
+BEGIN;
+CREATE TABLE parent (i int, v int);
+CREATE TABLE child_a(options text) INHERITS(parent);
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm21 AS SELECT * FROM parent;
+ROLLBACK;
+
+-- UNION statement is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm22 AS SELECT i,j FROM mv_base_a UNION ALL SELECT i,k FROM mv_base_b;;
+
+-- empty target list is not allowed with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm25 AS SELECT FROM mv_base_a;
+
+-- FOR UPDATE/SHARE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm26 AS SELECT i,j FROM mv_base_a FOR UPDATE;
+
+-- tartget list cannot contain ivm column that start with '__ivm'
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm28 AS SELECT i AS "__ivm_count__" FROM mv_base_a;
+
+-- expressions specified in GROUP BY must appear in the target list.
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm29 AS SELECT COUNT(i) FROM mv_base_a GROUP BY i;
+
+-- experssions containing an aggregate is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm30 AS SELECT sum(i)*0.5 FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm31 AS SELECT sum(i)/sum(j) FROM mv_base_a;
+
+-- VALUES is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_only_values1 AS values(1);
+
+-- base table which has row level security
+DROP USER IF EXISTS ivm_admin;
+DROP USER IF EXISTS ivm_user;
+CREATE USER ivm_admin;
+CREATE USER ivm_user;
+SET SESSION AUTHORIZATION ivm_admin;
+
+CREATE TABLE rls_tbl(id int, data text, owner name);
+INSERT INTO rls_tbl VALUES
+  (1,'foo','ivm_user'),
+  (2,'bar','postgres');
+CREATE TABLE num_tbl(id int, num text);
+INSERT INTO num_tbl VALUES
+  (1,'one'),
+  (2,'two'),
+  (3,'three'),
+  (4,'four');
+CREATE POLICY rls_tbl_policy ON rls_tbl FOR SELECT TO PUBLIC USING(owner = current_user);
+CREATE POLICY rls_tbl_policy2 ON rls_tbl FOR INSERT TO PUBLIC WITH CHECK(current_user LIKE 'ivm_%');
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+GRANT ALL on rls_tbl TO PUBLIC;
+GRANT ALL on num_tbl TO PUBLIC;
+
+SET SESSION AUTHORIZATION ivm_user;
+
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls AS SELECT * FROM rls_tbl;
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+INSERT INTO rls_tbl VALUES
+  (3,'baz','ivm_user'),
+  (4,'qux','postgres');
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls2 AS SELECT * FROM rls_tbl JOIN num_tbl USING(id);
+
+RESET SESSION AUTHORIZATION;
+
+WITH
+ x AS (UPDATE rls_tbl SET data = data || '_2' where id in (3,4)),
+ y AS (UPDATE num_tbl SET num = num || '_2' where id in (3,4))
+SELECT;
+SELECT * FROM ivm_rls2 ORDER BY 1,2,3;
+
+DROP TABLE rls_tbl CASCADE;
+DROP TABLE num_tbl CASCADE;
+DROP USER ivm_user;
+DROP USER ivm_admin;
+
+DROP TABLE mv_base_b CASCADE;
+DROP TABLE mv_base_a CASCADE;
-- 
2.17.1

v24-0010-Add-documentations-about-Incremental-View-Mainte.patchtext/x-diff; name=v24-0010-Add-documentations-about-Incremental-View-Mainte.patchDownload
From 903c9e9d7c75aa15f2bc9544248dec9ec83fe15b Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:25:34 +0900
Subject: [PATCH v24 10/15] Add documentations about Incremental View
 Maintenance

---
 doc/src/sgml/catalogs.sgml                    |  24 +-
 .../sgml/ref/create_materialized_view.sgml    | 131 +++++-
 .../sgml/ref/refresh_materialized_view.sgml   |   6 +-
 doc/src/sgml/rules.sgml                       | 443 ++++++++++++++++++
 4 files changed, 599 insertions(+), 5 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2f0def9b19..5d4d5a6e5e 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2183,6 +2183,15 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>relisivm</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if materialized view enables incremental view maintenance
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>relrewrite</structfield> <type>oid</type>
@@ -3465,6 +3474,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para>
      </listitem>
     </varlistentry>
+
+    <varlistentry>
+     <term><symbol>DEPENDENCY_IMMV</symbol> (<literal>m</literal>)</term>
+     <listitem>
+      <para>
+       The dependent object was created as part of creation of the Materialized
+       View with Incremental View Maintenance reference, and is really just a 
+       part of its internal implementation. The dependent object must not be
+       dropped unless the materialized view is also dropped.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
 
    Other dependency flavors might be needed in future.
@@ -3848,7 +3869,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <structfield>extrelocatable</structfield> <type>bool</type>
       </para>
       <para>
-       True if extension can be relocated to another schema
+      True for materialized views which are enabled for incremental
+      view maintenance (IVM).
       </para></entry>
      </row>
 
diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml
index d8c48252f4..8a440ca810 100644
--- a/doc/src/sgml/ref/create_materialized_view.sgml
+++ b/doc/src/sgml/ref/create_materialized_view.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
+CREATE [ INCREMENTAL ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
     [ (<replaceable>column_name</replaceable> [, ...] ) ]
     [ USING <replaceable class="parameter">method</replaceable> ]
     [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
@@ -60,6 +60,132 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
   <title>Parameters</title>
 
   <variablelist>
+   <varlistentry>
+    <term><literal>INCREMENTAL</literal></term>
+    <listitem>
+     <para>
+      If specified, some triggers are automatically created so that the rows
+      of the materialized view are immediately updated when base tables of the
+      materialized view are updated. In general, this allows faster update of
+      the materialized view at a price of slower update of the base tables
+      because the triggers will be invoked. We call this form of materialized
+      view as "Incrementally Maintainable Materialized View" (IMMV).
+     </para>
+     <para>
+      When <acronym>IMMV</acronym> is defined, a unique index is created on the view
+      automatically if possible.  If the view definition query has a GROUP BY clause,
+      a unique index is created on the columns of GROUP BY expressions.  Also, if the
+      view has DISTINCT clause, a unique index is created on all columns in the target
+      list.  Otherwise, if the view contains all primary key attritubes of its base
+      tables in the target list, a unique index is created on these attritubes.  In
+      other cases, no index is created.
+     </para>
+     <para>
+      There are restrictions of query definitions allowed to use this
+      option. The following are supported in query definitions for IMMV:
+      <itemizedlist>
+
+       <listitem>
+        <para>
+         Inner joins (including self-joins).
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Some built-in aggregate functions (count, sum, avg, min, max) without a HAVING
+         clause. 
+        </para>
+        </listitem>
+      </itemizedlist>
+
+      Unsupported queries with this option include the following:
+
+      <itemizedlist>
+       <listitem>
+        <para>
+         Outer joins.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Sub-queries.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Aggregate functions other than built-in count, sum, avg, min and max.
+        </para>
+       </listitem>
+       <listitem>
+        <para>
+         Aggregate functions with a HAVING clause.
+        </para>
+       </listitem>
+       <listitem>
+        <para>
+         DISTINCT ON, WINDOW, VALUES, LIMIT and OFFSET clause.
+        </para>
+       </listitem>
+      </itemizedlist>
+
+      Other restrictions include:
+      <itemizedlist>
+
+       <listitem>
+        <para>
+         IMMVs must be based on simple base tables. It's not supported to
+         create them on top of views or materialized views.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         When the TRUNCATE command is executed on a base table,
+         no changes are made to the materialized view.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         It is not supported to include system columns in an IMMV.
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+ERROR:  system column is not supported with IVM
+         </programlisting>
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Non-immutable functions are not supported.
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+ERROR:  functions in IMMV must be marked IMMUTABLE
+         </programlisting>
+        </para>
+        </listitem>
+
+       <listitem>
+        <para>
+         IMMVs do not support expressions that contains aggregates
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Logical replication does not support IMMVs.
+        </para>
+       </listitem>
+
+      </itemizedlist>
+
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>IF NOT EXISTS</literal></term>
     <listitem>
@@ -153,7 +279,8 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
       This clause specifies whether or not the materialized view should be
       populated at creation time.  If not, the materialized view will be
       flagged as unscannable and cannot be queried until <command>REFRESH
-      MATERIALIZED VIEW</command> is used.
+      MATERIALIZED VIEW</command> is used.  Also, if the view is IMMV,
+      triggers for maintaining the view are not created.
      </para>
     </listitem>
    </varlistentry>
diff --git a/doc/src/sgml/ref/refresh_materialized_view.sgml b/doc/src/sgml/ref/refresh_materialized_view.sgml
index 3bf8884447..dc81853057 100644
--- a/doc/src/sgml/ref/refresh_materialized_view.sgml
+++ b/doc/src/sgml/ref/refresh_materialized_view.sgml
@@ -35,9 +35,11 @@ REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] <replaceable class="parameter">name</
    owner of the materialized view.  The old contents are discarded.  If
    <literal>WITH DATA</literal> is specified (or defaults) the backing query
    is executed to provide the new data, and the materialized view is left in a
-   scannable state.  If <literal>WITH NO DATA</literal> is specified no new
+   scannable state.  Also, if the view is an incrementally maintainable materialized
+   view (IMMV) and was unpopulated, triggers for maintaining
+   the view are created.  If <literal>WITH NO DATA</literal> is specified no new
    data is generated and the materialized view is left in an unscannable
-   state.
+   state.  If the view is IMMV, the triggers are dropped.
   </para>
   <para>
    <literal>CONCURRENTLY</literal> and <literal>WITH NO DATA</literal> may not
diff --git a/doc/src/sgml/rules.sgml b/doc/src/sgml/rules.sgml
index 6065b1c2a3..264fdefc37 100644
--- a/doc/src/sgml/rules.sgml
+++ b/doc/src/sgml/rules.sgml
@@ -1093,6 +1093,449 @@ SELECT word FROM words ORDER BY word &lt;-&gt; 'caterpiler' LIMIT 10;
 
 </sect1>
 
+<sect1 id="rules-ivm">
+<title>Incremental View Maintenance</title>
+
+<indexterm zone="rules-ivm">
+ <primary>incremental view maintenance</primary>
+</indexterm>
+
+<indexterm zone="rules-ivm">
+ <primary>materialized view</primary>
+ <secondary>incremental view maintenance</secondary>
+</indexterm>
+
+<indexterm zone="rules-ivm">
+ <primary>view</primary>
+ <secondary>incremental view maintenance</secondary>
+</indexterm>
+
+<sect2 id="rules-ivm-overview">
+<title>Overview</title>
+
+<para>
+    Incremental View Maintenance (<acronym>IVM</acronym>) is a way to make
+    materialized views up-to-date in which only incremental changes are computed
+    and applied on views rather than recomputing the contents from scratch as
+    <command>REFRESH MATERIALIZED VIEW</command> does.  <acronym>IVM</acronym>
+    can update materialized views more efficiently than recomputation when only
+    small parts of the view are changed.
+</para>
+
+<para>
+    There are two approaches with regard to timing of view maintenance:
+    immediate and deferred.  In immediate maintenance, views are updated in the
+    same transaction that its base table is modified.  In deferred maintenance,
+    views are updated after the transaction is committed, for example, when the
+    view is accessed, as a response to user command like <command>REFRESH
+    MATERIALIZED VIEW</command>, or periodically in background, and so on.
+    <productname>PostgreSQL</productname> currently implements only a kind of
+    immediate maintenance, in which materialized views are updated immediately
+    in AFTER triggers when a base table is modified.
+</para>
+
+<para>
+    To create materialized views supporting <acronym>IVM</acronym>, use the
+    <command>CREATE INCREMENTAL MATERIALIZED VIEW</command>, for example:
+<programlisting>
+CREATE <emphasis>INCREMENTAL</emphasis> MATERIALIZED VIEW mymatview AS SELECT * FROM mytab;
+</programlisting>
+    When a materialized view is created with the <literal>INCREMENTAL</literal>
+    keyword, some triggers are automatically created so that the view's contents are
+    immediately updated when its base tables are modified. We call this form
+    of materialized view an Incrementally Maintainable Materialized View
+    (<acronym>IMMV</acronym>).
+<programlisting>
+postgres=# CREATE INCREMENTAL MATERIALIZED VIEW m AS SELECT * FROM t0;
+NOTICE:  could not create an index on materialized view "m" automatically
+HINT:  Create an index on the materialized view for effcient incremental maintenance.
+SELECT 3
+postgres=# SELECT * FROM m;
+ i
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+postgres=# INSERT INTO t0 VALUES (4);
+INSERT 0 1
+postgres=# SELECT * FROM m; -- automatically updated
+ i
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+</programlisting>
+</para>
+
+<para>
+    Some <acronym>IMMV</acronym>s have hidden columns which are added
+    automatically when a materialized view is created. Their name starts
+    with <literal>__ivm_</literal> and they contain information required
+    for maintaining the <acronym>IMMV</acronym>. Such columns are not visible
+    when the <acronym>IMMV</acronym> is accessed by <literal>SELECT *</literal>
+    but are visible if the column name is explicitly specified in the target
+    list. We can also see the hidden columns in <literal>\d</literal>
+    meta-commands of <command>psql</command> commands.
+</para>
+
+<para>
+    In general, <acronym>IMMV</acronym>s allow faster updates of materialized
+    views at the price of slower updates to their base tables. Updates of
+    <acronym>IMMV</acronym> is slower because triggers will be invoked and the
+    view is updated in triggers per modification statement.
+</para>
+
+<para>
+    For example, suppose a normal materialized view defined as below:
+
+<programlisting>
+test=# CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm AS
+        SELECT a.aid, b.bid, a.abalance, b.bbalance
+        FROM pgbench_accounts a JOIN pgbench_branches b USING(bid);
+SELECT 10000000
+
+</programlisting>
+
+    Updating a tuple in a base table of this materialized view is rapid but the
+   <command>REFRESH MATERIALIZED VIEW</command> command on this view takes a long time:
+
+<programlisting>
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 0.990 ms
+
+test=# REFRESH MATERIALIZED VIEW mv_normal ;
+REFRESH MATERIALIZED VIEW
+Time: 33533.952 ms (00:33.534)
+</programlisting>
+</para>
+
+<para>
+    On the other hand, after creating <acronym>IMMV</acronym> with the same view
+    definition as below:
+
+<programlisting>
+test=# CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm AS
+        SELECT a.aid, b.bid, a.abalance, b.bbalance
+        FROM pgbench_accounts a JOIN pgbench_branches b USING(bid);
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+NOTICE:  created index "mv_ivm_index" on materialized view "mv_ivm"
+</programlisting>
+
+    updating a tuple in a base table takes more than the normal view,
+    but its content is updated automatically and this is faster than the
+    <command>REFRESH MATERIALIZED VIEW</command> command.
+
+<programlisting>
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 13.068 ms
+</programlisting>
+
+</para>
+
+<para>
+    Appropriate indexes on <acronym>IMMV</acronym>s are necessary for
+    efficient <acronym>IVM</acronym> because it looks for tuples to be
+    updated in <acronym>IMMV</acronym>.  If there are no indexes, it
+    will take a long time.
+</para>
+
+<para>
+    Therefore, when <acronym>IMMV</acronym> is defined, a unique index is created on the view
+    automatically if possible.  If the view definition query has a GROUP BY clause, a unique
+    index is created on the columns of GROUP BY expressions.  Also, if the view has DISTINCT
+    clause, a unique index is created on all columns in the target list. Otherwise, if the
+    view contains all primary key attritubes of its base tables in the target list, a unique
+    index is created on these attritubes.  In other cases, no index is created.
+</para>
+
+<para>
+    In the previous example, a unique index "mv_ivm_index" is created on aid and bid
+    columns of materialized view "mv_ivm", and this enables the rapid update of the view.
+    Dropping this index make updating the view take a loger time.
+<programlisting>
+test=# DROP INDEX mv_ivm_index;
+DROP INDEX
+Time: 67.081 ms
+
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 16386.245 ms (00:16.386)
+</programlisting>
+
+</para>
+
+<para>
+    <acronym>IVM</acronym> is effective when we want to keep a materialized
+    view up-to-date and small fraction of a base table is modified
+    infrequently.  Due to the overhead of immediate maintenance, <acronym>IVM</acronym>
+    is not effective when a base table is modified frequently.  Also, when a
+    large part of a base table is modified or large data is inserted into a
+    base table, <acronym>IVM</acronym> is not effective and the cost of
+    maintenance can be larger than the <command>REFRESH MATERIALIZED VIEW</command>
+    command. In such situation, we can use <command>REFRESH MATERIALIZED VIEW</command>
+    and specify <literal>WITH NO DATA</literal> to disable immediate
+    maintenance before modifying a base table. After a base table modification,
+    execute the <command>REFRESH MATERIALIZED VIEW</command> (with <literal>WITH DATA</literal>)
+    command to refresh the view data and enable immediate maintenance.
+</para>
+
+</sect2>
+
+<sect2>
+<title>Supported View Definitions and Restrictions</title>
+
+<para>
+    Currently, we can create <acronym>IMMV</acronym>s using inner joins, and some
+    aggregates. However, several restrictions apply to the definition of IMMV.
+</para>
+
+<sect3>
+<title>Joins</title>
+<para>
+    Inner joins including self-join are supported. Outer joins are not supported.
+</para>
+</sect3>
+
+<sect3>
+<title>Aggregates</title>
+<para>
+    Supported aggregate functions are <function>count</function>, <function>sum</function>,
+    <function>avg</function>, <function>min</function>, and <function>max</function>.
+    Currently, only built-in aggregate functions are supported and user defined
+    aggregates cannot be used.  When a base table is modified, the new aggregated
+    values are incrementally calculated using the old aggregated values and values
+    of related hidden columns stored in <acronym>IMMV</acronym>.
+</para>
+
+<para>
+     Note that for <function>min</function> or <function>max</function>, the new values
+     could be re-calculated from base tables with regard to the affected groups when a
+     tuple containing the current minimal or maximal values are deleted from a base table.
+     Therefore, it can takes a long time to update an <acronym>IMMV</acronym> containing
+     these functions.
+</para>
+
+<para>
+    Also note that using <function>sum</function> or <function>avg</function> on
+    <type>real</type> (<type>float4</type>) type or <type>double precision</type>
+    (<type>float8</type>) type in <acronym>IMMV</acronym> is unsafe. This is
+    because aggregated values in <acronym>IMMV</acronym> can become different from
+    results calculated from base tables due to the limited precision of these types.
+    To avoid this problem, use the <type>numeric</type> type instead.
+</para>
+
+    <sect4>
+    <title>Restrictions on Aggregates</title>
+    <para>
+        There are the following restrictions:
+    <itemizedlist>
+        <listitem>
+        <para>
+            If we have a <literal>GROUP BY</literal> clause, expressions specified in
+               <literal>GROUP BY</literal> must appear in the target list.  This is
+               how tuples to be updated in the <acronym>IMMV</acronym> are identified.
+               These attributes are used as scan keys for searching tuples in the
+               <acronym>IMMV</acronym>, so indexes on them are required for efficient
+               <acronym>IVM</acronym>.
+        </para>
+        </listitem>
+
+        <listitem>
+        <para>
+            <literal>HAVING</literal> clause cannot be used.
+        </para>
+        </listitem>
+    </itemizedlist>
+    </para>
+    </sect4>
+</sect3>
+
+<sect3>
+<title>Other General Restrictions</title>
+<para>
+    There are other restrictions which generally apply to <acronym>IMMV</acronym>:
+    <itemizedlist>
+        <listitem>
+          <para>
+           Sub-queries cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+           CTEs cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+           Window functions cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s must be based on simple base tables.  It's not
+               supported to create them on top of views or materialized views.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            LIMIT and OFFSET clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s cannot contain system columns.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s cannot contain non-immutable functions.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            UNION/INTERSECT/EXCEPT clauses cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            DISTINCT ON clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            TABLESAMPLE parameter cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            inheritance parent tables cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            VALUES clause cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <literal>GROUPING SETS</literal> and <literal>FILTER</literal> clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            FOR UPDATE/SHARE cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            targetlist cannot contain columns whose name start with <literal>__ivm_</literal>.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            targetlist cannot contain expressions which contain an aggregate in it.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+              Logical replication is not supported, that is, even when a base table
+               at a publisher node is modified, <acronym>IMMV</acronym>s at subscriber
+               nodes are not updated.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            When the <literal>TRUNCATE</literal> command is executed on a base table,
+               nothing is changed on the <acronym>IMMV</acronym>.
+          </para>
+        </listitem>
+
+    </itemizedlist>
+</para>
+</sect3>
+
+</sect2>
+
+<sect2>
+<title><literal>DISTINCT</literal></title>
+
+<para>
+    <productname>PostgreSQL</productname> supports <acronym>IMMV</acronym> with
+    <literal>DISTINCT</literal>.  For example, suppose a <acronym>IMMV</acronym>
+    defined with <literal>DISTINCT</literal> on a base table containing duplicate
+    tuples.  When tuples are deleted from the base table, a tuple in the view is
+    deleted if and only if the multiplicity of the tuple becomes zero.  Moreover,
+    when tuples are inserted into the base table, a tuple is inserted into the
+    view only if the same tuple doesn't already exist in it.
+</para>
+
+<para>
+    Physically, an <acronym>IMMV</acronym> defined with <literal>DISTINCT</literal>
+    contains tuples after eliminating duplicates, and the multiplicity of each tuple
+    is stored in a hidden column named <literal>__ivm_count__</literal>.
+</para>
+</sect2>
+
+<sect2>
+<title>Concurrent Transactions</title>
+<para>
+    Suppose an <acronym>IMMV</acronym> is defined on two base tables and each
+    table was modified in different a concurrent transaction simultaneously.
+    In the transaction which was committed first, <acronym>IMMV</acronym> can
+    be updated considering only the change which happened in this transaction.
+    On the other hand, in order to update the view correctly in the transaction
+    which was committed later, we need to know the changes occurred in
+    both transactions.  For this reason, <literal>ExclusiveLock</literal>
+    is held on an <acronym>IMMV</acronym> immediately after a base table is
+    modified in <literal>READ COMMITTED</literal> mode to make sure that
+    the <acronym>IMMV</acronym> is updated in the latter transaction after
+    the former transaction is committed.  In <literal>REPEATABLE READ</literal>
+    or <literal>SERIALIZABLE</literal> mode, an error is raised immediately
+    if lock acquisition fails because any changes which occurred in
+    other transactions are not be visible in these modes and
+    <acronym>IMMV</acronym> cannot be updated correctly in such situations.
+    However, as an exception if the view has only one base table, 
+    the lock held on thew view is <literal>RowExclusiveLock</literal>.
+</para>
+</sect2>
+
+<sect2>
+<title>Row Level Security</title>
+<para>
+    If some base tables have row level security policy, rows that are not visible
+    to the materialized view's owner are excluded from the result.  In addition, such
+    rows are excluded as well when views are incrementally maintained.  However, if a
+    new policy is defined or policies are changed after the materialized view was created,
+    the new policy will not be applied to the view contents.  To apply the new policy,
+    you need to refresh materialized views.
+</para>
+</sect2>
+
+</sect1>
+
 <sect1 id="rules-update">
 <title>Rules on <command>INSERT</command>, <command>UPDATE</command>, and <command>DELETE</command></title>
 
-- 
2.17.1

#202Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#198)
Re: Implementing Incremental View Maintenance

Hello Takahashi-san,

On Wed, 22 Sep 2021 18:53:43 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hello Takahashi-san,

On Thu, 5 Aug 2021 08:53:47 +0000
"r.takahashi_2@fujitsu.com" <r.takahashi_2@fujitsu.com> wrote:

Hi Nagata-san,

Thank you for your reply.

I'll investigate this more, but we may have to prohibit views on partitioned
table and partitions.

I think this restriction is strict.
This feature is useful when the base table is large and partitioning is also useful in such case.

One reason of this issue is the lack of triggers on partitioned tables or partitions that
are not specified in the view definition.

However, even if we create triggers recursively on the parents or children, we would still
need more consideration. This is because we will have to convert the format of tuple of
modified table to the format of the table specified in the view for cases that the parent
and some children have different format.

I think supporting partitioned tables can be left for the next release.

I have several additional comments on the patch.

(1)
The following features are added to transition table.
- Prolong lifespan of transition table
- If table has row security policies, set them to the transition table
- Calculate pre-state of the table

Are these features only for IVM?
If there are other useful case, they should be separated from IVM patch and
should be independent patch for transition table.

Maybe. However, we don't have good idea about use cases other than IVM of
them for now...

(2)
DEPENDENCY_IMMV (m) is added to deptype of pg_depend.
What is the difference compared with existing deptype such as DEPENDENCY_INTERNAL (i)?

DEPENDENCY_IMMV was added to clear that a certain trigger is related to IMMV.
We dropped the IVM trigger and its dependencies from IMMV when REFRESH ... WITH NO DATA
is executed. Without the new deptype, we may accidentally delete a dependency created
with an intention other than the IVM trigger.

(3)
Converting from normal materialized view to IVM or from IVM to normal materialized view is not implemented yet.
Is it difficult?

I think create/drop triggers and __ivm_ columns can achieve this feature.

I think it is harder than you expected. When an IMMV is switched to a normal
materialized view, we needs to drop hidden columns (__ivm_count__ etc.), and in
the opposite case, we need to create them again. The former (IMMV->IVM) might be
easer, but for the latter (IVM->IMMV) I wonder we would need to re-create IMMV.

I am sorry but I found a mistake in the above description.
"IMMV->IVM" and "IVM->IMMV" were wrong. I've should use "IMMV->MV" and "MV->IMMV"
where MV means normal materialized view.w.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#203Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#201)
10 attachment(s)
Re: Implementing Incremental View Maintenance

Hi hackers,

I attached the rebased patch set.

Regards,
Yugo Nagata

On Thu, 23 Sep 2021 04:57:30 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Wed, 22 Sep 2021 19:17:12 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi hackers,

I attached the updated patch including fixes reported by
Zhihong Yu and Ryohei Takahashi.

Cfbot seems to fail to open the tar file, so I attached
patch files instead of tar ball.

Regards,
Yugo Nagata

On Wed, 22 Sep 2021 19:12:27 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hello Takahashi-san,

On Mon, 6 Sep 2021 10:06:37 +0000
"r.takahashi_2@fujitsu.com" <r.takahashi_2@fujitsu.com> wrote:

Hi Nagata-san,

I'm still reading the patch.
I have additional comments.

Thank you for your comments!

(1)
In v23-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patch, ivm member is added to IntoClause struct.
I think it is necessary to modify _copyIntoClause() and _equalIntoClause() functions.

Ok. I'll fix _copyIntoClause() and _equalIntoClause() as well as _readIntoClause() and _outIntoClause().

(2)
By executing pg_dump with v23-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patch,
the constraint which is automatically created during "CREATE INCREMENTAL MATERIALIZED VIEW" is also dumped.
This cause error during recovery as follows.

ivm=# create table t (c1 int, c2 int);
CREATE TABLE
ivm=# create incremental materialized view ivm_t as select distinct c1 from t;
NOTICE: created index "ivm_t_index" on materialized view "ivm_t"
SELECT 0

Then I executed pg_dump.

In the dump, the following SQLs appear.

CREATE INCREMENTAL MATERIALIZED VIEW public.ivm_t AS
SELECT DISTINCT t.c1
FROM public.t
WITH NO DATA;

ALTER TABLE ONLY public.ivm_t
ADD CONSTRAINT ivm_t_index UNIQUE (c1);

If I execute psql with the result of pg_dump, following error occurs.

ERROR: ALTER action ADD CONSTRAINT cannot be performed on relation "ivm_t"
DETAIL: This operation is not supported for materialized views.

Good catch! It was my mistake creating unique constraints on IMMV in spite of
we cannot defined them via SQL. I'll fix it to use unique indexes instead of
constraints.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

v24-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patchtext/x-diff; name=v24-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patchDownload
From f1da2a74414b83430fbb93baa38b85c8390059bd Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:05:02 +0900
Subject: [PATCH v24 01/15] Add a syntax to create Incrementally Maintainable
 Materialized Views

Allow to create Incrementally Maintainable Materialized View (IMMV)
by using INCREMENTAL option in CREATE MATERIALIZED VIEW command
as follow:

     CREATE [INCREMANTAL] MATERIALIZED VIEW xxxxx AS SELECT ....;
---
 src/backend/nodes/copyfuncs.c  |  1 +
 src/backend/nodes/equalfuncs.c |  1 +
 src/backend/nodes/outfuncs.c   |  1 +
 src/backend/nodes/readfuncs.c  |  1 +
 src/backend/parser/gram.y      | 32 +++++++++++++++++++++-----------
 src/include/nodes/primnodes.h  |  1 +
 src/include/parser/kwlist.h    |  1 +
 7 files changed, 27 insertions(+), 11 deletions(-)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 82464c9889..5769a10586 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1423,6 +1423,7 @@ _copyIntoClause(const IntoClause *from)
 	COPY_STRING_FIELD(tableSpaceName);
 	COPY_NODE_FIELD(viewQuery);
 	COPY_SCALAR_FIELD(skipData);
+	COPY_SCALAR_FIELD(ivm);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index f537d3eb96..80497dc240 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -155,6 +155,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
 	COMPARE_STRING_FIELD(tableSpaceName);
 	COMPARE_NODE_FIELD(viewQuery);
 	COMPARE_SCALAR_FIELD(skipData);
+	COMPARE_SCALAR_FIELD(ivm);
 
 	return true;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2e5ed77e18..f6166f7859 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1116,6 +1116,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
 	WRITE_STRING_FIELD(tableSpaceName);
 	WRITE_NODE_FIELD(viewQuery);
 	WRITE_BOOL_FIELD(skipData);
+	WRITE_BOOL_FIELD(ivm);
 }
 
 static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index abf08b7a2f..1e55a58f69 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -565,6 +565,7 @@ _readIntoClause(void)
 	READ_STRING_FIELD(tableSpaceName);
 	READ_NODE_FIELD(viewQuery);
 	READ_BOOL_FIELD(skipData);
+	READ_BOOL_FIELD(ivm);
 
 	READ_DONE();
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d0eb80e69c..78e17a655b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -444,6 +444,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target create_mv_target
+%type <boolean>	incremental
 
 %type <defelt>	createfunc_opt_item common_func_opt_item dostmt_opt_item
 %type <fun_param> func_arg func_arg_with_default table_func_column aggr_arg
@@ -675,7 +676,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
 	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
-	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+	INCLUDING INCREMENT INCREMENTAL INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
 	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
 	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -4271,30 +4272,32 @@ opt_with_data:
  *****************************************************************************/
 
 CreateMatViewStmt:
-		CREATE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
+		CREATE OptNoLog incremental MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $7;
-					ctas->into = $5;
+					ctas->query = $8;
+					ctas->into = $6;
 					ctas->objtype = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = false;
 					/* cram additional flags into the IntoClause */
-					$5->rel->relpersistence = $2;
-					$5->skipData = !($8);
+					$6->rel->relpersistence = $2;
+					$6->skipData = !($9);
+					$6->ivm = $3;
 					$$ = (Node *) ctas;
 				}
-		| CREATE OptNoLog MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
+		| CREATE OptNoLog incremental MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $10;
-					ctas->into = $8;
+					ctas->query = $11;
+					ctas->into = $9;
 					ctas->objtype = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = true;
 					/* cram additional flags into the IntoClause */
-					$8->rel->relpersistence = $2;
-					$8->skipData = !($11);
+					$9->rel->relpersistence = $2;
+					$9->skipData = !($12);
+					$9->ivm = $3;
 					$$ = (Node *) ctas;
 				}
 		;
@@ -4311,9 +4314,14 @@ create_mv_target:
 					$$->tableSpaceName = $5;
 					$$->viewQuery = NULL;		/* filled at analysis time */
 					$$->skipData = false;		/* might get changed later */
+					$$->ivm = false;
 				}
 		;
 
+incremental:	INCREMENTAL				{ $$ = true; }
+				| /*EMPTY*/				{ $$ = false; }
+		;
+
 OptNoLog:	UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
 		;
@@ -15664,6 +15672,7 @@ unreserved_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
@@ -16216,6 +16225,7 @@ bare_label_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 433437643e..7ea80c7ded 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -117,6 +117,7 @@ typedef struct IntoClause
 	char	   *tableSpaceName; /* table space to use, or NULL */
 	Node	   *viewQuery;		/* materialized view's SELECT query */
 	bool		skipData;		/* true for WITH NO DATA */
+	bool		ivm;			/* true for WITH IVM */
 } IntoClause;
 
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf876..2cafb4e7fe 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -205,6 +205,7 @@ PG_KEYWORD("in", IN_P, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("incremental", INCREMENTAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD, BARE_LABEL)
-- 
2.17.1

v24-0002-Add-relisivm-column-to-pg_class-system-catalog.patchtext/x-diff; name=v24-0002-Add-relisivm-column-to-pg_class-system-catalog.patchDownload
From dd5dc3c679b08353affbbf6f0a259849c3b04c1c Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:07:23 +0900
Subject: [PATCH v24 02/15] Add relisivm column to pg_class system catalog

If this boolean column is true, a relations is Incrementally Maintainable
Materialized View (IMMV). This is set when IMMV is created.
---
 src/backend/catalog/heap.c          |  1 +
 src/backend/catalog/index.c         |  1 +
 src/backend/utils/cache/lsyscache.c | 24 ++++++++++++++++++++++++
 src/backend/utils/cache/relcache.c  |  2 ++
 src/include/catalog/pg_class.h      |  3 +++
 src/include/utils/lsyscache.h       |  1 +
 src/include/utils/rel.h             |  2 ++
 7 files changed, 34 insertions(+)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 81cc39fb70..e2bc308dff 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -966,6 +966,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
+	values[Anum_pg_class_relisivm - 1] = BoolGetDatum(rd_rel->relisivm);
 	if (relacl != (Datum) 0)
 		values[Anum_pg_class_relacl - 1] = relacl;
 	else
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 26bfa74ce7..763f442a1d 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -961,6 +961,7 @@ index_create(Relation heapRelation,
 	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
 	indexRelation->rd_rel->relam = accessMethodObjectId;
 	indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid);
+	indexRelation->rd_rel->relisivm = false;
 
 	/*
 	 * store index's pg_class entry
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 4ebaa552a2..c626df32cc 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2013,6 +2013,30 @@ get_rel_relispartition(Oid relid)
 		return false;
 }
 
+/*
+ * get_rel_relisivm
+ *
+ *		Returns the relisivm flag associated with a given relation.
+ */
+bool
+get_rel_relisivm(Oid relid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp);
+		bool		result;
+
+		result = reltup->relisivm;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return false;
+}
+
 /*
  * get_rel_tablespace
  *
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9fa9e671a1..d80b652f55 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1916,6 +1916,8 @@ formrdesc(const char *relationName, Oid relationReltype,
 
 	/* ... and they're always populated, too */
 	relation->rd_rel->relispopulated = true;
+	/* ... and they're always no ivm, too */
+	relation->rd_rel->relisivm = false;
 
 	relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
 	relation->rd_rel->relpages = 0;
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fef9945ed8..2058978b89 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -119,6 +119,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
 	/* is relation a partition? */
 	bool		relispartition BKI_DEFAULT(f);
 
+	/* is relation a matview with ivm? */
+	bool		relisivm BKI_DEFAULT(f);
+
 	/* link to original rel during table rewrite; otherwise 0 */
 	Oid			relrewrite BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_class);
 
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 77871aaefc..09346aaa17 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -137,6 +137,7 @@ extern Oid	get_rel_namespace(Oid relid);
 extern Oid	get_rel_type_id(Oid relid);
 extern char get_rel_relkind(Oid relid);
 extern bool get_rel_relispartition(Oid relid);
+extern bool get_rel_relisivm(Oid relid);
 extern Oid	get_rel_tablespace(Oid relid);
 extern char get_rel_persistence(Oid relid);
 extern Oid	get_transform_fromsql(Oid typid, Oid langid, List *trftypes);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b4faa1c123..b5028f3449 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -649,6 +649,8 @@ RelationGetSmgr(Relation rel)
  */
 #define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated)
 
+#define RelationIsIVM(relation) ((relation)->rd_rel->relisivm)
+
 /*
  * RelationIsAccessibleInLogicalDecoding
  *		True if we need to log enough information to have access via
-- 
2.17.1

v24-0003-Add-new-deptype-option-m-in-pg_depend-system-cat.patchtext/x-diff; name=v24-0003-Add-new-deptype-option-m-in-pg_depend-system-cat.patchDownload
From 259f92b2870b314873837c0d8b71c3225370e431 Mon Sep 17 00:00:00 2001
From: Takuma Hoshiai <takuma.hoshiai@gmail.com>
Date: Fri, 17 Jan 2020 16:04:14 +0900
Subject: [PATCH v24 03/15] Add new deptype option 'm' in pg_depend system
 catalog

The deptype option 'm' mean specific database obejects referenced Incrementally
Maintainable Materialized View(IMMV). If set NO DATA flag to IMVM, these
database objects must be dropped.
---
 src/backend/catalog/dependency.c | 2 ++
 src/include/catalog/dependency.h | 3 ++-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 9f8eb1a37f..dc01822569 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -598,6 +598,7 @@ findDependentObjects(const ObjectAddress *object,
 			case DEPENDENCY_NORMAL:
 			case DEPENDENCY_AUTO:
 			case DEPENDENCY_AUTO_EXTENSION:
+			case DEPENDENCY_IMMV:
 				/* no problem */
 				break;
 
@@ -915,6 +916,7 @@ findDependentObjects(const ObjectAddress *object,
 				subflags = DEPFLAG_AUTO;
 				break;
 			case DEPENDENCY_INTERNAL:
+			case DEPENDENCY_IMMV:
 				subflags = DEPFLAG_INTERNAL;
 				break;
 			case DEPENDENCY_PARTITION_PRI:
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 3eca295ff4..6c0d5c3bf2 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -36,7 +36,8 @@ typedef enum DependencyType
 	DEPENDENCY_PARTITION_PRI = 'P',
 	DEPENDENCY_PARTITION_SEC = 'S',
 	DEPENDENCY_EXTENSION = 'e',
-	DEPENDENCY_AUTO_EXTENSION = 'x'
+	DEPENDENCY_AUTO_EXTENSION = 'x',
+	DEPENDENCY_IMMV = 'm'
 } DependencyType;
 
 /*
-- 
2.17.1

v24-0004-Allow-to-prolong-life-span-of-transition-tables-.patchtext/x-diff; name=v24-0004-Allow-to-prolong-life-span-of-transition-tables-.patchDownload
From 3f064e505db2bf70e1045de243c8d36b3167a0ec Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:09:45 +0900
Subject: [PATCH v24 04/15] Allow to prolong life span of transition tables
 until transaction end

Originally, tuplestores of AFTER trigger's transition tables were
freed for each query depth. For our IVM implementation, we would like
to prolong life of the tuplestores because we have to preserve them
for a whole query assuming that some base tables might be changed
in some trigger functions.
---
 src/backend/commands/trigger.c | 80 +++++++++++++++++++++++++++++++++-
 src/include/commands/trigger.h |  2 +
 2 files changed, 80 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index d8890d2c74..c7a2bc3ed8 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3570,6 +3570,10 @@ typedef struct AfterTriggerEventList
  * end of the list, so it is relatively easy to discard them.  The event
  * list chunks themselves are stored in event_cxt.
  *
+ * prolonged_tuplestored is a list of transition table tuplestores whose
+ * life are prolonged to the end of the outmost query instead of each nested
+ * query.
+ *
  * query_depth is the current depth of nested AfterTriggerBeginQuery calls
  * (-1 when the stack is empty).
  *
@@ -3635,6 +3639,7 @@ typedef struct AfterTriggersData
 	SetConstraintState state;	/* the active S C state */
 	AfterTriggerEventList events;	/* deferred-event list */
 	MemoryContext event_cxt;	/* memory context for events, if any */
+	List   *prolonged_tuplestores;	/* list of prolonged tuplestores */
 
 	/* per-query-level data: */
 	AfterTriggersQueryData *query_stack;	/* array of structs shown below */
@@ -3670,6 +3675,7 @@ struct AfterTriggersTableData
 	bool		closed;			/* true when no longer OK to add tuples */
 	bool		before_trig_done;	/* did we already queue BS triggers? */
 	bool		after_trig_done;	/* did we already queue AS triggers? */
+	bool		prolonged;			/* are transition tables prolonged? */
 	AfterTriggerEventList after_trig_events;	/* if so, saved list pointer */
 	Tuplestorestate *old_tuplestore;	/* "old" transition table, if any */
 	Tuplestorestate *new_tuplestore;	/* "new" transition table, if any */
@@ -4448,6 +4454,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
 }
 
 
+/*
+ * SetTransitionTablePreserved
+ *
+ * Prolong lifespan of transition tables corresponding specified relid and
+ * command type to the end of the outmost query instead of each nested query.
+ * This enables to use nested AFTER trigger's transition tables from outer
+ * query's triggers.  Currently, only immediate incremental view maintenance
+ * uses this.
+ */
+void
+SetTransitionTablePreserved(Oid relid, CmdType cmdType)
+{
+	AfterTriggersTableData *table;
+	AfterTriggersQueryData *qs;
+	bool		found = false;
+	ListCell   *lc;
+
+	/* Check state, like AfterTriggerSaveEvent. */
+	if (afterTriggers.query_depth < 0)
+		elog(ERROR, "SetTransitionTablePreserved() called outside of query");
+
+	qs = &afterTriggers.query_stack[afterTriggers.query_depth];
+
+	foreach(lc, qs->tables)
+	{
+		table = (AfterTriggersTableData *) lfirst(lc);
+		if (table->relid == relid && table->cmdType == cmdType &&
+			table->closed)
+		{
+			table->prolonged = true;
+			found = true;
+		}
+	}
+
+	if (!found)
+		elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType);
+}
+
+
 /*
  * GetAfterTriggersTableData
  *
@@ -4642,6 +4687,7 @@ AfterTriggerBeginXact(void)
 	 */
 	afterTriggers.firing_counter = (CommandId) 1;	/* mustn't be 0 */
 	afterTriggers.query_depth = -1;
+	afterTriggers.prolonged_tuplestores = NIL;
 
 	/*
 	 * Verify that there is no leftover state remaining.  If these assertions
@@ -4802,11 +4848,29 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
 		ts = table->old_tuplestore;
 		table->old_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+		{
+			if (table->prolonged && afterTriggers.query_depth > 0)
+			{
+				MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext);
+				afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts);
+				MemoryContextSwitchTo(oldcxt);
+			}
+			else
+				tuplestore_end(ts);
+		}
 		ts = table->new_tuplestore;
 		table->new_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+		{
+			if (table->prolonged && afterTriggers.query_depth > 0)
+			{
+				MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext);
+				afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts);
+				MemoryContextSwitchTo(oldcxt);
+			}
+			else
+				tuplestore_end(ts);
+		}
 		if (table->storeslot)
 			ExecDropSingleTupleTableSlot(table->storeslot);
 	}
@@ -4818,6 +4882,18 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
 	 */
 	qs->tables = NIL;
 	list_free_deep(tables);
+
+	/* Release prolonged tuplestores at the end of the outmost query */
+	if (afterTriggers.query_depth == 0)
+	{
+		foreach(lc, afterTriggers.prolonged_tuplestores)
+		{
+			ts = (Tuplestorestate *) lfirst(lc);
+			if (ts)
+				tuplestore_end(ts);
+		}
+		afterTriggers.prolonged_tuplestores = NIL;
+	}
 }
 
 
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 9ef7f6d768..0cfb0b3c5c 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -251,6 +251,8 @@ extern void AfterTriggerEndSubXact(bool isCommit);
 extern void AfterTriggerSetState(ConstraintsSetStmt *stmt);
 extern bool AfterTriggerPendingOnRel(Oid relid);
 
+extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType);
+
 
 /*
  * in utils/adt/ri_triggers.c
-- 
2.17.1

v24-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patchtext/x-diff; name=v24-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patchDownload
From 37ec819ca3bfb7b58399c13fafb600a6fd366613 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Wed, 11 Nov 2020 17:01:25 +0900
Subject: [PATCH v24 05/15] Add Incremental View Maintenance support to pg_dump

Support CREATE INCREMENTAL MATERIALIZED VIEW syntax.
---
 src/bin/pg_dump/pg_dump.c        | 20 +++++++++++++++++---
 src/bin/pg_dump/pg_dump.h        |  1 +
 src/bin/pg_dump/t/002_pg_dump.pl | 15 +++++++++++++++
 3 files changed, 33 insertions(+), 3 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d1842edde0..d44361a47b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6393,6 +6393,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_partkeydef;
 	int			i_ispartition;
 	int			i_partbound;
+	int			i_isivm;
 
 	/*
 	 * Find all the tables and table-like objects.
@@ -6596,12 +6597,19 @@ getTables(Archive *fout, int *numTables)
 		appendPQExpBufferStr(query,
 							 "pg_get_partkeydef(c.oid) AS partkeydef, "
 							 "c.relispartition AS ispartition, "
-							 "pg_get_expr(c.relpartbound, c.oid) AS partbound ");
+							 "pg_get_expr(c.relpartbound, c.oid) AS partbound, ");
 	else
 		appendPQExpBufferStr(query,
 							 "NULL AS partkeydef, "
 							 "false AS ispartition, "
-							 "NULL AS partbound ");
+							 "NULL AS partbound, ");
+
+	if (fout->remoteVersion >= 150000)
+		appendPQExpBufferStr(query,
+							 "c.relisivm AS isivm ");
+	else
+		appendPQExpBufferStr(query,
+							 "false AS isivm ");
 
 	/*
 	 * Left join to pg_depend to pick up dependency info linking sequences to
@@ -6719,6 +6727,8 @@ getTables(Archive *fout, int *numTables)
 	i_partkeydef = PQfnumber(res, "partkeydef");
 	i_ispartition = PQfnumber(res, "ispartition");
 	i_partbound = PQfnumber(res, "partbound");
+	i_isivm = PQfnumber(res, "isivm");
+
 
 	if (dopt->lockWaitTimeout)
 	{
@@ -6796,6 +6806,8 @@ getTables(Archive *fout, int *numTables)
 		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 		tblinfo[i].ispartition = (strcmp(PQgetvalue(res, i, i_ispartition), "t") == 0);
 		tblinfo[i].partbound = pg_strdup(PQgetvalue(res, i, i_partbound));
+		tblinfo[i].isivm = (strcmp(PQgetvalue(res, i, i_isivm), "t") == 0);
+
 
 		/* other fields were zeroed above */
 
@@ -15868,9 +15880,11 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
-		appendPQExpBuffer(q, "CREATE %s%s %s",
+		appendPQExpBuffer(q, "CREATE %s%s%s %s",
 						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
 						  "UNLOGGED " : "",
+						  tbinfo->relkind == RELKIND_MATVIEW && tbinfo->isivm ?
+						  "INCREMENTAL " : "",
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f9af14b793..c4e226831c 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -301,6 +301,7 @@ typedef struct _tableInfo
 	bool		dummy_view;		/* view's real definition must be postponed */
 	bool		postponed_def;	/* matview must be postponed into post-data */
 	bool		ispartition;	/* is table a partition? */
+	bool		isivm;			/* is incrementally maintainable materialized view? */
 
 	/*
 	 * These fields are computed only if we decide the table is interesting
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index d293f52b05..80efbcc7bc 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2180,6 +2180,21 @@ my %tests = (
 		  { exclude_dump_test_schema => 1, no_toast_compression => 1, },
 	},
 
+	'CREATE MATERIALIZED VIEW matview_ivm' => {
+		create_order => 21,
+		create_sql   => 'CREATE INCREMENTAL MATERIALIZED VIEW dump_test.matview_ivm (col1) AS
+					   SELECT col1 FROM dump_test.test_table;',
+		regexp => qr/^
+			\QCREATE INCREMENTAL MATERIALIZED VIEW dump_test.matview_ivm AS\E
+			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QFROM dump_test.test_table\E
+			\n\s+\QWITH NO DATA;\E
+			/xm,
+		like =>
+		  { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, },
+	},
+
 	'CREATE POLICY p1 ON test_table' => {
 		create_order => 22,
 		create_sql   => 'CREATE POLICY p1 ON dump_test.test_table
-- 
2.17.1

v24-0006-Add-Incremental-View-Maintenance-support-to-psql.patchtext/x-diff; name=v24-0006-Add-Incremental-View-Maintenance-support-to-psql.patchDownload
From 3537e13d686bafa9c31fdde641fca230ff6eef19 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:21:54 +0900
Subject: [PATCH v24 06/15] Add Incremental View Maintenance support to psql

Add tab completion and meta-command output for IVM.
---
 src/bin/psql/describe.c     | 32 +++++++++++++++++++++++++++++++-
 src/bin/psql/tab-complete.c | 14 +++++++++-----
 2 files changed, 40 insertions(+), 6 deletions(-)

diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 006661412e..edcc0d8c10 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1656,6 +1656,7 @@ describeOneTableDetails(const char *schemaname,
 		char		relpersistence;
 		char		relreplident;
 		char	   *relam;
+		bool		isivm;
 	}			tableinfo;
 	bool		show_column_details = false;
 
@@ -1668,7 +1669,26 @@ describeOneTableDetails(const char *schemaname,
 	initPQExpBuffer(&tmpbuf);
 
 	/* Get general table info */
-	if (pset.sversion >= 120000)
+	if (pset.sversion >= 150000)
+	{
+		printfPQExpBuffer(&buf,
+						  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+						  "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, "
+						  "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
+						  "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
+						  "c.relpersistence, c.relreplident, am.amname, "
+						  "c.relisivm\n"
+						  "FROM pg_catalog.pg_class c\n "
+						  "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+						  "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
+						  "WHERE c.oid = '%s';",
+						  (verbose ?
+						   "pg_catalog.array_to_string(c.reloptions || "
+						   "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+						   : "''"),
+						  oid);
+	}
+	else if (pset.sversion >= 120000)
 	{
 		printfPQExpBuffer(&buf,
 						  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
@@ -1852,6 +1872,10 @@ describeOneTableDetails(const char *schemaname,
 			(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
 	else
 		tableinfo.relam = NULL;
+	if (pset.sversion >= 150000)
+		tableinfo.isivm = strcmp(PQgetvalue(res, 0, 15), "t") == 0;
+	else
+		tableinfo.isivm = false;
 	PQclear(res);
 	res = NULL;
 
@@ -3652,6 +3676,12 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
 			printTableAddFooter(&cont, buf.data);
 		}
+
+		/* Incremental view maintance info */
+		if (verbose && tableinfo.relkind == RELKIND_MATVIEW && tableinfo.isivm)
+		{
+			printTableAddFooter(&cont, _("Incremental view maintenance: yes"));
+		}
 	}
 
 	/* reloptions, if verbose */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8e01f54500..4533fd75d9 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1059,6 +1059,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"INCREMENTAL MATERIALIZED VIEW", NULL, NULL, &Query_for_list_of_matviews, THING_NO_DROP | THING_NO_ALTER},
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2783,7 +2784,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
-		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
+		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW", "INCREMENTAL MATERIALIZED VIEW");
 	/* Complete PARTITION BY with RANGE ( or LIST ( or ... */
 	else if (TailMatches("PARTITION", "BY"))
 		COMPLETE_WITH("RANGE (", "LIST (", "HASH (");
@@ -3082,13 +3083,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches("CREATE", "MATERIALIZED"))
+	else if (Matches("CREATE", "MATERIALIZED") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED"))
 		COMPLETE_WITH("VIEW");
-	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	/* Complete CREATE MATERIALIZED VIEW <name> with AS  */
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny) ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH("SELECT");
 
 /* CREATE EVENT TRIGGER */
-- 
2.17.1

v24-0007-Add-Incremental-View-Maintenance-support.patchtext/x-diff; name=v24-0007-Add-Incremental-View-Maintenance-support.patchDownload
From af2d4dc7b2a8347bf600e40c6dbeabfbef8312b8 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Tue, 22 Dec 2020 18:40:18 +0900
Subject: [PATCH v24 07/15] Add Incremental View Maintenance support

In this implementation, AFTER triggers are used to collect
tuplestores containing transition table contents. When multiple tables
are changed, multiple AFTER triggers are invoked, then the final AFTER
trigger performs actual update of the matview. In addition, BEFORE
triggers are also used to handle global information for view
maintenance. To calculate view deltas, we need both pre-state and
post-state of base tables. Post-update states are available in AFTER
trigger, and pre-update states can be calculated by filtering inserted
tuples using cmin/xmin system columns, and append deleted tuples which
are contained in an old transition table.

This patch also allows self-join, simultaneous updates of more than
one base table, and multiple updates of the same base table.

Incrementally Maintainable Materialized Views (IMMV) can contain
duplicated tuples. Also, DISTINCT clause is supported. When IMMV is
created with DISTINCT, multiplicity of tuples is counted and stored
in  "__ivm_count__" column, which is a hidden column of IMMV.
The value in __ivm_count__ is updated when IMMV is maintained
incrementally. A tuple in IMMV can be removed if and only if the
count becomes zero.
---
 src/backend/access/transam/xact.c   |    5 +
 src/backend/commands/createas.c     |  713 ++++++++++++
 src/backend/commands/indexcmds.c    |   40 +
 src/backend/commands/matview.c      | 1549 ++++++++++++++++++++++++++-
 src/backend/commands/tablecmds.c    |    9 +
 src/backend/nodes/copyfuncs.c       |    1 +
 src/backend/nodes/equalfuncs.c      |    1 +
 src/backend/nodes/outfuncs.c        |    1 +
 src/backend/nodes/readfuncs.c       |    1 +
 src/backend/parser/parse_relation.c |   18 +-
 src/backend/rewrite/rewriteDefine.c |    3 +-
 src/include/catalog/pg_proc.dat     |    8 +
 src/include/commands/createas.h     |    5 +
 src/include/commands/matview.h      |    8 +
 src/include/nodes/parsenodes.h      |    2 +
 15 files changed, 2324 insertions(+), 40 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index ca6f6d57d3..b67bdc240a 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_enum.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/matview.h"
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "executor/spi.h"
@@ -2725,6 +2726,7 @@ AbortTransaction(void)
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
 	AtAbort_Twophase();
+	AtAbort_IVM();
 
 	/*
 	 * Advertise the fact that we aborted in pg_xact (assuming that we got as
@@ -4969,6 +4971,9 @@ AbortSubTransaction(void)
 	AbortBufferIO();
 	UnlockBuffers();
 
+	/* Clean up hash entries for incremental view maintenance */
+	AtAbort_IVM();
+
 	/* Reset WAL record construction state */
 	XLogResetInsertion();
 
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 0982851715..91888891bf 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -32,24 +32,41 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
 #include "catalog/toasting.h"
 #include "commands/createas.h"
+#include "commands/defrem.h"
 #include "commands/matview.h"
 #include "commands/prepare.h"
 #include "commands/tablecmds.h"
+#include "commands/tablespace.h"
+#include "commands/trigger.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "optimizer/clauses.h"
+#include "optimizer/optimizer.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_func.h"
+#include "parser/parse_type.h"
 #include "rewrite/rewriteHandler.h"
+#include "rewrite/rewriteManip.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
+#include "utils/syscache.h"
 
 typedef struct
 {
@@ -73,6 +90,13 @@ static bool intorel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void intorel_shutdown(DestReceiver *self);
 static void intorel_destroy(DestReceiver *self);
 
+static void CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid,
+									 Relids *relids, bool ex_lock);
+static void CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock);
+static void check_ivm_restriction(Node *node);
+static bool check_ivm_restriction_walker(Node *node, void *context);
+static void CreateIndexOnIMMV(Query *query, Relation matviewRel);
+static Bitmapset *get_primary_key_attnos_from_query(Query *qry, List **constraintList);
 
 /*
  * create_ctas_internal
@@ -108,6 +132,8 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	create->oncommit = into->onCommit;
 	create->tablespacename = into->tableSpaceName;
 	create->if_not_exists = false;
+	/* Using Materialized view only */
+	create->ivm = into->ivm;
 	create->accessMethod = into->accessMethod;
 
 	/*
@@ -238,6 +264,7 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 	List	   *rewritten;
 	PlannedStmt *plan;
 	QueryDesc  *queryDesc;
+	Query	   *query_immv = NULL;
 
 	/* Check if the relation exists or not */
 	if (CreateTableAsRelExists(stmt))
@@ -282,6 +309,22 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 		save_nestlevel = NewGUCNestLevel();
 	}
 
+	if (is_matview && into->ivm)
+	{
+		/* check if the query is supported in IMMV definition */
+		if (contain_mutable_functions((Node *) query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("mutable function is not supported on incrementally maintainable materialized view"),
+					 errhint("functions must be marked IMMUTABLE")));
+
+		check_ivm_restriction((Node *) query);
+
+		/* For IMMV, we need to rewrite matview query */
+		query = rewriteQueryForIMMV(query, into->colNames);
+		query_immv = copyObject(query);
+	}
+
 	if (into->skipData)
 	{
 		/*
@@ -358,11 +401,75 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 
 		/* Restore userid and security context */
 		SetUserIdAndSecContext(save_userid, save_sec_context);
+
+		if (into->ivm)
+		{
+			Oid matviewOid = address.objectId;
+			Relation matviewRel = table_open(matviewOid, NoLock);
+
+			/*
+			 * Mark relisivm field, if it's a matview and into->ivm is true.
+			 */
+			SetMatViewIVMState(matviewRel, true);
+
+			/* Create an index on incremental maintainable materialized view, if possible */
+			CreateIndexOnIMMV((Query *) into->viewQuery, matviewRel);
+
+			/* Create triggers on incremental maintainable materialized view */
+			if (!into->skipData)
+			{
+				Assert(query_immv != NULL);
+				CreateIvmTriggersOnBaseTables(query_immv, matviewOid, true);
+			}
+			table_close(matviewRel, NoLock);
+		}
 	}
 
 	return address;
 }
 
+/*
+ * rewriteQueryForIMMV -- rewrite view definition query for IMMV
+ *
+ * count(*) is added for counting distinct tuples in views.
+ */
+Query *
+rewriteQueryForIMMV(Query *query, List *colNames)
+{
+	Query *rewritten;
+
+	TargetEntry *tle;
+	Node *node;
+	ParseState *pstate = make_parsestate(NULL);
+	FuncCall *fn;
+
+	rewritten = copyObject(query);
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	/*
+	 * Convert DISTINCT to GROUP BY and add count(*) for counting distinct
+	 * tuples in views.
+	 */
+	if (rewritten->distinctClause)
+	{
+		rewritten->groupClause = transformDistinctClause(NULL, &rewritten->targetList, rewritten->sortClause, false);
+
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+		fn->agg_star = true;
+
+		node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+		tle = makeTargetEntry((Expr *) node,
+								list_length(rewritten->targetList) + 1,
+								pstrdup("__ivm_count__"),
+								false);
+		rewritten->targetList = lappend(rewritten->targetList, tle);
+		rewritten->hasAggs = true;
+	}
+
+	return rewritten;
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -623,3 +730,609 @@ intorel_destroy(DestReceiver *self)
 {
 	pfree(self);
 }
+
+/*
+ * CreateIvmTriggersOnBaseTables -- create IVM triggers on all base tables
+ */
+void
+CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create)
+{
+	Relids	relids = NULL;
+	bool	ex_lock = false;
+	Index	first_rtindex = is_create ? 1 : PRS2_NEW_VARNO + 1;
+	RangeTblEntry *rte;
+
+	/* Immediately return if we don't have any base tables. */
+	if (list_length(qry->rtable) < first_rtindex)
+		return;
+
+	/*
+	 * If the view has more than one base tables, we need an exclusive lock
+	 * on the view so that the view would be maintained serially to avoid
+	 * the inconsistency that occurs when two base tables are modified in
+	 * concurrent transactions. However, if the view has only one table,
+	 * we can use a weaker lock.
+	 *
+	 * The type of lock should be determined here, because if we check the
+	 * view definition at maintenance time, we need to acquire a weaker lock,
+	 * and upgrading the lock level after this increases probability of
+	 * deadlock.
+	 */
+
+	rte = list_nth(qry->rtable, first_rtindex - 1);
+	if (list_length(qry->rtable) > first_rtindex ||
+		rte->rtekind != RTE_RELATION)
+		ex_lock = true;
+
+	CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *)qry, matviewOid, &relids, ex_lock);
+
+	bms_free(relids);
+}
+
+static void
+CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid,
+									 Relids *relids, bool ex_lock)
+{
+	if (node == NULL)
+		return;
+
+	/* This can recurse, so check for excessive recursion */
+	check_stack_depth();
+
+	switch (nodeTag(node))
+	{
+		case T_Query:
+			{
+				Query *query = (Query *) node;
+
+				CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *)query->jointree, matviewOid, relids, ex_lock);
+			}
+			break;
+
+		case T_RangeTblRef:
+			{
+				int			rti = ((RangeTblRef *) node)->rtindex;
+				RangeTblEntry *rte = rt_fetch(rti, qry->rtable);
+
+				if (rte->rtekind == RTE_RELATION && !bms_is_member(rte->relid, *relids))
+				{
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_BEFORE, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_BEFORE, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_BEFORE, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_AFTER, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_AFTER, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_AFTER, ex_lock);
+
+					*relids = bms_add_member(*relids, rte->relid);
+				}
+			}
+			break;
+
+		case T_FromExpr:
+			{
+				FromExpr   *f = (FromExpr *) node;
+				ListCell   *l;
+
+				foreach(l, f->fromlist)
+					CreateIvmTriggersOnBaseTablesRecurse(qry, lfirst(l), matviewOid, relids, ex_lock);
+			}
+			break;
+
+		case T_JoinExpr:
+			{
+				JoinExpr   *j = (JoinExpr *) node;
+
+				CreateIvmTriggersOnBaseTablesRecurse(qry, j->larg, matviewOid, relids, ex_lock);
+				CreateIvmTriggersOnBaseTablesRecurse(qry, j->rarg, matviewOid, relids, ex_lock);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
+	}
+}
+
+/*
+ * CreateIvmTrigger -- create IVM trigger on a base table
+ */
+static void
+CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock)
+{
+	ObjectAddress	refaddr;
+	ObjectAddress	address;
+	CreateTrigStmt *ivm_trigger;
+	List *transitionRels = NIL;
+
+	Assert(timing == TRIGGER_TYPE_BEFORE || timing == TRIGGER_TYPE_AFTER);
+
+	refaddr.classId = RelationRelationId;
+	refaddr.objectId = viewOid;
+	refaddr.objectSubId = 0;
+
+	ivm_trigger = makeNode(CreateTrigStmt);
+	ivm_trigger->relation = NULL;
+	ivm_trigger->row = false;
+
+	ivm_trigger->timing = timing;
+	ivm_trigger->events = type;
+
+	switch (type)
+	{
+		case TRIGGER_TYPE_INSERT:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_ins_before" : "IVM_trigger_ins_after");
+			break;
+		case TRIGGER_TYPE_DELETE:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_del_before" : "IVM_trigger_del_after");
+			break;
+		case TRIGGER_TYPE_UPDATE:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_upd_before" : "IVM_trigger_upd_after");
+			break;
+		default:
+			elog(ERROR, "unsupported trigger type");
+	}
+
+	if (timing == TRIGGER_TYPE_AFTER)
+	{
+		if (type == TRIGGER_TYPE_INSERT || type == TRIGGER_TYPE_UPDATE)
+		{
+			TriggerTransition *n = makeNode(TriggerTransition);
+			n->name = "__ivm_newtable";
+			n->isNew = true;
+			n->isTable = true;
+
+			transitionRels = lappend(transitionRels, n);
+		}
+		if (type == TRIGGER_TYPE_DELETE || type == TRIGGER_TYPE_UPDATE)
+		{
+			TriggerTransition *n = makeNode(TriggerTransition);
+			n->name = "__ivm_oldtable";
+			n->isNew = false;
+			n->isTable = true;
+
+			transitionRels = lappend(transitionRels, n);
+		}
+	}
+
+	ivm_trigger->funcname =
+		(timing == TRIGGER_TYPE_BEFORE ? SystemFuncName("IVM_immediate_before") : SystemFuncName("IVM_immediate_maintenance"));
+
+	ivm_trigger->columns = NIL;
+	ivm_trigger->transitionRels = transitionRels;
+	ivm_trigger->whenClause = NULL;
+	ivm_trigger->isconstraint = false;
+	ivm_trigger->deferrable = false;
+	ivm_trigger->initdeferred = false;
+	ivm_trigger->constrrel = NULL;
+	ivm_trigger->args = list_make2(
+		makeString(DatumGetPointer(DirectFunctionCall1(oidout, ObjectIdGetDatum(viewOid)))),
+		makeString(DatumGetPointer(DirectFunctionCall1(boolout, BoolGetDatum(ex_lock))))
+		);
+
+	address = CreateTrigger(ivm_trigger, NULL, relOid, InvalidOid, InvalidOid,
+						 InvalidOid, InvalidOid, InvalidOid, NULL, true, false);
+
+	recordDependencyOn(&address, &refaddr, DEPENDENCY_IMMV);
+
+	/* Make changes-so-far visible */
+	CommandCounterIncrement();
+}
+
+/*
+ * check_ivm_restriction --- look for specify nodes in the query tree
+ */
+static void
+check_ivm_restriction(Node *node)
+{
+	check_ivm_restriction_walker(node, NULL);
+}
+
+static bool
+check_ivm_restriction_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	/*
+	 * We currently don't support Sub-Query.
+	 */
+	if (IsA(node, SubPlan) || IsA(node, SubLink))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("subquery is not supported on incrementally maintainable materialized view")));
+
+	/* This can recurse, so check for excessive recursion */
+	check_stack_depth();
+
+	switch (nodeTag(node))
+	{
+		case T_Query:
+			{
+				Query *qry = (Query *)node;
+				ListCell   *lc;
+				List       *vars;
+
+				/* if contained CTE, return error */
+				if (qry->cteList != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("CTE is not supported on incrementally maintainable materialized view")));
+				if (qry->havingQual != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg(" HAVING clause is not supported on incrementally maintainable materialized view")));
+				if (qry->sortClause != NIL)	/* There is a possibility that we don't need to return an error */
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("ORDER BY clause is not supported on incrementally maintainable materialized view")));
+				if (qry->limitOffset != NULL || qry->limitCount != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("LIMIT/OFFSET clause is not supported on incrementally maintainable materialized view")));
+				if (qry->hasDistinctOn)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DISTINCT ON is not supported on incrementally maintainable materialized view")));
+				if (qry->hasWindowFuncs)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("window functions are not supported on incrementally maintainable materialized view")));
+				if (qry->groupingSets != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("GROUPING SETS, ROLLUP, or CUBE clauses is not supported on incrementally maintainable materialized view")));
+				if (qry->setOperations != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("UNION/INTERSECT/EXCEPT statements are not supported on incrementally maintainable materialized view")));
+				if (list_length(qry->targetList) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("empty target list is not supported on incrementally maintainable materialized view")));
+				if (qry->rowMarks != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("FOR UPDATE/SHARE clause is not supported on incrementally maintainable materialized view")));
+
+				/* system column restrictions */
+				vars = pull_vars_of_level((Node *) qry, 0);
+				foreach(lc, vars)
+				{
+					if (IsA(lfirst(lc), Var))
+					{
+						Var *var = (Var *) lfirst(lc);
+						/* if system column, return error */
+						if (var->varattno < 0)
+							ereport(ERROR,
+									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									 errmsg("system column is not supported on incrementally maintainable materialized view")));
+					}
+				}
+
+				/* restrictions for rtable */
+				foreach(lc, qry->rtable)
+				{
+					RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+					if (rte->subquery)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("subquery is not supported on incrementally maintainable materialized view")));
+
+					if (rte->tablesample != NULL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("TABLESAMPLE clause is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("partitioned table is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_RELATION && has_superclass(rte->relid))
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("partitions is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_RELATION && find_inheritance_children(rte->relid, NoLock) != NIL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("inheritance parent is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_FOREIGN_TABLE)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("foreign table is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_VIEW ||
+						rte->relkind == RELKIND_MATVIEW)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view")));
+
+					if (rte->rtekind == RTE_VALUES)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("VALUES is not supported on incrementally maintainable materialized view")));
+
+				}
+
+				query_tree_walker(qry, check_ivm_restriction_walker, NULL, QTW_IGNORE_RANGE_TABLE);
+
+				break;
+			}
+		case T_TargetEntry:
+			{
+				TargetEntry *tle = (TargetEntry *)node;
+				if (isIvmName(tle->resname))
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("column name %s is not supported on incrementally maintainable materialized view", tle->resname)));
+
+				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				break;
+			}
+		case T_JoinExpr:
+			{
+				JoinExpr *joinexpr = (JoinExpr *)node;
+
+				if (joinexpr->jointype > JOIN_INNER)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("OUTER JOIN is not supported on incrementally maintainable materialized view")));
+
+				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+			}
+			break;
+			expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+			break;
+	}
+	return false;
+}
+
+/*
+ * CreateIndexOnIMMV
+ *
+ * Create a unique index on incremental maintainable materialized view.
+ * If the view definition query has a GROUP BY clause, the index is created
+ * on the columns of GROUP BY expressions. Otherwise, if the view contains
+ * all primary key attritubes of its base tables in the target list, the index
+ * is created on these attritubes. In other cases, no index is created.
+ */
+static void
+CreateIndexOnIMMV(Query *query, Relation matviewRel)
+{
+	Query *qry = (Query *) copyObject(query);
+	ListCell *lc;
+	IndexStmt  *index;
+	ObjectAddress address;
+	List *constraintList = NIL;
+	char		idxname[NAMEDATALEN];
+
+	snprintf(idxname, sizeof(idxname), "%s_index", RelationGetRelationName(matviewRel));
+
+	index = makeNode(IndexStmt);
+
+	index->unique = true;
+	index->primary = false;
+	index->isconstraint = false;
+	index->deferrable = false;
+	index->initdeferred = false;
+	index->idxname = idxname;
+	index->relation =
+		makeRangeVar(get_namespace_name(RelationGetNamespace(matviewRel)),
+					 pstrdup(RelationGetRelationName(matviewRel)),
+					 -1);
+	index->accessMethod = DEFAULT_INDEX_TYPE;
+	index->options = NIL;
+	index->tableSpace = get_tablespace_name(matviewRel->rd_rel->reltablespace);
+	index->whereClause = NULL;
+	index->indexParams = NIL;
+	index->indexIncludingParams = NIL;
+	index->excludeOpNames = NIL;
+	index->idxcomment = NULL;
+	index->indexOid = InvalidOid;
+	index->oldNode = InvalidOid;
+	index->oldCreateSubid = InvalidSubTransactionId;
+	index->oldFirstRelfilenodeSubid = InvalidSubTransactionId;
+	index->transformed = true;
+	index->concurrent = false;
+	index->if_not_exists = false;
+
+	if (qry->distinctClause)
+	{
+		/* create unique constraint on all columns */
+		foreach(lc, qry->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+			IndexElem  *iparam;
+
+			iparam = makeNode(IndexElem);
+			iparam->name = pstrdup(NameStr(attr->attname));
+			iparam->expr = NULL;
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->opclassopts = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+		}
+	}
+	else
+	{
+		Bitmapset *key_attnos;
+
+		/* create index on the base tables' primary key columns */
+		key_attnos = get_primary_key_attnos_from_query(qry, &constraintList);
+		if (key_attnos)
+		{
+			foreach(lc, qry->targetList)
+			{
+				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+				Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+
+				if (bms_is_member(tle->resno - FirstLowInvalidHeapAttributeNumber, key_attnos))
+				{
+					IndexElem  *iparam;
+
+					iparam = makeNode(IndexElem);
+					iparam->name = pstrdup(NameStr(attr->attname));
+					iparam->expr = NULL;
+					iparam->indexcolname = NULL;
+					iparam->collation = NIL;
+					iparam->opclass = NIL;
+					iparam->opclassopts = NIL;
+					iparam->ordering = SORTBY_DEFAULT;
+					iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+					index->indexParams = lappend(index->indexParams, iparam);
+				}
+			}
+
+		}
+		else
+		{
+			/* create no index, just notice that an appropriate index is necessary for efficient IVM */
+			ereport(NOTICE,
+					(errmsg("could not create an index on materialized view \"%s\" automatically",
+							RelationGetRelationName(matviewRel)),
+					 errdetail("This target list does not have all the primary key columns, "
+							   "or this view does not contain DISTINCT clause."),
+					 errhint("Create an index on the materialized view for efficient incremental maintenance.")));
+			return;
+		}
+	}
+
+	address = DefineIndex(RelationGetRelid(matviewRel),
+						  index,
+						  InvalidOid,
+						  InvalidOid,
+						  InvalidOid,
+						  false, true, false, false, true);
+
+	ereport(NOTICE,
+			(errmsg("created index \"%s\" on materialized view \"%s\"",
+					idxname, RelationGetRelationName(matviewRel))));
+
+
+	/*
+	 * Make dependencies so that the index is dropped if any base tables's
+	 * primary key is dropped.
+	 */
+	foreach(lc, constraintList)
+	{
+		Oid constraintOid = lfirst_oid(lc);
+		ObjectAddress	refaddr;
+
+		refaddr.classId = ConstraintRelationId;
+		refaddr.objectId = constraintOid;
+		refaddr.objectSubId = 0;
+
+		recordDependencyOn(&address, &refaddr, DEPENDENCY_NORMAL);
+	}
+}
+
+
+/*
+ * get_primary_key_attnos_from_query
+ *
+ * Identify the columns in base tables' primary keys in the target list.
+ *
+ * Returns a Bitmapset of the column attnos of the primary key's columns of
+ * tables that used in the query.  The attnos are offset by
+ * FirstLowInvalidHeapAttributeNumber as same as get_primary_key_attnos.
+ *
+ * If any table has no primary key or any primary key's columns is not in
+ * the target list, return NULL.  We also return NULL if any pkey constraint
+ * is deferrable.
+ *
+ * constraintList is set to a list of the OIDs of the pkey constraints.
+ */
+static Bitmapset *
+get_primary_key_attnos_from_query(Query *query, List **constraintList)
+{
+	List *key_attnos_list = NIL;
+	ListCell *lc;
+	int i;
+	Bitmapset *keys = NULL;
+	Relids	rels_in_from;
+	PlannerInfo root;
+
+
+	/*
+	 * Collect primary key attributes from all tables used in query. The key attributes
+	 * sets for each table are stored in key_attnos_list in order by RTE index.
+	 */
+	i = 1;
+	foreach(lc, query->rtable)
+	{
+		RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+		Bitmapset *key_attnos;
+		bool	has_pkey = true;
+
+		/* for tables, call get_primary_key_attnos */
+		if (r->rtekind == RTE_RELATION)
+		{
+			Oid constraintOid;
+			key_attnos = get_primary_key_attnos(r->relid, false, &constraintOid);
+			*constraintList = lappend_oid(*constraintList, constraintOid);
+			has_pkey = (key_attnos != NULL);
+		}
+		/* for other RTEs, store NULL into key_attnos_list */
+		else
+			key_attnos = NULL;
+
+		/*
+		 * If any table or subquery has no primary key or its pkey constraint is deferrable,
+		 * we cannot get key attributes for this query, so return NULL.
+		 */
+		if (!has_pkey)
+			return NULL;
+
+		key_attnos_list = lappend(key_attnos_list, key_attnos);
+	}
+
+	/* Collect key attributes appearing in the target list */
+	i = 1;
+	foreach(lc, query->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) flatten_join_alias_vars(query, lfirst(lc));
+
+		if (IsA(tle->expr, Var))
+		{
+			Var *var = (Var*) tle->expr;
+			Bitmapset *attnos = list_nth(key_attnos_list, var->varno - 1);
+
+			/* check if this attribute is from a base table's primary key */
+			if (bms_is_member(var->varattno - FirstLowInvalidHeapAttributeNumber, attnos))
+			{
+				/*
+				 * Remove found key attributes from key_attnos_list, and add this
+				 * to the result list.
+				 */
+				bms_del_member(attnos, var->varattno - FirstLowInvalidHeapAttributeNumber);
+				keys = bms_add_member(keys, i - FirstLowInvalidHeapAttributeNumber);
+			}
+		}
+		i++;
+	}
+
+	/* Collect relations appearing in the FROM clause */
+	rels_in_from = pull_varnos_of_level(&root, (Node *)query->jointree, 0);
+
+	/*
+	 * Check if all key attributes of relations in FROM are appearing in the target
+	 * list.  If an attribute remains in key_attnos_list in spite of the table is used
+	 * in FROM clause, the target is missing this key attribute, so we return NULL.
+	 */
+	i = 1;
+	foreach(lc, key_attnos_list)
+	{
+		Bitmapset *bms = (Bitmapset *)lfirst(lc);
+		if (!bms_is_empty(bms) && bms_is_member(i, rels_in_from))
+			return NULL;
+		i++;
+	}
+
+	return keys;
+}
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5e..5581c21298 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -36,6 +36,7 @@
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
+#include "commands/matview.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
@@ -1054,6 +1055,45 @@ DefineIndex(Oid relationId,
 	safe_index = indexInfo->ii_Expressions == NIL &&
 		indexInfo->ii_Predicate == NIL;
 
+	/*
+	 * We disallow unique indexes on IVM columns of IMMVs.
+	 */
+	if (RelationIsIVM(rel) && stmt->unique)
+	{
+		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
+		{
+			AttrNumber	attno = indexInfo->ii_IndexAttrNumbers[i];
+			if (attno > 0)
+			{
+				char *name = NameStr(TupleDescAttr(rel->rd_att, attno - 1)->attname);
+				if (name && isIvmName(name))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique index creation on IVM columns is not supported")));
+			}
+		}
+
+		if (indexInfo->ii_Expressions)
+		{
+			Bitmapset  *indexattrs = NULL;
+			int			varno = -1;
+
+			pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
+
+			while ((varno = bms_next_member(indexattrs, varno)) >= 0)
+			{
+				int attno = varno + FirstLowInvalidHeapAttributeNumber;
+				char *name = NameStr(TupleDescAttr(rel->rd_att, attno - 1)->attname);
+				if (name && isIvmName(name))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique index creation on IVM columns is not supported")));
+			}
+
+		}
+	}
+
+
 	/*
 	 * Report index creation if appropriate (delay this till after most of the
 	 * error checks)
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index fbbf769a87..a27d23434d 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -25,26 +25,47 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_type.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "commands/cluster.h"
+#include "commands/defrem.h"
 #include "commands/matview.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
+#include "commands/createas.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "executor/tstoreReceiver.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_func.h"
 #include "parser/parse_relation.h"
+#include "parser/parse_type.h"
 #include "pgstat.h"
 #include "rewrite/rewriteHandler.h"
+#include "rewrite/rewriteManip.h"
+#include "rewrite/rowsecurity.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
 
 
 typedef struct
@@ -58,6 +79,50 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_transientrel;
 
+#define MV_INIT_QUERYHASHSIZE	16
+
+/*
+ * MV_TriggerHashEntry
+ *
+ * Hash entry for base tables on which IVM trigger is invoked
+ */
+typedef struct MV_TriggerHashEntry
+{
+	Oid	matview_id;			/* OID of the materialized view */
+	int	before_trig_count;	/* count of before triggers invoked */
+	int	after_trig_count;	/* count of after triggers invoked */
+
+	TransactionId	xid;	/* Transaction id before the first table is modified*/
+	CommandId		cid;	/* Command id before the first table is modified */
+
+	List   *tables;		/* List of MV_TriggerTable */
+	bool	has_old;	/* tuples are deleted from any table? */
+	bool	has_new;	/* tuples are inserted into any table? */
+} MV_TriggerHashEntry;
+
+/*
+ * MV_TriggerTable
+ *
+ * IVM related data for tables on which the trigger is invoked.
+ */
+typedef struct MV_TriggerTable
+{
+	Oid		table_id;			/* OID of the modified table */
+	List   *old_tuplestores;	/* tuplestores for deleted tuples */
+	List   *new_tuplestores;	/* tuplestores for inserted tuples */
+	List   *old_rtes;			/* RTEs of ENRs for old_tuplestores*/
+	List   *new_rtes;			/* RTEs of ENRs for new_tuplestores */
+
+	List   *rte_indexes;		/* List of RTE index of the modified table */
+	RangeTblEntry *original_rte;	/* the original RTE saved before rewriting query */
+} MV_TriggerTable;
+
+static HTAB *mv_trigger_info = NULL;
+
+/* ENR name for materialized view delta */
+#define NEW_DELTA_ENRNAME "new_delta"
+#define OLD_DELTA_ENRNAME "old_delta"
+
 static int	matview_maintenance_depth = 0;
 
 static void transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
@@ -65,7 +130,9 @@ static bool transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void transientrel_shutdown(DestReceiver *self);
 static void transientrel_destroy(DestReceiver *self);
 static uint64 refresh_matview_datafill(DestReceiver *dest, Query *query,
-									   const char *queryString);
+						 QueryEnvironment *queryEnv,
+						 TupleDesc *resultTupleDesc,
+						 const char *queryString);
 static char *make_temptable_name_n(char *tempname, int n);
 static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 								   int save_sec_context);
@@ -73,6 +140,45 @@ static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersist
 static bool is_usable_unique_index(Relation indexRel);
 static void OpenMatViewIncrementalMaintenance(void);
 static void CloseMatViewIncrementalMaintenance(void);
+static Query *get_matview_query(Relation matviewRel);
+
+static Query *rewrite_query_for_preupdate_state(Query *query, List *tables,
+								  TransactionId xid, CommandId cid,
+								  ParseState *pstate);
+static void register_delta_ENRs(ParseState *pstate, Query *query, List *tables);
+static char *make_delta_enr_name(const char *prefix, Oid relid, int count);
+static RangeTblEntry *get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
+				 TransactionId xid, CommandId cid,
+				 QueryEnvironment *queryEnv);
+static RangeTblEntry *union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
+		   QueryEnvironment *queryEnv);
+static Query *rewrite_query_for_distinct(Query *query, ParseState *pstate);
+
+static void calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
+			DestReceiver *dest_old, DestReceiver *dest_new,
+			TupleDesc *tupdesc_old, TupleDesc *tupdesc_new,
+			QueryEnvironment *queryEnv);
+static Query *rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte_index);
+
+static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
+			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
+			Query *query, bool use_count, char *count_colname);
+static void apply_old_delta(const char *matviewname, const char *deltaname_old,
+				List *keys);
+static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
+				List *keys, const char *count_colname);
+static void apply_new_delta(const char *matviewname, const char *deltaname_new,
+				StringInfo target_list);
+static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
+				List *keys, StringInfo target_list, const char* count_colname);
+static char *get_matching_condition_string(List *keys);
+static void generate_equal(StringInfo querybuf, Oid opttype,
+			   const char *leftop, const char *rightop);
+
+static void mv_InitHashTables(void);
+static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry);
+
+static List *get_securityQuals(Oid relId, int rt_index, Query *query);
 
 /*
  * SetMatViewPopulatedState
@@ -114,6 +220,46 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
 	CommandCounterIncrement();
 }
 
+/*
+ * SetMatViewIVMState
+ *		Mark a materialized view as IVM, or not.
+ *
+ * NOTE: caller must be holding an appropriate lock on the relation.
+ */
+void
+SetMatViewIVMState(Relation relation, bool newstate)
+{
+	Relation	pgrel;
+	HeapTuple	tuple;
+
+	Assert(relation->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Update relation's pg_class entry.  Crucial side-effect: other backends
+	 * (and this one too!) are sent SI message to make them rebuild relcache
+	 * entries.
+	 */
+	pgrel = table_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(relation)));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u",
+			 RelationGetRelid(relation));
+
+	((Form_pg_class) GETSTRUCT(tuple))->relisivm = newstate;
+
+	CatalogTupleUpdate(pgrel, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	table_close(pgrel, RowExclusiveLock);
+
+	/*
+	 * Advance command counter to make the updated pg_class row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+}
+
 /*
  * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command
  *
@@ -140,8 +286,6 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 {
 	Oid			matviewOid;
 	Relation	matviewRel;
-	RewriteRule *rule;
-	List	   *actions;
 	Query	   *dataQuery;
 	Oid			tableSpace;
 	Oid			relowner;
@@ -155,6 +299,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 	int			save_sec_context;
 	int			save_nestlevel;
 	ObjectAddress address;
+	bool oldPopulated;
 
 	/* Determine strength of lock needed. */
 	concurrent = stmt->concurrent;
@@ -167,6 +312,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										  lockmode, 0,
 										  RangeVarCallbackOwnsTable, NULL);
 	matviewRel = table_open(matviewOid, NoLock);
+	oldPopulated = RelationIsPopulated(matviewRel);
 
 	/* Make sure it is a materialized view. */
 	if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW)
@@ -188,32 +334,12 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 				 errmsg("%s and %s options cannot be used together",
 						"CONCURRENTLY", "WITH NO DATA")));
 
-	/*
-	 * Check that everything is correct for a refresh. Problems at this point
-	 * are internal errors, so elog is sufficient.
-	 */
-	if (matviewRel->rd_rel->relhasrules == false ||
-		matviewRel->rd_rules->numLocks < 1)
-		elog(ERROR,
-			 "materialized view \"%s\" is missing rewrite information",
-			 RelationGetRelationName(matviewRel));
-
-	if (matviewRel->rd_rules->numLocks > 1)
-		elog(ERROR,
-			 "materialized view \"%s\" has too many rules",
-			 RelationGetRelationName(matviewRel));
 
-	rule = matviewRel->rd_rules->rules[0];
-	if (rule->event != CMD_SELECT || !(rule->isInstead))
-		elog(ERROR,
-			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
-			 RelationGetRelationName(matviewRel));
+	dataQuery = get_matview_query(matviewRel);
 
-	actions = rule->actions;
-	if (list_length(actions) != 1)
-		elog(ERROR,
-			 "the rule for materialized view \"%s\" is not a single action",
-			 RelationGetRelationName(matviewRel));
+	/* For IMMV, we need to rewrite matview query */
+	if (!stmt->skipData && RelationIsIVM(matviewRel))
+		dataQuery = rewriteQueryForIMMV(dataQuery,NIL);
 
 	/*
 	 * Check that there is a unique index with no WHERE clause on one or more
@@ -248,12 +374,6 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 					 errhint("Create a unique index with no WHERE clause on one or more columns of the materialized view.")));
 	}
 
-	/*
-	 * The stored query was rewritten at the time of the MV definition, but
-	 * has not been scribbled on by the planner.
-	 */
-	dataQuery = linitial_node(Query, actions);
-
 	/*
 	 * Check for active uses of the relation in the current transaction, such
 	 * as open scans.
@@ -294,6 +414,52 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 		relpersistence = matviewRel->rd_rel->relpersistence;
 	}
 
+	/* delete immv triggers */
+	if (RelationIsIVM(matviewRel) && stmt->skipData )
+	{
+		/* use deleted trigger */
+		Relation	depRel;
+		ScanKeyData key;
+		SysScanDesc scan;
+		HeapTuple	tup;
+		ObjectAddresses *immv_triggers;
+
+		immv_triggers = new_object_addresses();
+
+		/*
+		 * We save some cycles by opening pg_depend just once and passing the
+		 * Relation pointer down to all the recursive deletion steps.
+		 */
+		depRel = table_open(DependRelationId, RowExclusiveLock);
+
+		ScanKeyInit(&key,
+					Anum_pg_depend_refobjid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(matviewOid));
+
+		scan = systable_beginscan(depRel, DependReferenceIndexId, true,
+								  NULL, 1, &key);
+		while ((tup = systable_getnext(scan)) != NULL)
+		{
+			ObjectAddress obj;
+			Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
+
+			if (foundDep->deptype == DEPENDENCY_IMMV)
+			{
+				obj.classId = foundDep->classid;
+				obj.objectId = foundDep->objid;
+				obj.objectSubId = foundDep->refobjsubid;
+				add_exact_object_address(&obj, immv_triggers);
+			}
+		}
+		systable_endscan(scan);
+
+		performMultipleDeletions(immv_triggers, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
+
+		table_close(depRel, RowExclusiveLock);
+		free_object_addresses(immv_triggers);
+	}
+
 	/*
 	 * Create the transient table that will receive the regenerated data. Lock
 	 * it against access by any other process until commit (by which time it
@@ -313,7 +479,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 
 	/* Generate the data, if wanted. */
 	if (!stmt->skipData)
-		processed = refresh_matview_datafill(dest, dataQuery, queryString);
+		processed = refresh_matview_datafill(dest, dataQuery, NULL, NULL, queryString);
 
 	/* Make the matview match the newly generated data. */
 	if (concurrent)
@@ -348,6 +514,9 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 			pgstat_count_heap_insert(matviewRel, processed);
 	}
 
+	if (!stmt->skipData && RelationIsIVM(matviewRel) && !oldPopulated)
+		CreateIvmTriggersOnBaseTables(dataQuery, matviewOid, false);
+
 	table_close(matviewRel, NoLock);
 
 	/* Roll back any GUC changes */
@@ -382,6 +551,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  */
 static uint64
 refresh_matview_datafill(DestReceiver *dest, Query *query,
+						 QueryEnvironment *queryEnv,
+						 TupleDesc *resultTupleDesc,
 						 const char *queryString)
 {
 	List	   *rewritten;
@@ -418,7 +589,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 	/* Create a QueryDesc, redirecting output to our tuple receiver */
 	queryDesc = CreateQueryDesc(plan, queryString,
 								GetActiveSnapshot(), InvalidSnapshot,
-								dest, NULL, NULL, 0);
+								dest, NULL, queryEnv ? queryEnv: NULL, 0);
 
 	/* call ExecutorStart to prepare the plan for execution */
 	ExecutorStart(queryDesc, 0);
@@ -428,6 +599,9 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 
 	processed = queryDesc->estate->es_processed;
 
+	if (resultTupleDesc)
+		*resultTupleDesc = CreateTupleDescCopy(queryDesc->tupDesc);
+
 	/* and clean up */
 	ExecutorFinish(queryDesc);
 	ExecutorEnd(queryDesc);
@@ -942,3 +1116,1308 @@ CloseMatViewIncrementalMaintenance(void)
 	matview_maintenance_depth--;
 	Assert(matview_maintenance_depth >= 0);
 }
+
+/*
+ * get_matview_query - get the Query from a matview's _RETURN rule.
+ */
+static Query *
+get_matview_query(Relation matviewRel)
+{
+	RewriteRule *rule;
+	List * actions;
+
+	/*
+	 * Check that everything is correct for a refresh. Problems at this point
+	 * are internal errors, so elog is sufficient.
+	 */
+	if (matviewRel->rd_rel->relhasrules == false ||
+		matviewRel->rd_rules->numLocks < 1)
+		elog(ERROR,
+			 "materialized view \"%s\" is missing rewrite information",
+			 RelationGetRelationName(matviewRel));
+
+	if (matviewRel->rd_rules->numLocks > 1)
+		elog(ERROR,
+			 "materialized view \"%s\" has too many rules",
+			 RelationGetRelationName(matviewRel));
+
+	rule = matviewRel->rd_rules->rules[0];
+	if (rule->event != CMD_SELECT || !(rule->isInstead))
+		elog(ERROR,
+			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
+			 RelationGetRelationName(matviewRel));
+
+	actions = rule->actions;
+	if (list_length(actions) != 1)
+		elog(ERROR,
+			 "the rule for materialized view \"%s\" is not a single action",
+			 RelationGetRelationName(matviewRel));
+
+	/*
+	 * The stored query was rewritten at the time of the MV definition, but
+	 * has not been scribbled on by the planner.
+	 */
+	return linitial_node(Query, actions);
+}
+
+
+/* ----------------------------------------------------
+ *		Incremental View Maintenance routines
+ * ---------------------------------------------------
+ */
+
+/*
+ * IVM_immediate_before
+ *
+ * IVM trigger function invoked before base table is modified. If this is
+ * invoked firstly in the same statement, we save the transaction id and the
+ * command id at that time.
+ */
+Datum
+IVM_immediate_before(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	char	   *matviewOid_text = trigdata->tg_trigger->tgargs[0];
+	char	   *ex_lock_text = trigdata->tg_trigger->tgargs[1];
+	Oid			matviewOid;
+	MV_TriggerHashEntry *entry;
+	bool	found;
+	bool	ex_lock;
+
+	matviewOid = DatumGetObjectId(DirectFunctionCall1(oidin, CStringGetDatum(matviewOid_text)));
+	ex_lock = DatumGetBool(DirectFunctionCall1(boolin, CStringGetDatum(ex_lock_text)));
+
+	/* If the view has more than one tables, we have to use an exclusive lock. */
+	if (ex_lock)
+	{
+		/*
+		 * Wait for concurrent transactions which update this materialized view at
+		 * READ COMMITED. This is needed to see changes committed in other
+		 * transactions. No wait and raise an error at REPEATABLE READ or
+		 * SERIALIZABLE to prevent update anomalies of matviews.
+		 * XXX: dead-lock is possible here.
+		 */
+		if (!IsolationUsesXactSnapshot())
+			LockRelationOid(matviewOid, ExclusiveLock);
+		else if (!ConditionalLockRelationOid(matviewOid, ExclusiveLock))
+		{
+			/* try to throw error by name; relation could be deleted... */
+			char	   *relname = get_rel_name(matviewOid);
+
+			if (!relname)
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						errmsg("could not obtain lock on materialized view during incremental maintenance")));
+
+			ereport(ERROR,
+					(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+					errmsg("could not obtain lock on materialized view \"%s\" during incremental maintenance",
+							relname)));
+		}
+	}
+	else
+		LockRelationOid(matviewOid, RowExclusiveLock);
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_trigger_info)
+		mv_InitHashTables();
+
+	entry = (MV_TriggerHashEntry *) hash_search(mv_trigger_info,
+											  (void *) &matviewOid,
+											  HASH_ENTER, &found);
+
+	/* On the first BEFORE to update the view, initialize trigger data */
+	if (!found)
+	{
+		Snapshot snapshot = GetActiveSnapshot();
+
+		entry->matview_id = matviewOid;
+		entry->before_trig_count = 0;
+		entry->after_trig_count = 0;
+		entry->xid = GetCurrentTransactionId();
+		entry->cid = snapshot->curcid;
+		entry->tables = NIL;
+		entry->has_old = false;
+		entry->has_new = false;
+	}
+
+	entry->before_trig_count++;
+
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * IVM_immediate_before
+ *
+ * IVM trigger function invoked after base table is modified.
+ * For each table, tuplestores of transition tables are collected.
+ * and after the last modification
+ */
+Datum
+IVM_immediate_maintenance(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	Relation	rel;
+	Oid			relid;
+	Oid			matviewOid;
+	Query	   *query;
+	Query	   *rewritten = NULL;
+	char	   *matviewOid_text = trigdata->tg_trigger->tgargs[0];
+	Relation	matviewRel;
+	int old_depth = matview_maintenance_depth;
+
+	Oid			relowner;
+	Tuplestorestate *old_tuplestore = NULL;
+	Tuplestorestate *new_tuplestore = NULL;
+	DestReceiver *dest_new = NULL, *dest_old = NULL;
+	Oid			save_userid;
+	int			save_sec_context;
+	int			save_nestlevel;
+
+	MV_TriggerHashEntry *entry;
+	MV_TriggerTable		*table;
+	bool	found;
+
+	ParseState		 *pstate;
+	QueryEnvironment *queryEnv = create_queryEnv();
+	MemoryContext	oldcxt;
+	ListCell   *lc;
+	int			i;
+
+
+	/* Create a ParseState for rewriting the view definition query */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	rel = trigdata->tg_relation;
+	relid = rel->rd_id;
+
+	matviewOid = DatumGetObjectId(DirectFunctionCall1(oidin, CStringGetDatum(matviewOid_text)));
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_trigger_info)
+		mv_InitHashTables();
+
+	/* get the entry for this materialized view */
+	entry = (MV_TriggerHashEntry *) hash_search(mv_trigger_info,
+											  (void *) &matviewOid,
+											  HASH_FIND, &found);
+	Assert (found && entry != NULL);
+	entry->after_trig_count++;
+
+	/* search the entry for the modified table and create new entry if not found */
+	found = false;
+	foreach(lc, entry->tables)
+	{
+		table = (MV_TriggerTable *) lfirst(lc);
+		if (table->table_id == relid)
+		{
+			found = true;
+			break;
+		}
+	}
+	if (!found)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		table = (MV_TriggerTable *) palloc0(sizeof(MV_TriggerTable));
+		table->table_id = relid;
+		table->old_tuplestores = NIL;
+		table->new_tuplestores = NIL;
+		table->old_rtes = NIL;
+		table->new_rtes = NIL;
+		table->rte_indexes = NIL;
+		entry->tables = lappend(entry->tables, table);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* Save the transition tables and make a request to not free immediately */
+	if (trigdata->tg_oldtable)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+		table->old_tuplestores = lappend(table->old_tuplestores, trigdata->tg_oldtable);
+		entry->has_old = true;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (trigdata->tg_newtable)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+		table->new_tuplestores = lappend(table->new_tuplestores, trigdata->tg_newtable);
+		entry->has_new = true;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (entry->has_new || entry->has_old)
+	{
+		CmdType cmd;
+
+		if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+			cmd = CMD_INSERT;
+		else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+			cmd = CMD_DELETE;
+		else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+			cmd = CMD_UPDATE;
+		else
+			elog(ERROR,"unsupported trigger type");
+
+		/* Prolong lifespan of transition tables to the end of the last AFTER trigger */
+		SetTransitionTablePreserved(relid, cmd);
+	}
+
+
+	/* If this is not the last AFTER trigger call, immediately exit. */
+	Assert (entry->before_trig_count >= entry->after_trig_count);
+	if (entry->before_trig_count != entry->after_trig_count)
+		return PointerGetDatum(NULL);
+
+	/*
+	 * If this is the last AFTER trigger call, continue and update the view.
+	 */
+
+	/*
+	 * Advance command counter to make the updated base table row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+
+	matviewRel = table_open(matviewOid, NoLock);
+
+	/* get view query*/
+	query = get_matview_query(matviewRel);
+
+	/* Make sure it is a materialized view. */
+	Assert(matviewRel->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Get and push the latast snapshot to see any changes which is committed
+	 * during waiting in other transactions at READ COMMITTED level.
+	 */
+	PushActiveSnapshot(GetTransactionSnapshot());
+
+	/*
+	 * Check for active uses of the relation in the current transaction, such
+	 * as open scans.
+	 *
+	 * NB: We count on this to protect us against problems with refreshing the
+	 * data using TABLE_INSERT_FROZEN.
+	 */
+	CheckTableNotInUse(matviewRel, "refresh a materialized view incrementally");
+
+	/*
+	 * Switch to the owner's userid, so that any functions are run as that
+	 * user.  Also arrange to make GUC variable changes local to this command.
+	 * We will switch modes when we are about to execute user code.
+	 */
+	relowner = matviewRel->rd_rel->relowner;
+	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+	save_nestlevel = NewGUCNestLevel();
+
+	/*
+	 * rewrite query for calculating deltas
+	 */
+
+	rewritten = copyObject(query);
+
+	/* Replace resnames in a target list with materialized view's attnames */
+	i = 0;
+	foreach (lc, rewritten->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char *resname = NameStr(attr->attname);
+
+		tle->resname = pstrdup(resname);
+		i++;
+	}
+
+	/* Set all tables in the query to pre-update state */
+	rewritten = rewrite_query_for_preupdate_state(rewritten, entry->tables,
+												  entry->xid, entry->cid,
+												  pstate);
+	/* Rewrite for DISTINCT clause */
+	rewritten = rewrite_query_for_distinct(rewritten, pstate);
+
+	/* Create tuplestores to store view deltas */
+	if (entry->has_old)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		old_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+		dest_old = CreateDestReceiver(DestTuplestore);
+		SetTuplestoreDestReceiverParams(dest_old,
+									old_tuplestore,
+									TopTransactionContext,
+									false,
+									NULL,
+									NULL);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (entry->has_new)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		new_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+		dest_new = CreateDestReceiver(DestTuplestore);
+		SetTuplestoreDestReceiverParams(dest_new,
+									new_tuplestore,
+									TopTransactionContext,
+									false,
+									NULL,
+									NULL);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* for all modified tables */
+	foreach(lc, entry->tables)
+	{
+		ListCell *lc2;
+
+		table = (MV_TriggerTable *) lfirst(lc);
+
+		/* loop for self-join */
+		foreach(lc2, table->rte_indexes)
+		{
+			int	rte_index = lfirst_int(lc2);
+			TupleDesc		tupdesc_old;
+			TupleDesc		tupdesc_new;
+			bool	use_count = false;
+			char   *count_colname = NULL;
+
+			count_colname = pstrdup("__ivm_count__");
+
+			if (query->distinctClause)
+				use_count = true;
+
+			/* calculate delta tables */
+			calc_delta(table, rte_index, rewritten, dest_old, dest_new,
+					   &tupdesc_old, &tupdesc_new, queryEnv);
+
+			/* Set the table in the query to post-update state */
+			rewritten = rewrite_query_for_postupdate_state(rewritten, table, rte_index);
+
+			PG_TRY();
+			{
+				/* apply the delta tables to the materialized view */
+				apply_delta(matviewOid, old_tuplestore, new_tuplestore,
+							tupdesc_old, tupdesc_new, query, use_count,
+							count_colname);
+			}
+			PG_CATCH();
+			{
+				matview_maintenance_depth = old_depth;
+				PG_RE_THROW();
+			}
+			PG_END_TRY();
+
+			/* clear view delta tuplestores */
+			if (old_tuplestore)
+				tuplestore_clear(old_tuplestore);
+			if (new_tuplestore)
+				tuplestore_clear(new_tuplestore);
+		}
+	}
+
+	/* Clean up hash entry and delete tuplestores */
+	clean_up_IVM_hash_entry(entry);
+	if (old_tuplestore)
+	{
+		dest_old->rDestroy(dest_old);
+		tuplestore_end(old_tuplestore);
+	}
+	if (new_tuplestore)
+	{
+		dest_new->rDestroy(dest_new);
+		tuplestore_end(new_tuplestore);
+	}
+
+	/* Pop the original snapshot. */
+	PopActiveSnapshot();
+
+	table_close(matviewRel, NoLock);
+
+	/* Roll back any GUC changes */
+	AtEOXact_GUC(false, save_nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(save_userid, save_sec_context);
+
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * rewrite_query_for_preupdate_state
+ *
+ * Rewrite the query so that base tables' RTEs will represent "pre-update"
+ * state of tables. This is necessary to calculate view delta after multiple
+ * tables are modified. xid and cid are the transaction id and command id
+ * before the first table was modified.
+ */
+static Query*
+rewrite_query_for_preupdate_state(Query *query, List *tables,
+								  TransactionId xid, CommandId cid,
+								  ParseState *pstate)
+{
+	ListCell *lc;
+	int num_rte = list_length(query->rtable);
+	int i;
+
+
+	/* register delta ENRs */
+	register_delta_ENRs(pstate, query, tables);
+
+	/* XXX: Is necessary? Is this right timing? */
+	AcquireRewriteLocks(query, true, false);
+
+	i = 1;
+	foreach(lc, query->rtable)
+	{
+		RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+
+		ListCell *lc2;
+		foreach(lc2, tables)
+		{
+			MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc2);
+			/*
+			 * if the modified table is found then replace the original RTE with
+			 * "pre-state" RTE and append its index to the list.
+			 */
+			if (r->relid == table->table_id)
+			{
+				lfirst(lc) = get_prestate_rte(r, table, xid, cid, pstate->p_queryEnv);
+				table->rte_indexes = lappend_int(table->rte_indexes, i);
+				break;
+			}
+		}
+
+		/* finish the loop if we processed all RTE included in the original query */
+		if (i++ >= num_rte)
+			break;
+	}
+
+	return query;
+}
+
+/*
+ * register_delta_ENRs
+ *
+ * For all modified tables, make ENRs for their transition tables
+ * and register them to the queryEnv. ENR's RTEs are also appended
+ * into the list in query tree.
+ */
+static void
+register_delta_ENRs(ParseState *pstate, Query *query, List *tables)
+{
+	QueryEnvironment *queryEnv = pstate->p_queryEnv;
+	ListCell *lc;
+	RangeTblEntry	*rte;
+
+	foreach(lc, tables)
+	{
+		MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc);
+		ListCell *lc2;
+		int count;
+
+		count = 0;
+		foreach(lc2, table->old_tuplestores)
+		{
+			Tuplestorestate *oldtable = (Tuplestorestate *) lfirst(lc2);
+			EphemeralNamedRelation enr =
+				palloc(sizeof(EphemeralNamedRelationData));
+			ParseNamespaceItem *nsitem;
+
+			enr->md.name = make_delta_enr_name("old", table->table_id, count);
+			enr->md.reliddesc = table->table_id;
+			enr->md.tupdesc = NULL;
+			enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+			enr->md.enrtuples = tuplestore_tuple_count(oldtable);
+			enr->reldata = oldtable;
+			register_ENR(queryEnv, enr);
+
+			nsitem = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+			rte = nsitem->p_rte;
+			/* if base table has RLS, set security condition to enr */
+			rte->securityQuals = get_securityQuals(table->table_id, list_length(query->rtable) + 1, query);
+
+			query->rtable = lappend(query->rtable, rte);
+			table->old_rtes = lappend(table->old_rtes, rte);
+
+			count++;
+		}
+
+		count = 0;
+		foreach(lc2, table->new_tuplestores)
+		{
+			Tuplestorestate *newtable = (Tuplestorestate *) lfirst(lc2);
+			EphemeralNamedRelation enr =
+				palloc(sizeof(EphemeralNamedRelationData));
+			ParseNamespaceItem *nsitem;
+
+			enr->md.name = make_delta_enr_name("new", table->table_id, count);
+			enr->md.reliddesc = table->table_id;
+			enr->md.tupdesc = NULL;
+			enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+			enr->md.enrtuples = tuplestore_tuple_count(newtable);
+			enr->reldata = newtable;
+			register_ENR(queryEnv, enr);
+
+			nsitem = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+			rte = nsitem->p_rte;
+			/* if base table has RLS, set security condition to enr*/
+			rte->securityQuals = get_securityQuals(table->table_id, list_length(query->rtable) + 1, query);
+
+			query->rtable = lappend(query->rtable, rte);
+			table->new_rtes = lappend(table->new_rtes, rte);
+
+			count++;
+		}
+	}
+}
+
+/*
+ * get_prestate_rte
+ *
+ * Rewrite RTE of the modified table to a subquery which represents
+ * "pre-state" table. The original RTE is saved in table->rte_original.
+ */
+static RangeTblEntry*
+get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
+				 TransactionId xid, CommandId cid,
+				 QueryEnvironment *queryEnv)
+{
+	StringInfoData str;
+	RawStmt *raw;
+	Query *sub;
+	Relation rel;
+	ParseState *pstate;
+	char *relname;
+	int i;
+
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	/*
+	 * We can use NoLock here since AcquireRewriteLocks should
+	 * have locked the rel already.
+	 */
+	rel = table_open(table->table_id, NoLock);
+	relname = quote_qualified_identifier(
+					get_namespace_name(RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+	table_close(rel, NoLock);
+
+	initStringInfo(&str);
+	appendStringInfo(&str,
+		"SELECT t.* FROM %s t"
+		" WHERE (age(t.xmin) - age(%u::text::xid) > 0) OR"
+		" (t.xmin = %u AND t.cmin::text::int < %u)",
+			relname, xid, xid, cid);
+
+	for (i = 0; i < list_length(table->old_tuplestores); i++)
+	{
+		appendStringInfo(&str, " UNION ALL ");
+		appendStringInfo(&str," SELECT * FROM %s",
+			make_delta_enr_name("old", table->table_id, i));
+	}
+
+	raw = (RawStmt*)linitial(raw_parser(str.data, RAW_PARSE_DEFAULT));
+	sub = transformStmt(pstate, raw->stmt);
+
+	/* If this query has setOperations, RTEs in rtables has a subquery which contains ENR */
+	if (sub->setOperations != NULL)
+	{
+		ListCell *lc;
+
+		/* add securityQuals for tuplestores */
+		foreach (lc, sub->rtable)
+		{
+			RangeTblEntry *rte;
+			RangeTblEntry *sub_rte;
+
+			rte = (RangeTblEntry *)lfirst(lc);
+			Assert(rte->subquery != NULL);
+
+			sub_rte = (RangeTblEntry *)linitial(rte->subquery->rtable);
+			if (sub_rte->rtekind == RTE_NAMEDTUPLESTORE)
+				/* rt_index is always 1, bacause subquery has enr_rte only */
+				sub_rte->securityQuals = get_securityQuals(sub_rte->relid, 1, sub);
+		}
+	}
+
+	/* save the original RTE */
+	table->original_rte = copyObject(rte);
+
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = sub;
+	rte->security_barrier = false;
+	/* Clear fields that should not be set in a subquery RTE */
+	rte->relid = InvalidOid;
+	rte->relkind = 0;
+	rte->rellockmode = 0;
+	rte->tablesample = NULL;
+	rte->inh = false;			/* must not be set for a subquery */
+
+	rte->requiredPerms = 0;		/* no permission check on subquery itself */
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->insertedCols = NULL;
+	rte->updatedCols = NULL;
+	rte->extraUpdatedCols = NULL;
+
+	return rte;
+}
+
+/*
+ * make_delta_enr_name
+ *
+ * Make a name for ENR of a transition table from the base table's oid.
+ * prefix will be "new" or "old" depending on its transition table kind..
+ */
+static char*
+make_delta_enr_name(const char *prefix, Oid relid, int count)
+{
+	char buf[NAMEDATALEN];
+	char *name;
+
+	snprintf(buf, NAMEDATALEN, "__ivm_%s_%u_%u", prefix, relid, count);
+	name = pstrdup(buf);
+
+	return name;
+}
+
+/*
+ * union_ENRs
+ *
+ * Make a single table delta by unionning all transition tables of the modified table
+ * whose RTE is specified by
+ */
+static RangeTblEntry*
+union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
+		   QueryEnvironment *queryEnv)
+{
+	StringInfoData str;
+	ParseState	*pstate;
+	RawStmt *raw;
+	Query *sub;
+	int	i;
+	RangeTblEntry *enr_rte;
+
+	/* Create a ParseState for rewriting the view definition query */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	initStringInfo(&str);
+
+	for (i = 0; i < list_length(enr_rtes); i++)
+	{
+		if (i > 0)
+			appendStringInfo(&str, " UNION ALL ");
+
+		appendStringInfo(&str,
+			" SELECT * FROM %s",
+			make_delta_enr_name(prefix, relid, i));
+	}
+
+	raw = (RawStmt*)linitial(raw_parser(str.data, RAW_PARSE_DEFAULT));
+	sub = transformStmt(pstate, raw->stmt);
+
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = sub;
+	rte->security_barrier = false;
+	/* Clear fields that should not be set in a subquery RTE */
+	rte->relid = InvalidOid;
+	rte->relkind = 0;
+	rte->rellockmode = 0;
+	rte->tablesample = NULL;
+	rte->inh = false;			/* must not be set for a subquery */
+
+	rte->requiredPerms = 0;		/* no permission check on subquery itself */
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->insertedCols = NULL;
+	rte->updatedCols = NULL;
+	rte->extraUpdatedCols = NULL;
+	/* if base table has RLS, set security condition to enr*/
+	enr_rte = (RangeTblEntry *)linitial(sub->rtable);
+	/* rt_index is always 1, bacause subquery has enr_rte only */
+	enr_rte->securityQuals = get_securityQuals(relid, 1, sub);
+
+	return rte;
+}
+
+/*
+ * rewrite_query_for_distinct
+ *
+ * Rewrite query for counting DISTINCT clause.
+ */
+static Query *
+rewrite_query_for_distinct(Query *query, ParseState *pstate)
+{
+	TargetEntry *tle_count;
+	FuncCall *fn;
+	Node *node;
+
+	/* Add count(*) for counting distinct tuples in views */
+	fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+	fn->agg_star = true;
+	if (!query->groupClause && !query->hasAggs)
+		query->groupClause = transformDistinctClause(NULL, &query->targetList, query->sortClause, false);
+
+	node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+	tle_count = makeTargetEntry((Expr *) node,
+								list_length(query->targetList) + 1,
+								pstrdup("__ivm_count__"),
+								false);
+	query->targetList = lappend(query->targetList, tle_count);
+	query->hasAggs = true;
+
+	return query;
+}
+
+/*
+ * calc_delta
+ *
+ * Calculate view deltas generated under the modification of a table specified
+ * by the RTE index.
+ */
+static void
+calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
+			DestReceiver *dest_old, DestReceiver *dest_new,
+			TupleDesc *tupdesc_old, TupleDesc *tupdesc_new,
+			QueryEnvironment *queryEnv)
+{
+	ListCell *lc = list_nth_cell(query->rtable, rte_index - 1);
+	RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+	/* Generate old delta */
+	if (list_length(table->old_rtes) > 0)
+	{
+		/* Replace the modified table with the old delta table and calculate the old view delta. */
+		lfirst(lc) = union_ENRs(rte, table->table_id, table->old_rtes, "old", queryEnv);
+		refresh_matview_datafill(dest_old, query, queryEnv, tupdesc_old, "");
+	}
+
+	/* Generate new delta */
+	if (list_length(table->new_rtes) > 0)
+	{
+		/* Replace the modified table with the new delta table and calculate the new view delta*/
+		lfirst(lc) = union_ENRs(rte, table->table_id, table->new_rtes, "new", queryEnv);
+		refresh_matview_datafill(dest_new, query, queryEnv, tupdesc_new, "");
+	}
+}
+
+/*
+ * rewrite_query_for_postupdate_state
+ *
+ * Rewrite the query so that the specified base table's RTEs will represent
+ * "post-update" state of tables. This is called after the view delta
+ * calculation due to changes on this table finishes.
+ */
+static Query*
+rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte_index)
+{
+	ListCell *lc = list_nth_cell(query->rtable, rte_index - 1);
+
+	/* Retore the original RTE */
+	lfirst(lc) = table->original_rte;
+
+	return query;
+}
+
+/*
+ * apply_delta
+ *
+ * Apply deltas to the materialized view. In outer join cases, this requires
+ * the view maintenance graph.
+ */
+static void
+apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
+			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
+			Query *query, bool use_count, char *count_colname)
+{
+	StringInfoData querybuf;
+	StringInfoData target_list_buf;
+	Relation	matviewRel;
+	char	   *matviewname;
+	ListCell	*lc;
+	int			i;
+	List	   *keys = NIL;
+
+
+	/*
+	 * get names of the materialized view and delta tables
+	 */
+
+	matviewRel = table_open(matviewOid, NoLock);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	/*
+	 * Build parts of the maintenance queries
+	 */
+
+	initStringInfo(&querybuf);
+	initStringInfo(&target_list_buf);
+
+	/* build string of target list */
+	for (i = 0; i < matviewRel->rd_att->natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char   *resname = NameStr(attr->attname);
+
+		if (i != 0)
+			appendStringInfo(&target_list_buf, ", ");
+		appendStringInfo(&target_list_buf, "%s", quote_qualified_identifier(NULL, resname));
+	}
+
+	i = 0;
+	foreach (lc, query->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char *resname = NameStr(attr->attname);
+
+		i++;
+
+		if (tle->resjunk)
+			continue;
+
+		keys = lappend(keys, resname);
+	}
+
+	/* Start maintaining the materialized view. */
+	OpenMatViewIncrementalMaintenance();
+
+	/* Open SPI context. */
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	/* For tuple deletion */
+	if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
+	{
+		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		int				rc;
+
+		/* convert tuplestores to ENR, and register for SPI */
+		enr->md.name = pstrdup(OLD_DELTA_ENRNAME);
+		enr->md.reliddesc = InvalidOid;
+		enr->md.tupdesc = tupdesc_old;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(old_tuplestores);
+		enr->reldata = old_tuplestores;
+
+		rc = SPI_register_relation(enr);
+		if (rc != SPI_OK_REL_REGISTER)
+			elog(ERROR, "SPI_register failed");
+
+		if (use_count)
+			/* apply old delta and get rows to be recalculated */
+			apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME,
+									   keys, count_colname);
+		else
+			apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys);
+
+	}
+	/* For tuple insertion */
+	if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
+	{
+		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		int rc;
+
+		/* convert tuplestores to ENR, and register for SPI */
+		enr->md.name = pstrdup(NEW_DELTA_ENRNAME);
+		enr->md.reliddesc = InvalidOid;
+		enr->md.tupdesc = tupdesc_new;;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(new_tuplestores);
+		enr->reldata = new_tuplestores;
+
+		rc = SPI_register_relation(enr);
+		if (rc != SPI_OK_REL_REGISTER)
+			elog(ERROR, "SPI_register failed");
+
+		/* apply new delta */
+		if (use_count)
+			apply_new_delta_with_count(matviewname, NEW_DELTA_ENRNAME,
+								keys, &target_list_buf, count_colname);
+		else
+			apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf);
+	}
+
+	/* We're done maintaining the materialized view. */
+	CloseMatViewIncrementalMaintenance();
+
+	table_close(matviewRel, NoLock);
+
+	/* Close SPI context. */
+	if (SPI_finish() != SPI_OK_FINISH)
+		elog(ERROR, "SPI_finish failed");
+}
+
+/*
+ * apply_old_delta_with_count
+ *
+ * Execute a query for applying a delta table given by deltname_old
+ * which contains tuples to be deleted from to a materialized view given by
+ * matviewname.  This is used when counting is required, that is, the view
+ * has aggregate or distinct.
+ */
+static void
+apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
+				List *keys, const char *count_colname)
+{
+	StringInfoData	querybuf;
+	char   *match_cond;
+
+	/* build WHERE condition for searching tuples to be deleted */
+	match_cond = get_matching_condition_string(keys);
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"WITH t AS ("			/* collecting tid of target tuples in the view */
+						"SELECT diff.%s, "			/* count column */
+								"(diff.%s OPERATOR(pg_catalog.=) mv.%s) AS for_dlt, "
+								"mv.ctid "
+						"FROM %s AS mv, %s AS diff "
+						"WHERE %s"					/* tuple matching condition */
+					"), updt AS ("			/* update a tuple if this is not to be deleted */
+						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.-) t.%s "
+						"FROM t WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND NOT for_dlt "
+					"), dlt AS ("			/* delete a tuple if this is to be deleted */
+						"DELETE FROM %s AS mv USING t "
+						"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt"
+					")",
+					count_colname,
+					count_colname, count_colname,
+					matviewname, deltaname_old,
+					match_cond,
+					matviewname, count_colname, count_colname, count_colname,
+					matviewname);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_old_delta
+ *
+ * Execute a query for applying a delta table given by deltname_old
+ * which contains tuples to be deleted from to a materialized view given by
+ * matviewname.  This is used when counting is not required.
+ */
+static void
+apply_old_delta(const char *matviewname, const char *deltaname_old,
+				List *keys)
+{
+	StringInfoData	querybuf;
+	StringInfoData	keysbuf;
+	char   *match_cond;
+	ListCell *lc;
+
+	/* build WHERE condition for searching tuples to be deleted */
+	match_cond = get_matching_condition_string(keys);
+
+	/* build string of keys list */
+	initStringInfo(&keysbuf);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char   *resname = NameStr(attr->attname);
+		appendStringInfo(&keysbuf, "%s", quote_qualified_identifier("mv", resname));
+		if (lnext(keys, lc))
+			appendStringInfo(&keysbuf, ", ");
+	}
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+	"DELETE FROM %s WHERE ctid IN ("
+		"SELECT tid FROM (SELECT row_number() over (partition by %s) AS \"__ivm_row_number__\","
+								  "mv.ctid AS tid,"
+								  "diff.\"__ivm_count__\""
+						 "FROM %s AS mv, %s AS diff "
+						 "WHERE %s) v "
+					"WHERE v.\"__ivm_row_number__\" OPERATOR(pg_catalog.<=) v.\"__ivm_count__\")",
+					matviewname,
+					keysbuf.data,
+					matviewname, deltaname_old,
+					match_cond);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_new_delta_with_count
+ *
+ * Execute a query for applying a delta table given by deltname_new
+ * which contains tuples to be inserted into a materialized view given by
+ * matviewname.  This is used when counting is required, that is, the view
+ * has aggregate or distinct. Also, when a table in EXISTS sub queries
+ * is modified.
+ */
+static void
+apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
+				List *keys, StringInfo target_list, const char* count_colname)
+{
+	StringInfoData	querybuf;
+	StringInfoData	returning_keys;
+	ListCell	*lc;
+	char	*match_cond = "";
+
+	/* build WHERE condition for searching tuples to be updated */
+	match_cond = get_matching_condition_string(keys);
+
+	/* build string of keys list */
+	initStringInfo(&returning_keys);
+	if (keys)
+	{
+		foreach (lc, keys)
+		{
+			Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+			char   *resname = NameStr(attr->attname);
+			appendStringInfo(&returning_keys, "%s", quote_qualified_identifier("mv", resname));
+			if (lnext(keys, lc))
+				appendStringInfo(&returning_keys, ", ");
+		}
+	}
+	else
+		appendStringInfo(&returning_keys, "NULL");
+
+	/* Search for matching tuples from the view and update if found or insert if not. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"WITH updt AS ("		/* update a tuple if this exists in the view */
+						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.+) diff.%s "
+						"FROM %s AS diff "
+						"WHERE %s "					/* tuple matching condition */
+						"RETURNING %s"				/* returning keys of updated tuples */
+					") INSERT INTO %s (%s)"	/* insert a new tuple if this doesn't existw */
+						"SELECT %s FROM %s AS diff "
+						"WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE %s);",
+					matviewname, count_colname, count_colname, count_colname,
+					deltaname_new,
+					match_cond,
+					returning_keys.data,
+					matviewname, target_list->data,
+					target_list->data, deltaname_new,
+					match_cond);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_new_delta
+ *
+ * Execute a query for applying a delta table given by deltname_new
+ * which contains tuples to be inserted into a materialized view given by
+ * matviewname.  This is used when counting is not required.
+ */
+static void
+apply_new_delta(const char *matviewname, const char *deltaname_new,
+				StringInfo target_list)
+{
+	StringInfoData	querybuf;
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"INSERT INTO %s (%s) SELECT %s FROM ("
+						"SELECT diff.*, generate_series(1, diff.\"__ivm_count__\") "
+						"FROM %s AS diff) AS v",
+					matviewname, target_list->data, target_list->data,
+					deltaname_new);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * get_matching_condition_string
+ *
+ * Build a predicate string for looking for a tuple with given keys.
+ */
+static char *
+get_matching_condition_string(List *keys)
+{
+	StringInfoData match_cond;
+	ListCell	*lc;
+
+	/* If there is no key columns, the condition is always true. */
+	if (keys == NIL)
+		return "true";
+
+	initStringInfo(&match_cond);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char   *resname = NameStr(attr->attname);
+		char   *mv_resname = quote_qualified_identifier("mv", resname);
+		char   *diff_resname = quote_qualified_identifier("diff", resname);
+		Oid		typid = attr->atttypid;
+
+		/* Considering NULL values, we can not use simple = operator. */
+		appendStringInfo(&match_cond, "(");
+		generate_equal(&match_cond, typid, mv_resname, diff_resname);
+		appendStringInfo(&match_cond, " OR (%s IS NULL AND %s IS NULL))",
+						 mv_resname, diff_resname);
+
+		if (lnext(keys, lc))
+			appendStringInfo(&match_cond, " AND ");
+	}
+
+	return match_cond.data;
+}
+
+/*
+ * generate_equals
+ *
+ * Generate an equality clause using given operands' default equality
+ * operator.
+ */
+static void
+generate_equal(StringInfo querybuf, Oid opttype,
+			   const char *leftop, const char *rightop)
+{
+	TypeCacheEntry *typentry;
+
+	typentry = lookup_type_cache(opttype, TYPECACHE_EQ_OPR);
+	if (!OidIsValid(typentry->eq_opr))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_FUNCTION),
+				 errmsg("could not identify an equality operator for type %s",
+						format_type_be(opttype))));
+
+	generate_operator_clause(querybuf,
+							 leftop, opttype,
+							 typentry->eq_opr,
+							 rightop, opttype);
+}
+
+/*
+ * mv_InitHashTables
+ */
+static void
+mv_InitHashTables(void)
+{
+	HASHCTL		ctl;
+
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(MV_TriggerHashEntry);
+	mv_trigger_info = hash_create("MV trigger info",
+								 MV_INIT_QUERYHASHSIZE,
+								 &ctl, HASH_ELEM | HASH_BLOBS);
+}
+
+/*
+ * AtAbort_IVM
+ *
+ * Clean up hash entries for all materialized views. This is called at
+ * transaction abort.
+ */
+void
+AtAbort_IVM()
+{
+	HASH_SEQ_STATUS seq;
+	MV_TriggerHashEntry *entry;
+
+	if (mv_trigger_info)
+	{
+		hash_seq_init(&seq, mv_trigger_info);
+		while ((entry = hash_seq_search(&seq)) != NULL)
+			clean_up_IVM_hash_entry(entry);
+	}
+}
+
+/*
+ * clean_up_IVM_hash_entry
+ *
+ * Clean up tuple stores and hash entries for a materialized view after its
+ * maintenance finished.
+ */
+static void
+clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry)
+{
+	bool found;
+	ListCell *lc;
+
+	foreach(lc, entry->tables)
+	{
+		MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc);
+
+		list_free(table->old_tuplestores);
+		list_free(table->new_tuplestores);
+	}
+	list_free(entry->tables);
+
+	hash_search(mv_trigger_info, (void *) &entry->matview_id, HASH_REMOVE, &found);
+}
+
+/*
+ * isIvmName
+ *
+ * Check if this is a IVM hidden column from the name.
+ */
+bool
+isIvmName(const char *s)
+{
+	if (s)
+		return (strncmp(s, "__ivm_", 6) == 0);
+	return false;
+}
+
+/*
+ * get_securityQuals
+ *
+ * Get row security policy on a relation.
+ * This is used by IVM for copying RLS from base table to enr.
+ */
+static List *
+get_securityQuals(Oid relId, int rt_index, Query *query)
+{
+	ParseState *pstate;
+	Relation rel;
+	ParseNamespaceItem *nsitem;
+	RangeTblEntry *rte;
+	List *securityQuals;
+	List *withCheckOptions;
+	bool  hasRowSecurity;
+	bool  hasSubLinks;
+
+	securityQuals = NIL;
+	pstate = make_parsestate(NULL);
+
+	rel = table_open(relId, NoLock);
+	nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, false);
+	rte = nsitem->p_rte;
+
+	get_row_security_policies(query, rte, rt_index,
+							  &securityQuals, &withCheckOptions,
+							  &hasRowSecurity, &hasSubLinks);
+
+	/*
+	 * Make sure the query is marked correctly if row level security
+	 * applies, or if the new quals had sublinks.
+	 */
+	if (hasRowSecurity)
+		query->hasRowSecurity = true;
+	if (hasSubLinks)
+		query->hasSubLinks = true;
+
+	table_close(rel, NoLock);
+
+	return securityQuals;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 857cc5ce6e..a5652f93ed 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -50,6 +50,7 @@
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
+#include "commands/matview.h"
 #include "commands/event_trigger.h"
 #include "commands/policy.h"
 #include "commands/sequence.h"
@@ -3394,6 +3395,14 @@ renameatt_internal(Oid myrelid,
 	targetrelation = relation_open(myrelid, AccessExclusiveLock);
 	renameatt_check(myrelid, RelationGetForm(targetrelation), recursing);
 
+	/*
+	 * Don't rename IVM columns.
+	 */
+	if (RelationIsIVM(targetrelation) && isIvmName(oldattname))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("IVM column can not be renamed")));
+
 	/*
 	 * if the 'recurse' flag is set then we are supposed to rename this
 	 * attribute in all classes that inherit from 'relname' (as well as in
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 5769a10586..51bd4ffce5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2460,6 +2460,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(relisivm);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 80497dc240..6df5d21b55 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2764,6 +2764,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(relisivm);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index f6166f7859..8133850e98 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3253,6 +3253,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 1e55a58f69..96fa85758f 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1443,6 +1443,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index c5c3f26ecf..4a52035371 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -36,6 +36,7 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 #include "utils/varlena.h"
+#include "commands/matview.h"
 
 
 /*
@@ -79,7 +80,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							int count, int offset,
 							int rtindex, int sublevels_up,
 							int location, bool include_dropped,
-							List **colnames, List **colvars);
+							List **colnames, List **colvars, bool is_ivm);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
 
@@ -1433,6 +1434,7 @@ addRangeTableEntry(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -1521,6 +1523,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -2676,7 +2679,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 						expandTupleDesc(tupdesc, rte->eref,
 										rtfunc->funccolcount, atts_done,
 										rtindex, sublevels_up, location,
-										include_dropped, colnames, colvars);
+										include_dropped, colnames, colvars, false);
 					}
 					else if (functypclass == TYPEFUNC_SCALAR)
 					{
@@ -2944,7 +2947,7 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
 	expandTupleDesc(rel->rd_att, eref, rel->rd_att->natts, 0,
 					rtindex, sublevels_up,
 					location, include_dropped,
-					colnames, colvars);
+					colnames, colvars, RelationIsIVM(rel));
 	relation_close(rel, AccessShareLock);
 }
 
@@ -2961,7 +2964,7 @@ static void
 expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 				int rtindex, int sublevels_up,
 				int location, bool include_dropped,
-				List **colnames, List **colvars)
+				List **colnames, List **colvars, bool is_ivm)
 {
 	ListCell   *aliascell;
 	int			varattno;
@@ -2974,6 +2977,9 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 	{
 		Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
 
+		if (is_ivm && isIvmName(NameStr(attr->attname)) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		if (attr->attisdropped)
 		{
 			if (include_dropped)
@@ -3127,6 +3133,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 		Var		   *varnode = (Var *) lfirst(var);
 		TargetEntry *te;
 
+		/* if transform * into columnlist with IMMV, remove IVM columns */
+		if (rte->relisivm && isIvmName(label) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		te = makeTargetEntry((Expr *) varnode,
 							 (AttrNumber) pstate->p_next_resno++,
 							 label,
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 6589345ac4..02f33d404b 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -776,7 +776,8 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 														attr->atttypmod))));
 	}
 
-	if (i != resultDesc->natts)
+	/* No check for materialized views since this could have special columns for IVM */
+	if ((!isSelect || requireColumnNameMatch) && i != resultDesc->natts)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 isSelect ?
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d068d6532e..4845e71898 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11689,4 +11689,12 @@
   prorettype => 'bytea', proargtypes => 'pg_brin_minmax_multi_summary',
   prosrc => 'brin_minmax_multi_summary_send' },
 
+# IVM
+{ oid => '786', descr => 'ivm trigger (before)',
+  proname => 'IVM_immediate_before', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_before' },
+{ oid => '787', descr => 'ivm trigger (after)',
+  proname => 'IVM_immediate_maintenance', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_maintenance' },
+
 ]
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index ad5054d116..a57ce463e1 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -16,6 +16,7 @@
 
 #include "catalog/objectaddress.h"
 #include "nodes/params.h"
+#include "nodes/pathnodes.h"
 #include "parser/parse_node.h"
 #include "tcop/dest.h"
 #include "utils/queryenvironment.h"
@@ -25,6 +26,10 @@ extern ObjectAddress ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *st
 									   ParamListInfo params, QueryEnvironment *queryEnv,
 									   QueryCompletion *qc);
 
+extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create);
+
+extern Query *rewriteQueryForIMMV(Query *query, List *colNames);
+
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
 
 extern DestReceiver *CreateIntoRelDestReceiver(IntoClause *intoClause);
diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h
index 214b1c1df6..13a5722f17 100644
--- a/src/include/commands/matview.h
+++ b/src/include/commands/matview.h
@@ -15,6 +15,7 @@
 #define MATVIEW_H
 
 #include "catalog/objectaddress.h"
+#include "fmgr.h"
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
 #include "tcop/dest.h"
@@ -23,6 +24,8 @@
 
 extern void SetMatViewPopulatedState(Relation relation, bool newstate);
 
+extern void SetMatViewIVMState(Relation relation, bool newstate);
+
 extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										ParamListInfo params, QueryCompletion *qc);
 
@@ -30,4 +33,9 @@ extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
 
 extern bool MatViewIncrementalMaintenanceIsEnabled(void);
 
+extern Datum IVM_immediate_before(PG_FUNCTION_ARGS);
+extern Datum IVM_immediate_maintenance(PG_FUNCTION_ARGS);
+extern void AtAbort_IVM(void);
+extern bool isIvmName(const char *s);
+
 #endif							/* MATVIEW_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 49123e28a4..526ae434af 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1035,6 +1035,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		relisivm;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -2194,6 +2195,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		ivm;			/* incremental view maintenance is used by materialized view */
 } CreateStmt;
 
 /* ----------
-- 
2.17.1

v24-0008-Add-aggregates-support-in-IVM.patchtext/x-diff; name=v24-0008-Add-aggregates-support-in-IVM.patchDownload
From a356fb3b252afc4ab1171b19c6d53e77a7661573 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Mon, 2 Aug 2021 14:59:27 +0900
Subject: [PATCH v24 08/15] Add aggregates support in IVM
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Currently, count, sum, avg, min and max are supported.

As a restriction, expressions specified in GROUP BY must appear in
the target list because tuples to be updated in IMMV are identified
by using this group key. However, in the case of aggregates without
GROUP BY, there is only one tuple in the view, so keys are not uses
to identify tuples.

When creating a IMMV, in addition to __ivm_count column, some hidden
columns for each aggregate are added to the target list. For example,
names of these hidden columns are ivm_count_avg and ivm_sum_avg for
the average function, and so on.

In the case of views without aggregate functions, only the number of
tuple multiplicities in __ivm_count__ column are updated at incremental
maintenance. On the other hand, in the case of view with aggregates,
the aggregated values and related hidden columns are also updated. The
way of update depends the kind of aggregate function. Specifically,
sum and count are updated by simply adding or subtracting delta value
calculated from delta tables. avg is updated by using values of sum
and count stored in views as hidden columns and deltas calculated
from delta tables.

In min or max cases, it becomes more complicated. For an example of min,
when tuples are inserted, the smaller value between the current min value
in the view and the value calculated from the new delta table is used.
When tuples are deleted, if the current min value in the view equals to
the min in the old delta table, we need re-computation the latest min
value from base tables. Otherwise, the current value in the view remains.

As to sum, avg, min, and max (any aggregate functions except count),
NULL in input values is ignored, and this returns a null value when no
rows are selected. To support this specification, the number of not-NULL
input values is counted and stored in views as a hidden column. In the
case of count(), count(x) returns zero when no rows are selected, and
count(*) doesn't ignore NULL input. These specification are also supported.
---
 src/backend/commands/createas.c |  299 ++++++++-
 src/backend/commands/matview.c  | 1016 ++++++++++++++++++++++++++++++-
 src/include/commands/createas.h |    1 +
 3 files changed, 1281 insertions(+), 35 deletions(-)

diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 91888891bf..ce6e6ffe33 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/regproc.h"
 #include "utils/rel.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
@@ -80,6 +81,11 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_intorel;
 
+typedef struct
+{
+	bool	has_agg;
+} check_ivm_restriction_context;
+
 /* utility functions for CTAS definition creation */
 static ObjectAddress create_ctas_internal(List *attrList, IntoClause *into);
 static ObjectAddress create_ctas_nodata(List *tlist, IntoClause *into);
@@ -94,9 +100,10 @@ static void CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid mat
 									 Relids *relids, bool ex_lock);
 static void CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock);
 static void check_ivm_restriction(Node *node);
-static bool check_ivm_restriction_walker(Node *node, void *context);
+static bool check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context);
 static void CreateIndexOnIMMV(Query *query, Relation matviewRel);
 static Bitmapset *get_primary_key_attnos_from_query(Query *qry, List **constraintList);
+static bool check_aggregate_supports_ivm(Oid aggfnoid);
 
 /*
  * create_ctas_internal
@@ -432,6 +439,7 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
  * rewriteQueryForIMMV -- rewrite view definition query for IMMV
  *
  * count(*) is added for counting distinct tuples in views.
+ * Also, additional hidden columns are added for aggregate values.
  */
 Query *
 rewriteQueryForIMMV(Query *query, List *colNames)
@@ -446,14 +454,46 @@ rewriteQueryForIMMV(Query *query, List *colNames)
 	rewritten = copyObject(query);
 	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
 
-	/*
-	 * Convert DISTINCT to GROUP BY and add count(*) for counting distinct
-	 * tuples in views.
-	 */
-	if (rewritten->distinctClause)
+	/* group keys must be in targetlist */
+	if (rewritten->groupClause)
 	{
+		ListCell *lc;
+		foreach(lc, rewritten->groupClause)
+		{
+			SortGroupClause *scl = (SortGroupClause *) lfirst(lc);
+			TargetEntry *tle = get_sortgroupclause_tle(scl, rewritten->targetList);
+
+			if (tle->resjunk)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("GROUP BY expression not appearing in select list is not supported on incrementally maintainable materialized view")));
+		}
+	}
+	/* Convert DISTINCT to GROUP BY.  count(*) will be added afterward. */
+	else if (!rewritten->hasAggs && rewritten->distinctClause)
 		rewritten->groupClause = transformDistinctClause(NULL, &rewritten->targetList, rewritten->sortClause, false);
 
+	/* Add additional columns for aggregate values */
+	if (rewritten->hasAggs)
+	{
+		ListCell *lc;
+		List *aggs = NIL;
+		AttrNumber next_resno = list_length(rewritten->targetList) + 1;
+
+		foreach(lc, rewritten->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+			char *resname = (colNames == NIL ? tle->resname : strVal(list_nth(colNames, tle->resno - 1)));
+
+			if (IsA(tle->expr, Aggref))
+				makeIvmAggColumn(pstate, (Aggref *)tle->expr, resname, &next_resno, &aggs);
+		}
+		rewritten->targetList = list_concat(rewritten->targetList, aggs);
+	}
+
+	/* Add count(*) for counting distinct tuples in views */
+	if (rewritten->distinctClause || rewritten->hasAggs)
+	{
 		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
 		fn->agg_star = true;
 
@@ -470,6 +510,91 @@ rewriteQueryForIMMV(Query *query, List *colNames)
 	return rewritten;
 }
 
+/*
+ * makeIvmAggColumn -- make additional aggregate columns for IVM
+ *
+ * For an aggregate column specified by aggref, additional aggregate columns
+ * are added, which are used to calculate the new aggregate value in IMMV.
+ * An additional aggregate columns has a name based on resname
+ * (ex. ivm_count_resname), and resno specified by next_resno. The created
+ * columns are returned to aggs, and the resno for the next column is also
+ * returned to next_resno.
+ *
+ * Currently, an additional count() is created for aggref other than count.
+ * In addition, sum() is created for avg aggregate column.
+ */
+void
+makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber *next_resno, List **aggs)
+{
+	TargetEntry *tle_count;
+	Node *node;
+	FuncCall *fn;
+	Const	*dmy_arg = makeConst(INT4OID,
+								 -1,
+								 InvalidOid,
+								 sizeof(int32),
+								 Int32GetDatum(1),
+								 false,
+								 true); /* pass by value */
+	const char *aggname = get_func_name(aggref->aggfnoid);
+
+	/*
+	 * For aggregate functions except count, add count() func with the same arg parameters.
+	 * This count result is used for determining if the aggregate value should be NULL or not.
+	 * Also, add sum() func for avg because we need to calculate an average value as sum/count.
+	 *
+	 * XXX: If there are same expressions explicitly in the target list, we can use this instead
+	 * of adding new duplicated one.
+	 */
+	if (strcmp(aggname, "count") != 0)
+	{
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+
+		/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+		node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+		((Aggref *)node)->args = aggref->args;
+
+		tle_count = makeTargetEntry((Expr *) node,
+									*next_resno,
+									pstrdup(makeObjectName("__ivm_count",resname, "_")),
+									false);
+		*aggs = lappend(*aggs, tle_count);
+		(*next_resno)++;
+	}
+	if (strcmp(aggname, "avg") == 0)
+	{
+		List *dmy_args = NIL;
+		ListCell *lc;
+		foreach(lc, aggref->aggargtypes)
+		{
+			Oid		typeid = lfirst_oid(lc);
+			Type	type = typeidType(typeid);
+
+			Const *con = makeConst(typeid,
+								   -1,
+								   typeTypeCollation(type),
+								   typeLen(type),
+								   (Datum) 0,
+								   true,
+								   typeByVal(type));
+			dmy_args = lappend(dmy_args, con);
+			ReleaseSysCache(type);
+		}
+		fn = makeFuncCall(list_make1(makeString("sum")), NIL, COERCE_EXPLICIT_CALL, -1);
+
+		/* Make a Func with dummy args, and then override this by the original agg's args. */
+		node = ParseFuncOrColumn(pstate, fn->funcname, dmy_args, NULL, fn, false, -1);
+		((Aggref *)node)->args = aggref->args;
+
+		tle_count = makeTargetEntry((Expr *) node,
+									*next_resno,
+									pstrdup(makeObjectName("__ivm_sum",resname, "_")),
+									false);
+		*aggs = lappend(*aggs, tle_count);
+		(*next_resno)++;
+	}
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -923,11 +1048,13 @@ CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock
 static void
 check_ivm_restriction(Node *node)
 {
-	check_ivm_restriction_walker(node, NULL);
+	check_ivm_restriction_context context = {false};
+
+	check_ivm_restriction_walker(node, &context);
 }
 
 static bool
-check_ivm_restriction_walker(Node *node, void *context)
+check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context)
 {
 	if (node == NULL)
 		return false;
@@ -1008,6 +1135,8 @@ check_ivm_restriction_walker(Node *node, void *context)
 					}
 				}
 
+				context->has_agg |= qry->hasAggs;
+
 				/* restrictions for rtable */
 				foreach(lc, qry->rtable)
 				{
@@ -1056,7 +1185,7 @@ check_ivm_restriction_walker(Node *node, void *context)
 
 				}
 
-				query_tree_walker(qry, check_ivm_restriction_walker, NULL, QTW_IGNORE_RANGE_TABLE);
+				query_tree_walker(qry, check_ivm_restriction_walker, (void *) context, QTW_IGNORE_RANGE_TABLE);
 
 				break;
 			}
@@ -1067,8 +1196,12 @@ check_ivm_restriction_walker(Node *node, void *context)
 						ereport(ERROR,
 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								 errmsg("column name %s is not supported on incrementally maintainable materialized view", tle->resname)));
+				if (context->has_agg && !IsA(tle->expr, Aggref) && contain_aggs_of_level((Node *) tle->expr, 0))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("expression containing an aggregate in it is not supported on incrementally maintainable materialized view")));
 
-				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
 				break;
 			}
 		case T_JoinExpr:
@@ -1080,15 +1213,128 @@ check_ivm_restriction_walker(Node *node, void *context)
 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								 errmsg("OUTER JOIN is not supported on incrementally maintainable materialized view")));
 
-				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
+				break;
 			}
-			break;
-			expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+		case T_Aggref:
+			{
+				/* Check if this supports IVM */
+				Aggref *aggref = (Aggref *) node;
+				const char *aggname = format_procedure(aggref->aggfnoid);
+
+				if (aggref->aggfilter != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with FILTER clause is not supported on incrementally maintainable materialized view")));
+
+				if (aggref->aggdistinct != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with DISTINCT arguments is not supported on incrementally maintainable materialized view")));
+
+				if (aggref->aggorder != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with ORDER clause is not supported on incrementally maintainable materialized view")));
+
+				if (!check_aggregate_supports_ivm(aggref->aggfnoid))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function %s is not supported on incrementally maintainable materialized view", aggname)));
+				break;
+			}
+		default:
+			expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
 			break;
 	}
 	return false;
 }
 
+/*
+ * check_aggregate_supports_ivm
+ *
+ * Check if the given aggregate function is supporting IVM
+ */
+static bool
+check_aggregate_supports_ivm(Oid aggfnoid)
+{
+	switch (aggfnoid)
+	{
+		/* count */
+		case F_COUNT_ANY:
+		case F_COUNT_:
+
+		/* sum */
+		case F_SUM_INT8:
+		case F_SUM_INT4:
+		case F_SUM_INT2:
+		case F_SUM_FLOAT4:
+		case F_SUM_FLOAT8:
+		case F_SUM_MONEY:
+		case F_SUM_INTERVAL:
+		case F_SUM_NUMERIC:
+
+		/* avg */
+		case F_AVG_INT8:
+		case F_AVG_INT4:
+		case F_AVG_INT2:
+		case F_AVG_NUMERIC:
+		case F_AVG_FLOAT4:
+		case F_AVG_FLOAT8:
+		case F_AVG_INTERVAL:
+
+		/* min */
+		case F_MIN_ANYARRAY:
+		case F_MIN_INT8:
+		case F_MIN_INT4:
+		case F_MIN_INT2:
+		case F_MIN_OID:
+		case F_MIN_FLOAT4:
+		case F_MIN_FLOAT8:
+		case F_MIN_DATE:
+		case F_MIN_TIME:
+		case F_MIN_TIMETZ:
+		case F_MIN_MONEY:
+		case F_MIN_TIMESTAMP:
+		case F_MIN_TIMESTAMPTZ:
+		case F_MIN_INTERVAL:
+		case F_MIN_TEXT:
+		case F_MIN_NUMERIC:
+		case F_MIN_BPCHAR:
+		case F_MIN_TID:
+		case F_MIN_ANYENUM:
+		case F_MIN_INET:
+		case F_MIN_PG_LSN:
+
+		/* max */
+		case F_MAX_ANYARRAY:
+		case F_MAX_INT8:
+		case F_MAX_INT4:
+		case F_MAX_INT2:
+		case F_MAX_OID:
+		case F_MAX_FLOAT4:
+		case F_MAX_FLOAT8:
+		case F_MAX_DATE:
+		case F_MAX_TIME:
+		case F_MAX_TIMETZ:
+		case F_MAX_MONEY:
+		case F_MAX_TIMESTAMP:
+		case F_MAX_TIMESTAMPTZ:
+		case F_MAX_INTERVAL:
+		case F_MAX_TEXT:
+		case F_MAX_NUMERIC:
+		case F_MAX_BPCHAR:
+		case F_MAX_TID:
+		case F_MAX_ANYENUM:
+		case F_MAX_INET:
+		case F_MAX_PG_LSN:
+			return true;
+
+		default:
+			return false;
+	}
+}
+
 /*
  * CreateIndexOnIMMV
  *
@@ -1138,7 +1384,30 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel)
 	index->concurrent = false;
 	index->if_not_exists = false;
 
-	if (qry->distinctClause)
+
+	if (qry->groupClause)
+	{
+		/* create unique constraint on GROUP BY expression columns */
+		foreach(lc, qry->groupClause)
+		{
+			SortGroupClause *scl = (SortGroupClause *) lfirst(lc);
+			TargetEntry *tle = get_sortgroupclause_tle(scl, qry->targetList);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+			IndexElem  *iparam;
+
+			iparam = makeNode(IndexElem);
+			iparam->name = pstrdup(NameStr(attr->attname));
+			iparam->expr = NULL;
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->opclassopts = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+		}
+	}
+	else if (qry->distinctClause)
 	{
 		/* create unique constraint on all columns */
 		foreach(lc, qry->targetList)
@@ -1197,7 +1466,7 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel)
 					(errmsg("could not create an index on materialized view \"%s\" automatically",
 							RelationGetRelationName(matviewRel)),
 					 errdetail("This target list does not have all the primary key columns, "
-							   "or this view does not contain DISTINCT clause."),
+							   "or this view does not contain GROUP BY or DISTINCT clause."),
 					 errhint("Create an index on the materialized view for efficient incremental maintenance.")));
 			return;
 		}
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index a27d23434d..219444571a 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -81,6 +81,32 @@ typedef struct
 
 #define MV_INIT_QUERYHASHSIZE	16
 
+/* MV query type codes */
+#define MV_PLAN_RECALC			1
+#define MV_PLAN_SET_VALUE		2
+
+/*
+ * MI_QueryKey
+ *
+ * The key identifying a prepared SPI plan in our query hashtable
+ */
+typedef struct MV_QueryKey
+{
+	Oid			matview_id;	/* OID of materialized view */
+	int32		query_type;	/* query type ID, see MV_PLAN_XXX above */
+} MV_QueryKey;
+
+/*
+ * MV_QueryHashEntry
+ *
+ * Hash entry for cached plans used to maintain materialized views.
+ */
+typedef struct MV_QueryHashEntry
+{
+	MV_QueryKey key;
+	SPIPlanPtr	plan;
+} MV_QueryHashEntry;
+
 /*
  * MV_TriggerHashEntry
  *
@@ -117,8 +143,16 @@ typedef struct MV_TriggerTable
 	RangeTblEntry *original_rte;	/* the original RTE saved before rewriting query */
 } MV_TriggerTable;
 
+static HTAB *mv_query_cache = NULL;
 static HTAB *mv_trigger_info = NULL;
 
+/* kind of IVM operation for the view */
+typedef enum
+{
+	IVM_ADD,
+	IVM_SUB
+} IvmOp;
+
 /* ENR name for materialized view delta */
 #define NEW_DELTA_ENRNAME "new_delta"
 #define OLD_DELTA_ENRNAME "old_delta"
@@ -152,7 +186,7 @@ static RangeTblEntry *get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *tabl
 				 QueryEnvironment *queryEnv);
 static RangeTblEntry *union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
 		   QueryEnvironment *queryEnv);
-static Query *rewrite_query_for_distinct(Query *query, ParseState *pstate);
+static Query *rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate);
 
 static void calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
 			DestReceiver *dest_old, DestReceiver *dest_new,
@@ -163,19 +197,48 @@ static Query *rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *
 static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
 			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
 			Query *query, bool use_count, char *count_colname);
+static void append_set_clause_for_count(const char *resname, StringInfo buf_old,
+							StringInfo buf_new,StringInfo aggs_list);
+static void append_set_clause_for_sum(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list);
+static void append_set_clause_for_avg(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list,
+						  const char *aggtype);
+static void append_set_clause_for_minmax(const char *resname, StringInfo buf_old,
+							 StringInfo buf_new, StringInfo aggs_list,
+							 bool is_min);
+static char *get_operation_string(IvmOp op, const char *col, const char *arg1, const char *arg2,
+					 const char* count_col, const char *castType);
+static char *get_null_condition_string(IvmOp op, const char *arg1, const char *arg2,
+						  const char* count_col);
 static void apply_old_delta(const char *matviewname, const char *deltaname_old,
 				List *keys);
 static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
-				List *keys, const char *count_colname);
+				List *keys, StringInfo aggs_list, StringInfo aggs_set,
+				List *minmax_list, List *is_min_list,
+				const char *count_colname,
+				SPITupleTable **tuptable_recalc, uint64 *num_recalc);
 static void apply_new_delta(const char *matviewname, const char *deltaname_new,
 				StringInfo target_list);
 static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
-				List *keys, StringInfo target_list, const char* count_colname);
+				List *keys, StringInfo target_list, StringInfo aggs_set,
+				const char* count_colname);
 static char *get_matching_condition_string(List *keys);
+static char *get_returning_string(List *minmax_list, List *is_min_list, List *keys);
+static char *get_minmax_recalc_condition_string(List *minmax_list, List *is_min_list);
+static char *get_select_for_recalc_string(List *keys);
+static void recalc_and_set_values(SPITupleTable *tuptable_recalc, int64 num_tuples,
+					  List *namelist, List *keys, Relation matviewRel);
+static SPIPlanPtr get_plan_for_recalc(Oid matviewOid, List *namelist, List *keys, Oid *keyTypes);
+static SPIPlanPtr get_plan_for_set_values(Oid matviewOid, char *matviewname, List *namelist,
+						Oid *valTypes);
 static void generate_equal(StringInfo querybuf, Oid opttype,
 			   const char *leftop, const char *rightop);
 
 static void mv_InitHashTables(void);
+static SPIPlanPtr mv_FetchPreparedPlan(MV_QueryKey *key);
+static void mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan);
+static void mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type);
 static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry);
 
 static List *get_securityQuals(Oid relId, int rt_index, Query *query);
@@ -1441,8 +1504,8 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
 	rewritten = rewrite_query_for_preupdate_state(rewritten, entry->tables,
 												  entry->xid, entry->cid,
 												  pstate);
-	/* Rewrite for DISTINCT clause */
-	rewritten = rewrite_query_for_distinct(rewritten, pstate);
+	/* Rewrite for DISTINCT clause and aggregates functions */
+	rewritten = rewrite_query_for_distinct_and_aggregates(rewritten, pstate);
 
 	/* Create tuplestores to store view deltas */
 	if (entry->has_old)
@@ -1493,7 +1556,7 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
 
 			count_colname = pstrdup("__ivm_count__");
 
-			if (query->distinctClause)
+			if (query->hasAggs || query->distinctClause)
 				use_count = true;
 
 			/* calculate delta tables */
@@ -1855,17 +1918,34 @@ union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
 }
 
 /*
- * rewrite_query_for_distinct
+ * rewrite_query_for_distinct_and_aggregates
  *
- * Rewrite query for counting DISTINCT clause.
+ * Rewrite query for counting DISTINCT clause and aggregate functions.
  */
 static Query *
-rewrite_query_for_distinct(Query *query, ParseState *pstate)
+rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate)
 {
 	TargetEntry *tle_count;
 	FuncCall *fn;
 	Node *node;
 
+	/* For aggregate views */
+	if (query->hasAggs)
+	{
+		ListCell *lc;
+		List *aggs = NIL;
+		AttrNumber next_resno = list_length(query->targetList) + 1;
+
+		foreach(lc, query->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+			if (IsA(tle->expr, Aggref))
+				makeIvmAggColumn(pstate, (Aggref *)tle->expr, tle->resname, &next_resno, &aggs);
+		}
+		query->targetList = list_concat(query->targetList, aggs);
+	}
+
 	/* Add count(*) for counting distinct tuples in views */
 	fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
 	fn->agg_star = true;
@@ -1934,6 +2014,8 @@ rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte
 	return query;
 }
 
+#define IVM_colname(type, col) makeObjectName("__ivm_" type, col, "_")
+
 /*
  * apply_delta
  *
@@ -1947,11 +2029,16 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 {
 	StringInfoData querybuf;
 	StringInfoData target_list_buf;
+	StringInfo	aggs_list_buf = NULL;
+	StringInfo	aggs_set_old = NULL;
+	StringInfo	aggs_set_new = NULL;
 	Relation	matviewRel;
 	char	   *matviewname;
 	ListCell	*lc;
 	int			i;
 	List	   *keys = NIL;
+	List	   *minmax_list = NIL;
+	List	   *is_min_list = NIL;
 
 
 	/*
@@ -1969,6 +2056,15 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 	initStringInfo(&querybuf);
 	initStringInfo(&target_list_buf);
 
+	if (query->hasAggs)
+	{
+		if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
+			aggs_set_old = makeStringInfo();
+		if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
+			aggs_set_new = makeStringInfo();
+		aggs_list_buf = makeStringInfo();
+	}
+
 	/* build string of target list */
 	for (i = 0; i < matviewRel->rd_att->natts; i++)
 	{
@@ -1992,7 +2088,65 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		if (tle->resjunk)
 			continue;
 
-		keys = lappend(keys, resname);
+		/*
+		 * For views without aggregates, all attributes are used as keys to identify a
+		 * tuple in a view.
+		 */
+		if (!query->hasAggs)
+			keys = lappend(keys, attr);
+
+		/* For views with aggregates, we need to build SET clause for updating aggregate
+		 * values. */
+		if (query->hasAggs && IsA(tle->expr, Aggref))
+		{
+			Aggref *aggref = (Aggref *) tle->expr;
+			const char *aggname = get_func_name(aggref->aggfnoid);
+
+			/*
+			 * We can use function names here because it is already checked if these
+			 * can be used in IMMV by its OID at the definition time.
+			 */
+
+			/* count */
+			if (!strcmp(aggname, "count"))
+				append_set_clause_for_count(resname, aggs_set_old, aggs_set_new, aggs_list_buf);
+
+			/* sum */
+			else if (!strcmp(aggname, "sum"))
+				append_set_clause_for_sum(resname, aggs_set_old, aggs_set_new, aggs_list_buf);
+
+			/* avg */
+			else if (!strcmp(aggname, "avg"))
+				append_set_clause_for_avg(resname, aggs_set_old, aggs_set_new, aggs_list_buf,
+										  format_type_be(aggref->aggtype));
+
+			/* min/max */
+			else if (!strcmp(aggname, "min") || !strcmp(aggname, "max"))
+			{
+				bool	is_min = (!strcmp(aggname, "min"));
+
+				append_set_clause_for_minmax(resname, aggs_set_old, aggs_set_new, aggs_list_buf, is_min);
+
+				/* make a resname list of min and max aggregates */
+				minmax_list = lappend(minmax_list, resname);
+				is_min_list = lappend_int(is_min_list, is_min);
+			}
+			else
+				elog(ERROR, "unsupported aggregate function: %s", aggname);
+		}
+	}
+
+	/* If we have GROUP BY clause, we use its entries as keys. */
+	if (query->hasAggs && query->groupClause)
+	{
+		foreach (lc, query->groupClause)
+		{
+			SortGroupClause *sgcl = (SortGroupClause *) lfirst(lc);
+			TargetEntry		*tle = get_sortgroupclause_tle(sgcl, query->targetList);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+
+			keys = lappend(keys, attr);
+		}
 	}
 
 	/* Start maintaining the materialized view. */
@@ -2006,6 +2160,8 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 	if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
 	{
 		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		SPITupleTable  *tuptable_recalc = NULL;
+		uint64			num_recalc;
 		int				rc;
 
 		/* convert tuplestores to ENR, and register for SPI */
@@ -2023,10 +2179,19 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		if (use_count)
 			/* apply old delta and get rows to be recalculated */
 			apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME,
-									   keys, count_colname);
+									   keys, aggs_list_buf, aggs_set_old,
+									   minmax_list, is_min_list,
+									   count_colname, &tuptable_recalc, &num_recalc);
 		else
 			apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys);
 
+		/*
+		 * If we have min or max, we might have to recalculate aggregate values from base tables
+		 * on some tuples. TIDs and keys such tuples are returned as a result of the above query.
+		 */
+		if (minmax_list && tuptable_recalc)
+			recalc_and_set_values(tuptable_recalc, num_recalc, minmax_list, keys, matviewRel);
+
 	}
 	/* For tuple insertion */
 	if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
@@ -2049,7 +2214,7 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		/* apply new delta */
 		if (use_count)
 			apply_new_delta_with_count(matviewname, NEW_DELTA_ENRNAME,
-								keys, &target_list_buf, count_colname);
+								keys, aggs_set_new, &target_list_buf, count_colname);
 		else
 			apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf);
 	}
@@ -2064,49 +2229,410 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		elog(ERROR, "SPI_finish failed");
 }
 
+/*
+ * append_set_clause_for_count
+ *
+ * Append SET clause string for count aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_count(const char *resname, StringInfo buf_old,
+							StringInfo buf_new,StringInfo aggs_list)
+{
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* resname = mv.resname - t.resname */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, resname, "mv", "t", NULL, NULL));
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* resname = mv.resname + diff.resname */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, resname, "mv", "diff", NULL, NULL));
+	}
+
+	appendStringInfo(aggs_list, ", %s",
+		quote_qualified_identifier("diff", resname)
+	);
+}
+
+/*
+ * append_set_clause_for_sum
+ *
+ * Append SET clause string for sum aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_sum(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list)
+{
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* sum = mv.sum - t.sum */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, resname, "mv", "t", count_col, NULL)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* sum = mv.sum + diff.sum */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, resname, "mv", "diff", count_col, NULL)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * append_set_clause_for_avg
+ *
+ * Append SET clause string for avg aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_avg(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list,
+						  const char *aggtype)
+{
+	char *sum_col = IVM_colname("sum", resname);
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* avg = (mv.sum - t.sum)::aggtype / (mv.count - t.count) */
+		appendStringInfo(buf_old,
+			", %s = %s OPERATOR(pg_catalog./) %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, aggtype),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+		/* sum = mv.sum - t.sum */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, sum_col),
+			get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, NULL)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* avg = (mv.sum + diff.sum)::aggtype / (mv.count + diff.count) */
+		appendStringInfo(buf_new,
+			", %s = %s OPERATOR(pg_catalog./) %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, aggtype),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+		/* sum = mv.sum + diff.sum */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, sum_col),
+			get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, NULL)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("sum", resname)),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * append_set_clause_for_minmax
+ *
+ * Append SET clause string for min or max aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ * is_min is true if this is min, false if not.
+ */
+static void
+append_set_clause_for_minmax(const char *resname, StringInfo buf_old,
+							 StringInfo buf_new, StringInfo aggs_list,
+							 bool is_min)
+{
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/*
+		 * If the new value doesn't became NULL then use the value remaining
+		 * in the view although this will be recomputated afterwords.
+		 */
+		appendStringInfo(buf_old,
+			", %s = CASE WHEN %s THEN NULL ELSE %s END",
+			quote_qualified_identifier(NULL, resname),
+			get_null_condition_string(IVM_SUB, "mv", "t", count_col),
+			quote_qualified_identifier("mv", resname)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/*
+		 * min = LEAST(mv.min, diff.min)
+		 * max = GREATEST(mv.max, diff.max)
+		 */
+		appendStringInfo(buf_new,
+			", %s = CASE WHEN %s THEN NULL ELSE %s(%s,%s) END",
+			quote_qualified_identifier(NULL, resname),
+			get_null_condition_string(IVM_ADD, "mv", "diff", count_col),
+
+			is_min ? "LEAST" : "GREATEST",
+			quote_qualified_identifier("mv", resname),
+			quote_qualified_identifier("diff", resname)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * get_operation_string
+ *
+ * Build a string to calculate the new aggregate values.
+ */
+static char *
+get_operation_string(IvmOp op, const char *col, const char *arg1, const char *arg2,
+					 const char* count_col, const char *castType)
+{
+	StringInfoData buf;
+	StringInfoData castString;
+	char   *col1 = quote_qualified_identifier(arg1, col);
+	char   *col2 = quote_qualified_identifier(arg2, col);
+	char	op_char = (op == IVM_SUB ? '-' : '+');
+
+	initStringInfo(&buf);
+	initStringInfo(&castString);
+
+	if (castType)
+		appendStringInfo(&castString, "::%s", castType);
+
+	if (!count_col)
+	{
+		/*
+		 * If the attributes don't have count columns then calc the result
+		 * by using the operator simply.
+		 */
+		appendStringInfo(&buf, "(%s OPERATOR(pg_catalog.%c) %s)%s",
+			col1, op_char, col2, castString.data);
+	}
+	else
+	{
+		/*
+		 * If the attributes have count columns then consider the condition
+		 * where the result becomes NULL.
+		 */
+		char *null_cond = get_null_condition_string(op, arg1, arg2, count_col);
+
+		appendStringInfo(&buf,
+			"(CASE WHEN %s THEN NULL "
+				"WHEN %s IS NULL THEN %s "
+				"WHEN %s IS NULL THEN %s "
+				"ELSE (%s OPERATOR(pg_catalog.%c) %s)%s END)",
+			null_cond,
+			col1, col2,
+			col2, col1,
+			col1, op_char, col2, castString.data
+		);
+	}
+
+	return buf.data;
+}
+
+/*
+ * get_null_condition_string
+ *
+ * Build a predicate string for CASE clause to check if an aggregate value
+ * will became NULL after the given operation is applied.
+ */
+static char *
+get_null_condition_string(IvmOp op, const char *arg1, const char *arg2,
+						  const char* count_col)
+{
+	StringInfoData null_cond;
+	initStringInfo(&null_cond);
+
+	switch (op)
+	{
+		case IVM_ADD:
+			appendStringInfo(&null_cond,
+				"%s OPERATOR(pg_catalog.=) 0 AND %s OPERATOR(pg_catalog.=) 0",
+				quote_qualified_identifier(arg1, count_col),
+				quote_qualified_identifier(arg2, count_col)
+			);
+			break;
+		case IVM_SUB:
+			appendStringInfo(&null_cond,
+				"%s OPERATOR(pg_catalog.=) %s",
+				quote_qualified_identifier(arg1, count_col),
+				quote_qualified_identifier(arg2, count_col)
+			);
+			break;
+		default:
+			elog(ERROR,"unknown operation");
+	}
+
+	return null_cond.data;
+}
+
+
 /*
  * apply_old_delta_with_count
  *
  * Execute a query for applying a delta table given by deltname_old
  * which contains tuples to be deleted from to a materialized view given by
  * matviewname.  This is used when counting is required, that is, the view
- * has aggregate or distinct.
+ * has aggregate or distinct. Also, when a table in EXISTS sub queries
+ * is modified.
+ *
+ * If the view desn't have aggregates or has GROUP BY, this requires a keys
+ * list to identify a tuple in the view. If the view has aggregates, this
+ * requires strings representing resnames of aggregates and SET clause for
+ * updating aggregate values.
+ *
+ * If the view has min or max aggregate, this requires a list of resnames of
+ * min/max aggregates and a list of boolean which represents which entries in
+ * minmax_list is min. These are necessary to check if we need to recalculate
+ * min or max aggregate values. In this case, this query returns TID and keys
+ * of tuples which need to be recalculated.  This result and the number of rows
+ * are stored in tuptables and num_recalc repectedly.
+ *
  */
 static void
 apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
-				List *keys, const char *count_colname)
+				List *keys, StringInfo aggs_list, StringInfo aggs_set,
+				List *minmax_list, List *is_min_list,
+				const char *count_colname,
+				SPITupleTable **tuptable_recalc, uint64 *num_recalc)
 {
 	StringInfoData	querybuf;
 	char   *match_cond;
+	char   *updt_returning = "";
+	char   *select_for_recalc = "SELECT";
+	bool	agg_without_groupby = (list_length(keys) == 0);
+
+	Assert(tuptable_recalc != NULL);
+	Assert(num_recalc != NULL);
 
 	/* build WHERE condition for searching tuples to be deleted */
 	match_cond = get_matching_condition_string(keys);
 
+	/*
+	 * We need a special RETURNING clause and SELECT statement for min/max to
+	 * check which tuple needs re-calculation from base tables.
+	 */
+	if (minmax_list)
+	{
+		updt_returning = get_returning_string(minmax_list, is_min_list, keys);
+		select_for_recalc = get_select_for_recalc_string(keys);
+	}
+
 	/* Search for matching tuples from the view and update or delete if found. */
 	initStringInfo(&querybuf);
 	appendStringInfo(&querybuf,
 					"WITH t AS ("			/* collecting tid of target tuples in the view */
 						"SELECT diff.%s, "			/* count column */
-								"(diff.%s OPERATOR(pg_catalog.=) mv.%s) AS for_dlt, "
+								"(diff.%s OPERATOR(pg_catalog.=) mv.%s AND %s) AS for_dlt, "
 								"mv.ctid "
+								"%s "				/* aggregate columns */
 						"FROM %s AS mv, %s AS diff "
 						"WHERE %s"					/* tuple matching condition */
 					"), updt AS ("			/* update a tuple if this is not to be deleted */
 						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.-) t.%s "
+											"%s"	/* SET clauses for aggregates */
 						"FROM t WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND NOT for_dlt "
+						"%s"						/* RETURNING clause for recalc infomation */
 					"), dlt AS ("			/* delete a tuple if this is to be deleted */
 						"DELETE FROM %s AS mv USING t "
 						"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt"
-					")",
+					") %s",							/* SELECT returning which tuples need to be recalculated */
 					count_colname,
-					count_colname, count_colname,
+					count_colname, count_colname, (agg_without_groupby ? "false" : "true"),
+					(aggs_list != NULL ? aggs_list->data : ""),
 					matviewname, deltaname_old,
 					match_cond,
 					matviewname, count_colname, count_colname, count_colname,
-					matviewname);
+					(aggs_set != NULL ? aggs_set->data : ""),
+					updt_returning,
+					matviewname,
+					select_for_recalc);
 
-	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_SELECT)
 		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+
+
+	/* Return tuples to be recalculated. */
+	if (minmax_list)
+	{
+		*tuptable_recalc = SPI_tuptable;
+		*num_recalc = SPI_processed;
+	}
+	else
+	{
+		*tuptable_recalc = NULL;
+		*num_recalc = 0;
+	}
 }
 
 /*
@@ -2166,10 +2692,15 @@ apply_old_delta(const char *matviewname, const char *deltaname_old,
  * matviewname.  This is used when counting is required, that is, the view
  * has aggregate or distinct. Also, when a table in EXISTS sub queries
  * is modified.
+ *
+ * If the view desn't have aggregates or has GROUP BY, this requires a keys
+ * list to identify a tuple in the view. If the view has aggregates, this
+ * requires strings representing SET clause for updating aggregate values.
  */
 static void
 apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
-				List *keys, StringInfo target_list, const char* count_colname)
+				List *keys, StringInfo aggs_set, StringInfo target_list,
+				const char* count_colname)
 {
 	StringInfoData	querybuf;
 	StringInfoData	returning_keys;
@@ -2200,6 +2731,7 @@ apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
 	appendStringInfo(&querybuf,
 					"WITH updt AS ("		/* update a tuple if this exists in the view */
 						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.+) diff.%s "
+											"%s "	/* SET clauses for aggregates */
 						"FROM %s AS diff "
 						"WHERE %s "					/* tuple matching condition */
 						"RETURNING %s"				/* returning keys of updated tuples */
@@ -2207,6 +2739,7 @@ apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
 						"SELECT %s FROM %s AS diff "
 						"WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE %s);",
 					matviewname, count_colname, count_colname, count_colname,
+					(aggs_set != NULL ? aggs_set->data : ""),
 					deltaname_new,
 					match_cond,
 					returning_keys.data,
@@ -2281,6 +2814,349 @@ get_matching_condition_string(List *keys)
 	return match_cond.data;
 }
 
+/*
+ * get_returning_string
+ *
+ * Build a string for RETURNING clause of UPDATE used in apply_old_delta_with_count.
+ * This clause returns ctid and a boolean value that indicates if we need to
+ * recalculate min or max value, for each updated row.
+ */
+static char *
+get_returning_string(List *minmax_list, List *is_min_list, List *keys)
+{
+	StringInfoData returning;
+	char		*recalc_cond;
+	ListCell	*lc;
+
+	Assert(minmax_list != NIL && is_min_list != NIL);
+	recalc_cond = get_minmax_recalc_condition_string(minmax_list, is_min_list);
+
+	initStringInfo(&returning);
+
+	appendStringInfo(&returning, "RETURNING mv.ctid AS tid, (%s) AS recalc", recalc_cond);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char *resname = NameStr(attr->attname);
+		appendStringInfo(&returning, ", %s", quote_qualified_identifier("mv", resname));
+	}
+
+	return returning.data;
+}
+
+/*
+ * get_minmax_recalc_condition_string
+ *
+ * Build a predicate string for checking if any min/max aggregate
+ * value needs to be recalculated.
+ */
+static char *
+get_minmax_recalc_condition_string(List *minmax_list, List *is_min_list)
+{
+	StringInfoData recalc_cond;
+	ListCell	*lc1, *lc2;
+
+	initStringInfo(&recalc_cond);
+
+	Assert (list_length(minmax_list) == list_length(is_min_list));
+
+	forboth (lc1, minmax_list, lc2, is_min_list)
+	{
+		char   *resname = (char *) lfirst(lc1);
+		bool	is_min = (bool) lfirst_int(lc2);
+		char   *op_str = (is_min ? ">=" : "<=");
+
+		appendStringInfo(&recalc_cond, "%s OPERATOR(pg_catalog.%s) %s",
+			quote_qualified_identifier("mv", resname),
+			op_str,
+			quote_qualified_identifier("t", resname)
+		);
+
+		if (lnext(minmax_list, lc1))
+			appendStringInfo(&recalc_cond, " OR ");
+	}
+
+	return recalc_cond.data;
+}
+
+/*
+ * get_select_for_recalc_string
+ *
+ * Build a query to return tid and keys of tuples which need
+ * recalculation. This is used as the result of the query
+ * built by apply_old_delta.
+ */
+static char *
+get_select_for_recalc_string(List *keys)
+{
+	StringInfoData qry;
+	ListCell	*lc;
+
+	initStringInfo(&qry);
+
+	appendStringInfo(&qry, "SELECT tid");
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		appendStringInfo(&qry, ", %s", NameStr(attr->attname));
+	}
+
+	appendStringInfo(&qry, " FROM updt WHERE recalc");
+
+	return qry.data;
+}
+
+/*
+ * recalc_and_set_values
+ *
+ * Recalculate tuples in a materialized from base tables and update these.
+ * The tuples which needs recalculation are specified by keys, and resnames
+ * of columns to be updated are specified by namelist. TIDs and key values
+ * are given by tuples in tuptable_recalc. Its first attribute must be TID
+ * and key values must be following this.
+ */
+static void
+recalc_and_set_values(SPITupleTable *tuptable_recalc, int64 num_tuples,
+					  List *namelist, List *keys, Relation matviewRel)
+{
+	TupleDesc   tupdesc_recalc = tuptable_recalc->tupdesc;
+	Oid		   *keyTypes = NULL, *types = NULL;
+	char	   *keyNulls = NULL, *nulls = NULL;
+	Datum	   *keyVals = NULL, *vals = NULL;
+	int			num_vals = list_length(namelist);
+	int			num_keys = list_length(keys);
+	uint64      i;
+	Oid			matviewOid;
+	char	   *matviewname;
+
+	matviewOid = RelationGetRelid(matviewRel);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	/* If we have keys, initialize arrays for them. */
+	if (keys)
+	{
+		keyTypes = palloc(sizeof(Oid) * num_keys);
+		keyNulls = palloc(sizeof(char) * num_keys);
+		keyVals = palloc(sizeof(Datum) * num_keys);
+		/* a tuple contains keys to be recalculated and ctid to be updated*/
+		Assert(tupdesc_recalc->natts == num_keys + 1);
+
+		/* Types of key attributes  */
+		for (i = 0; i < num_keys; i++)
+			keyTypes[i] = TupleDescAttr(tupdesc_recalc, i + 1)->atttypid;
+	}
+
+	/* allocate memory for all attribute names and tid */
+	types = palloc(sizeof(Oid) * (num_vals + 1));
+	nulls = palloc(sizeof(char) * (num_vals + 1));
+	vals = palloc(sizeof(Datum) * (num_vals + 1));
+
+	/* For each tuple which needs recalculation */
+	for (i = 0; i < num_tuples; i++)
+	{
+		int j;
+		bool isnull;
+		SPIPlanPtr plan;
+		SPITupleTable *tuptable_newvals;
+		TupleDesc   tupdesc_newvals;
+
+		/* Set group key values as parameters if needed. */
+		if (keys)
+		{
+			for (j = 0; j < num_keys; j++)
+			{
+				keyVals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, j + 2, &isnull);
+				if (isnull)
+					keyNulls[j] = 'n';
+				else
+					keyNulls[j] = ' ';
+			}
+		}
+
+		/*
+		 * Get recalculated values from base tables. The result must be
+		 * only one tuple thich contains the new values for specified keys.
+		 */
+		plan = get_plan_for_recalc(matviewOid, namelist, keys, keyTypes);
+		if (SPI_execute_plan(plan, keyVals, keyNulls, false, 0) != SPI_OK_SELECT)
+			elog(ERROR, "SPI_execute_plan");
+		if (SPI_processed != 1)
+			elog(ERROR, "SPI_execute_plan returned zero or more than one rows");
+
+		tuptable_newvals = SPI_tuptable;
+		tupdesc_newvals = tuptable_newvals->tupdesc;
+
+		Assert(tupdesc_newvals->natts == num_vals);
+
+		/* Set the new values as parameters */
+		for (j = 0; j < tupdesc_newvals->natts; j++)
+		{
+			if (i == 0)
+				types[j] = TupleDescAttr(tupdesc_newvals, j)->atttypid;
+
+			vals[j] = SPI_getbinval(tuptable_newvals->vals[0], tupdesc_newvals, j + 1, &isnull);
+			if (isnull)
+				nulls[j] = 'n';
+			else
+				nulls[j] = ' ';
+		}
+		/* Set TID of the view tuple to be updated as a parameter */
+		types[j] = TIDOID;
+		vals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, 1, &isnull);
+		nulls[j] = ' ';
+
+		/* Update the view tuple to the new values */
+		plan = get_plan_for_set_values(matviewOid, matviewname, namelist, types);
+		if (SPI_execute_plan(plan, vals, nulls, false, 0) != SPI_OK_UPDATE)
+			elog(ERROR, "SPI_execute_plan");
+	}
+}
+
+
+/*
+ * get_plan_for_recalc
+ *
+ * Create or fetch a plan for recalculating value in the view's target list
+ * from base tables using the definition query of materialized view specified
+ * by matviewOid. namelist is a list of resnames of values to be recalculated.
+ *
+ * keys is a list of keys to identify tuples to be recalculated if this is not
+ * empty. KeyTypes is an array of types of keys.
+ */
+static SPIPlanPtr
+get_plan_for_recalc(Oid matviewOid, List *namelist, List *keys, Oid *keyTypes)
+{
+	MV_QueryKey hash_key;
+	SPIPlanPtr	plan;
+
+	/* Fetch or prepare a saved plan for the recalculation */
+	mv_BuildQueryKey(&hash_key, matviewOid, MV_PLAN_RECALC);
+	if ((plan = mv_FetchPreparedPlan(&hash_key)) == NULL)
+	{
+		ListCell	   *lc;
+		StringInfoData	str;
+		char   *viewdef;
+
+		/* get view definition of matview */
+		viewdef = text_to_cstring((text *) DatumGetPointer(
+					DirectFunctionCall1(pg_get_viewdef, ObjectIdGetDatum(matviewOid))));
+		/* get rid of trailing semi-colon */
+		viewdef[strlen(viewdef)-1] = '\0';
+
+		/*
+		 * Build a query string for recalculating values. This is like
+		 *
+		 *  SELECT x1, x2, x3, ... FROM ( ... view definition query ...) mv
+		 *   WHERE (key1, key2, ...) = ($1, $2, ...);
+		 */
+
+		initStringInfo(&str);
+		appendStringInfo(&str, "SELECT ");
+		foreach (lc, namelist)
+		{
+			appendStringInfo(&str, "%s", (char *) lfirst(lc));
+			if (lnext(namelist, lc))
+				appendStringInfoString(&str, ", ");
+		}
+		appendStringInfo(&str, " FROM (%s) mv", viewdef);
+
+		if (keys)
+		{
+			int		i = 1;
+			char	paramname[16];
+
+			appendStringInfo(&str, " WHERE (");
+			foreach (lc, keys)
+			{
+				Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+				char   *resname = NameStr(attr->attname);
+				Oid		typid = attr->atttypid;
+
+				sprintf(paramname, "$%d", i);
+				appendStringInfo(&str, "(");
+				generate_equal(&str, typid, resname, paramname);
+				appendStringInfo(&str, " OR (%s IS NULL AND %s IS NULL))",
+								 resname, paramname);
+
+				if (lnext(keys, lc))
+					appendStringInfoString(&str, " AND ");
+				i++;
+			}
+			appendStringInfo(&str, ")");
+		}
+		else
+			keyTypes = NULL;
+
+		plan = SPI_prepare(str.data, list_length(keys), keyTypes);
+		if (plan == NULL)
+			elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), str.data);
+
+		SPI_keepplan(plan);
+		mv_HashPreparedPlan(&hash_key, plan);
+	}
+
+	return plan;
+}
+
+/*
+ * get_plan_for_set_values
+ *
+ * Create or fetch a plan for applying new values calculated by
+ * get_plan_for_recalc to a materialized view specified by matviewOid.
+ * matviewname is the name of the view.  namelist is a list of resnames
+ * of attributes to be updated, and valTypes is an array of types of the
+ * values.
+ */
+static SPIPlanPtr
+get_plan_for_set_values(Oid matviewOid, char *matviewname, List *namelist,
+						Oid *valTypes)
+{
+	MV_QueryKey	key;
+	SPIPlanPtr	plan;
+
+	/* Fetch or prepare a saved plan for the real check */
+	mv_BuildQueryKey(&key, matviewOid, MV_PLAN_SET_VALUE);
+	if ((plan = mv_FetchPreparedPlan(&key)) == NULL)
+	{
+		ListCell	  *lc;
+		StringInfoData str;
+		int		i;
+
+		/*
+		 * Build a query string for applying min/max values. This is like
+		 *
+		 *  UPDATE matviewname AS mv
+		 *   SET (x1, x2, x3, x4) = ($1, $2, $3, $4)
+		 *   WHERE ctid = $5;
+		 */
+
+		initStringInfo(&str);
+		appendStringInfo(&str, "UPDATE %s AS mv SET (", matviewname);
+		foreach (lc, namelist)
+		{
+			appendStringInfo(&str, "%s", (char *) lfirst(lc));
+			if (lnext(namelist, lc))
+				appendStringInfoString(&str, ", ");
+		}
+		appendStringInfo(&str, ") = ROW(");
+
+		for (i = 1; i <= list_length(namelist); i++)
+			appendStringInfo(&str, "%s$%d", (i==1 ? "" : ", "), i);
+
+		appendStringInfo(&str, ") WHERE ctid OPERATOR(pg_catalog.=) $%d", i);
+
+		plan = SPI_prepare(str.data, list_length(namelist) + 1, valTypes);
+		if (plan == NULL)
+			elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), str.data);
+
+		SPI_keepplan(plan);
+		mv_HashPreparedPlan(&key, plan);
+	}
+
+	return plan;
+}
+
 /*
  * generate_equals
  *
@@ -2314,6 +3190,13 @@ mv_InitHashTables(void)
 {
 	HASHCTL		ctl;
 
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(MV_QueryKey);
+	ctl.entrysize = sizeof(MV_QueryHashEntry);
+	mv_query_cache = hash_create("MV query cache",
+								 MV_INIT_QUERYHASHSIZE,
+								 &ctl, HASH_ELEM | HASH_BLOBS);
+
 	memset(&ctl, 0, sizeof(ctl));
 	ctl.keysize = sizeof(Oid);
 	ctl.entrysize = sizeof(MV_TriggerHashEntry);
@@ -2322,6 +3205,99 @@ mv_InitHashTables(void)
 								 &ctl, HASH_ELEM | HASH_BLOBS);
 }
 
+/*
+ * mv_FetchPreparedPlan
+ */
+static SPIPlanPtr
+mv_FetchPreparedPlan(MV_QueryKey *key)
+{
+	MV_QueryHashEntry *entry;
+	SPIPlanPtr	plan;
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_query_cache)
+		mv_InitHashTables();
+
+	/*
+	 * Lookup for the key
+	 */
+	entry = (MV_QueryHashEntry *) hash_search(mv_query_cache,
+											  (void *) key,
+											  HASH_FIND, NULL);
+	if (entry == NULL)
+		return NULL;
+
+	/*
+	 * Check whether the plan is still valid.  If it isn't, we don't want to
+	 * simply rely on plancache.c to regenerate it; rather we should start
+	 * from scratch and rebuild the query text too.  This is to cover cases
+	 * such as table/column renames.  We depend on the plancache machinery to
+	 * detect possible invalidations, though.
+	 *
+	 * CAUTION: this check is only trustworthy if the caller has already
+	 * locked both materialized views and base tables.
+	 */
+	plan = entry->plan;
+	if (plan && SPI_plan_is_valid(plan))
+		return plan;
+
+	/*
+	 * Otherwise we might as well flush the cached plan now, to free a little
+	 * memory space before we make a new one.
+	 */
+	entry->plan = NULL;
+	if (plan)
+		SPI_freeplan(plan);
+
+	return NULL;
+}
+
+/*
+ * mv_HashPreparedPlan
+ *
+ * Add another plan to our private SPI query plan hashtable.
+ */
+static void
+mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan)
+{
+	MV_QueryHashEntry *entry;
+	bool		found;
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_query_cache)
+		mv_InitHashTables();
+
+	/*
+	 * Add the new plan.  We might be overwriting an entry previously found
+	 * invalid by mv_FetchPreparedPlan.
+	 */
+	entry = (MV_QueryHashEntry *) hash_search(mv_query_cache,
+											  (void *) key,
+											  HASH_ENTER, &found);
+	Assert(!found || entry->plan == NULL);
+	entry->plan = plan;
+}
+
+/*
+ * mv_BuildQueryKey
+ *
+ * Construct a hashtable key for a prepared SPI plan for IVM.
+ */
+static void
+mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type)
+{
+	/*
+	 * We assume struct MV_QueryKey contains no padding bytes, else we'd need
+	 * to use memset to clear them.
+	 */
+	key->matview_id = matview_id;
+	key->query_type = query_type;
+}
+
 /*
  * AtAbort_IVM
  *
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index a57ce463e1..702b097079 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -29,6 +29,7 @@ extern ObjectAddress ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *st
 extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create);
 
 extern Query *rewriteQueryForIMMV(Query *query, List *colNames);
+extern void makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber *next_resno, List **aggs);
 
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
 
-- 
2.17.1

v24-0009-Add-regression-tests-for-Incremental-View-Mainte.patchtext/x-diff; name=v24-0009-Add-regression-tests-for-Incremental-View-Mainte.patchDownload
From 300e50eae24ce9800c2c0d8601c7204833d1dd49 Mon Sep 17 00:00:00 2001
From: Takuma Hoshiai <takuma.hoshiai@gmail.com>
Date: Wed, 10 Mar 2021 11:11:13 +0900
Subject: [PATCH v24 09/15] Add regression tests for Incremental View
 Maintenance

---
 .../regress/expected/incremental_matview.out  | 812 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/incremental_matview.sql  | 400 +++++++++
 3 files changed, 1213 insertions(+), 1 deletion(-)
 create mode 100644 src/test/regress/expected/incremental_matview.out
 create mode 100644 src/test/regress/sql/incremental_matview.sql

diff --git a/src/test/regress/expected/incremental_matview.out b/src/test/regress/expected/incremental_matview.out
new file mode 100644
index 0000000000..4b14100acd
--- /dev/null
+++ b/src/test/regress/expected/incremental_matview.out
@@ -0,0 +1,812 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) WITH NO DATA;
+NOTICE:  could not create an index on materialized view "mv_ivm_1" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ERROR:  materialized view "mv_ivm_1" has not been populated
+HINT:  Use the REFRESH MATERIALIZED VIEW command.
+REFRESH MATERIALIZED VIEW mv_ivm_1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- immediate maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- rename of IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_rename AS SELECT DISTINCT * FROM mv_base_a;
+NOTICE:  created index "mv_ivm_rename_index" on materialized view "mv_ivm_rename"
+ALTER MATERIALIZED VIEW mv_ivm_rename RENAME COLUMN __ivm_count__ TO xxx;
+ERROR:  IVM column can not be renamed
+DROP MATERIALIZED VIEW mv_ivm_rename;
+-- unique index on IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_unique AS SELECT DISTINCT * FROM mv_base_a;
+NOTICE:  created index "mv_ivm_unique_index" on materialized view "mv_ivm_unique"
+CREATE UNIQUE INDEX ON mv_ivm_unique(__ivm_count__);
+ERROR:  unique index creation on IVM columns is not supported
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__));
+ERROR:  unique index creation on IVM columns is not supported
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__ + 1));
+ERROR:  unique index creation on IVM columns is not supported
+DROP MATERIALIZED VIEW mv_ivm_unique;
+-- some query syntax
+BEGIN;
+CREATE FUNCTION ivm_func() RETURNS int LANGUAGE 'sql'
+       AS 'SELECT 1' IMMUTABLE;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_func AS SELECT * FROM ivm_func();
+NOTICE:  could not create an index on materialized view "mv_ivm_func" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_no_tbl AS SELECT 1;
+NOTICE:  could not create an index on materialized view "mv_ivm_no_tbl" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+ROLLBACK;
+-- result of materialized view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_duplicate" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+NOTICE:  created index "mv_ivm_distinct_index" on materialized view "mv_ivm_distinct"
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 20
+ 30
+ 40
+ 50
+(6 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+ROLLBACK;
+-- support SUM(), COUNT() and AVG() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i), AVG(j) FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_agg_index" on materialized view "mv_ivm_agg"
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 | 120 |     2 | 60.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg          
+---+-----+-------+----------------------
+ 1 |  10 |     1 |  10.0000000000000000
+ 2 | 220 |     2 | 110.0000000000000000
+ 3 |  30 |     1 |  30.0000000000000000
+ 4 |  40 |     1 |  40.0000000000000000
+ 5 |  50 |     1 |  50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support COUNT(*) aggregate function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(*) FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_agg_index" on materialized view "mv_ivm_agg"
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 |  20 |     1
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 | 120 |     2
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+ROLLBACK;
+-- support aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j), COUNT(j), AVG(j) FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_group" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count |         avg         
+-----+-------+---------------------
+ 150 |     5 | 30.0000000000000000
+(1 row)
+
+INSERT INTO mv_base_a VALUES(6,60);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count |         avg         
+-----+-------+---------------------
+ 210 |     6 | 35.0000000000000000
+(1 row)
+
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count | avg 
+-----+-------+-----
+     |     0 |    
+(1 row)
+
+ROLLBACK;
+-- resolved issue: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+NOTICE:  created index "mv_ivm_avg_bug_index" on materialized view "mv_ivm_avg_bug"
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     3 |  3.3333333333333333
+ 2 |  80 |     3 | 26.6666666666666667
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support MIN(), MAX() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT i, MIN(j), MAX(j)  FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_min_max_index" on materialized view "mv_ivm_min_max"
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  10 |  10
+ 2 |  20 |  20
+ 3 |  30 |  30
+ 4 |  40 |  40
+ 5 |  50 |  50
+(5 rows)
+
+INSERT INTO mv_base_a VALUES
+  (1,11), (1,12),
+  (2,21), (2,22),
+  (3,31), (3,32),
+  (4,41), (4,42),
+  (5,51), (5,52);
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  10 |  12
+ 2 |  20 |  22
+ 3 |  30 |  32
+ 4 |  40 |  42
+ 5 |  50 |  52
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  11 |  12
+ 2 |  20 |  22
+ 3 |  30 |  31
+ 4 |  40 |  42
+ 5 |  50 |  52
+(5 rows)
+
+ROLLBACK;
+-- support MIN(), MAX() aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT MIN(j), MAX(j)  FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_min_max" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+  10 |  50
+(1 row)
+
+INSERT INTO mv_base_a VALUES
+  (0,0), (6,60), (7,70);
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+   0 |  70
+(1 row)
+
+DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+  10 |  60
+(1 row)
+
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+     |    
+(1 row)
+
+ROLLBACK;
+-- support self join view and multiple change on the same table
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1, 10), (2, 20), (3, 30);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_self(v1, v2) AS
+ SELECT t1.v, t2.v FROM base_t AS t1 JOIN base_t AS t2 ON t1.i = t2.i;
+NOTICE:  could not create an index on materialized view "mv_self" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_self ORDER BY v1;
+ v1 | v2 
+----+----
+ 10 | 10
+ 20 | 20
+ 30 | 30
+(3 rows)
+
+INSERT INTO base_t VALUES (4,40);
+DELETE FROM base_t WHERE i = 1;
+UPDATE base_t SET v = v*10 WHERE i=2;
+SELECT * FROM mv_self ORDER BY v1;
+ v1  | v2  
+-----+-----
+  30 |  30
+  40 |  40
+ 200 | 200
+(3 rows)
+
+WITH
+ ins_t1 AS (INSERT INTO base_t VALUES (5,50) RETURNING 1),
+ ins_t2 AS (INSERT INTO base_t VALUES (6,60) RETURNING 1),
+ upd_t AS (UPDATE base_t SET v = v + 100  RETURNING 1),
+ dlt_t AS (DELETE FROM base_t WHERE i IN (4,5)  RETURNING 1)
+SELECT NULL;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT * FROM mv_self ORDER BY v1;
+ v1  | v2  
+-----+-----
+  50 |  50
+  60 |  60
+ 130 | 130
+ 300 | 300
+(4 rows)
+
+ROLLBACK;
+-- support simultaneous table changes
+BEGIN;
+CREATE TABLE base_r (i int, v int);
+CREATE TABLE base_s (i int, v int);
+INSERT INTO base_r VALUES (1, 10), (2, 20), (3, 30);
+INSERT INTO base_s VALUES (1, 100), (2, 200), (3, 300);
+CREATE INCREMENTAL MATERIALIZED VIEW mv(v1, v2) AS
+ SELECT r.v, s.v FROM base_r AS r JOIN base_s AS s USING(i);
+NOTICE:  could not create an index on materialized view "mv" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY v1;
+ v1 | v2  
+----+-----
+ 10 | 100
+ 20 | 200
+ 30 | 300
+(3 rows)
+
+WITH
+ ins_r AS (INSERT INTO base_r VALUES (1,11) RETURNING 1),
+ ins_r2 AS (INSERT INTO base_r VALUES (3,33) RETURNING 1),
+ ins_s AS (INSERT INTO base_s VALUES (2,222) RETURNING 1),
+ upd_r AS (UPDATE base_r SET v = v + 1000 WHERE i = 2 RETURNING 1),
+ dlt_s AS (DELETE FROM base_s WHERE i = 3 RETURNING 1)
+SELECT NULL;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT * FROM mv ORDER BY v1;
+  v1  | v2  
+------+-----
+   10 | 100
+   11 | 100
+ 1020 | 200
+ 1020 | 222
+(4 rows)
+
+ROLLBACK;
+-- support foreign reference constraints
+BEGIN;
+CREATE TABLE ri1 (i int PRIMARY KEY);
+CREATE TABLE ri2 (i int PRIMARY KEY REFERENCES ri1(i) ON UPDATE CASCADE ON DELETE CASCADE, v int);
+INSERT INTO ri1 VALUES (1),(2),(3);
+INSERT INTO ri2 VALUES (1),(2),(3);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ri(i1, i2) AS
+ SELECT ri1.i, ri2.i FROM ri1 JOIN ri2 USING(i);
+NOTICE:  created index "mv_ri_index" on materialized view "mv_ri"
+SELECT * FROM mv_ri ORDER BY i1;
+ i1 | i2 
+----+----
+  1 |  1
+  2 |  2
+  3 |  3
+(3 rows)
+
+UPDATE ri1 SET i=10 where i=1;
+DELETE FROM ri1 WHERE i=2;
+SELECT * FROM mv_ri ORDER BY i2;
+ i1 | i2 
+----+----
+  3 |  3
+ 10 | 10
+(2 rows)
+
+ROLLBACK;
+-- views including NULL
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1,10),(2, NULL);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+NOTICE:  could not create an index on materialized view "mv" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY i;
+ i | v  
+---+----
+ 1 | 10
+ 2 |   
+(2 rows)
+
+UPDATE base_t SET v = 20 WHERE i = 2;
+SELECT * FROM mv ORDER BY i;
+ i | v  
+---+----
+ 1 | 10
+ 2 | 20
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+NOTICE:  could not create an index on materialized view "mv" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY i;
+ i 
+---
+(0 rows)
+
+INSERT INTO base_t VALUES (1),(NULL);
+SELECT * FROM mv ORDER BY i;
+ i 
+---
+ 1
+  
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (1, 10), (1, 20);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, sum(v) FROM base_t GROUP BY i;
+NOTICE:  created index "mv_index" on materialized view "mv"
+SELECT * FROM mv ORDER BY i;
+ i | sum 
+---+-----
+ 1 |  30
+   |   3
+(2 rows)
+
+UPDATE base_t SET v = v * 10;
+SELECT * FROM mv ORDER BY i;
+ i | sum 
+---+-----
+ 1 | 300
+   |  30
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (NULL, 3), (NULL, 4), (NULL, 5);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, min(v), max(v) FROM base_t GROUP BY i;
+NOTICE:  created index "mv_index" on materialized view "mv"
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   1 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 1;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 3;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 5;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   4
+(1 row)
+
+ROLLBACK;
+-- IMMV containing user defined type
+BEGIN;
+CREATE TYPE mytype;
+CREATE FUNCTION mytype_in(cstring)
+ RETURNS mytype AS 'int4in'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+NOTICE:  return type mytype is only a shell
+CREATE FUNCTION mytype_out(mytype)
+ RETURNS cstring AS 'int4out'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+NOTICE:  argument type mytype is only a shell
+CREATE TYPE mytype (
+ LIKE = int4,
+ INPUT = mytype_in,
+ OUTPUT = mytype_out
+);
+CREATE FUNCTION mytype_eq(mytype, mytype)
+ RETURNS bool AS 'int4eq'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_lt(mytype, mytype)
+ RETURNS bool AS 'int4lt'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_cmp(mytype, mytype)
+ RETURNS integer AS 'btint4cmp'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE OPERATOR = (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_eq);
+CREATE OPERATOR < (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_lt);
+CREATE OPERATOR CLASS mytype_ops
+ DEFAULT FOR TYPE mytype USING btree AS
+ OPERATOR        1       <,
+ OPERATOR        3       = ,
+ FUNCTION		1		mytype_cmp(mytype,mytype);
+CREATE TABLE t_mytype (x mytype);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_mytype AS
+ SELECT * FROM t_mytype;
+NOTICE:  could not create an index on materialized view "mv_mytype" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+INSERT INTO t_mytype VALUES ('1'::mytype);
+SELECT * FROM mv_mytype;
+ x 
+---
+ 1
+(1 row)
+
+ROLLBACK;
+-- outer join is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i;
+ERROR:  OUTER JOIN is not supported on incrementally maintainable materialized view
+-- CTE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS
+    WITH b AS ( SELECT * FROM mv_base_b) SELECT a.i,a.j FROM mv_base_a a, b WHERE a.i = b.i;
+ERROR:  CTE is not supported on incrementally maintainable materialized view
+-- contain system column
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm01 AS SELECT i,j,xmin FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT i,j,xmin::text AS x_min FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm06 AS SELECT i,j,xidsend(xmin) AS x_min FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+-- contain subquery
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm03 AS SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT * FROM mv_base_b) b WHERE a.i = b.i;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm05 AS SELECT i,j, (SELECT k FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+-- contain ORDER BY
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm07 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) ORDER BY i,j,k;
+ERROR:  ORDER BY clause is not supported on incrementally maintainable materialized view
+-- contain HAVING
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm08 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) GROUP BY i,j,k HAVING SUM(i) > 5;
+ERROR:   HAVING clause is not supported on incrementally maintainable materialized view
+-- contain view or materialized view
+CREATE VIEW b_view AS SELECT i,k FROM mv_base_b;
+CREATE MATERIALIZED VIEW b_mview AS SELECT i,k FROM mv_base_b;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm07 AS SELECT a.i,a.j FROM mv_base_a a,b_view b WHERE a.i = b.i;
+ERROR:  VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm08 AS SELECT a.i,a.j FROM mv_base_a a,b_mview b WHERE a.i = b.i;
+ERROR:  VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm09 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm10 AS SELECT a.i,a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) OR a.i > 5;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+-- contain mutable functions
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+ERROR:  mutable function is not supported on incrementally maintainable materialized view
+HINT:  functions must be marked IMMUTABLE
+-- LIMIT/OFFSET is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm13 AS SELECT i,j FROM mv_base_a LIMIT 10 OFFSET 5;
+ERROR:  LIMIT/OFFSET clause is not supported on incrementally maintainable materialized view
+-- DISTINCT ON is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm14 AS SELECT DISTINCT ON(i) i, j FROM mv_base_a;
+ERROR:  DISTINCT ON is not supported on incrementally maintainable materialized view
+-- TABLESAMPLE clause is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm15 AS SELECT i, j FROM mv_base_a TABLESAMPLE SYSTEM(50);
+ERROR:  TABLESAMPLE clause is not supported on incrementally maintainable materialized view
+-- window functions are not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm16 AS SELECT *, cume_dist() OVER (ORDER BY i) AS rank FROM mv_base_a;
+ERROR:  window functions are not supported on incrementally maintainable materialized view
+-- aggregate function with some options is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm17 AS SELECT COUNT(*) FILTER(WHERE i < 3) FROM mv_base_a;
+ERROR:  aggregate function with FILTER clause is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm18 AS SELECT COUNT(DISTINCT i)  FROM mv_base_a;
+ERROR:  aggregate function with DISTINCT arguments is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm19 AS SELECT array_agg(j ORDER BY i DESC) FROM mv_base_a;
+ERROR:  aggregate function with ORDER clause is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm20 AS SELECT i,SUM(j) FROM mv_base_a GROUP BY GROUPING SETS((i),());
+ERROR:  GROUPING SETS, ROLLUP, or CUBE clauses is not supported on incrementally maintainable materialized view
+-- inheritance parent is not supported
+BEGIN;
+CREATE TABLE parent (i int, v int);
+CREATE TABLE child_a(options text) INHERITS(parent);
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm21 AS SELECT * FROM parent;
+ERROR:  inheritance parent is not supported on incrementally maintainable materialized view
+ROLLBACK;
+-- UNION statement is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm22 AS SELECT i,j FROM mv_base_a UNION ALL SELECT i,k FROM mv_base_b;;
+ERROR:  UNION/INTERSECT/EXCEPT statements are not supported on incrementally maintainable materialized view
+-- empty target list is not allowed with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm25 AS SELECT FROM mv_base_a;
+ERROR:  empty target list is not supported on incrementally maintainable materialized view
+-- FOR UPDATE/SHARE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm26 AS SELECT i,j FROM mv_base_a FOR UPDATE;
+ERROR:  FOR UPDATE/SHARE clause is not supported on incrementally maintainable materialized view
+-- tartget list cannot contain ivm column that start with '__ivm'
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm28 AS SELECT i AS "__ivm_count__" FROM mv_base_a;
+ERROR:  column name __ivm_count__ is not supported on incrementally maintainable materialized view
+-- expressions specified in GROUP BY must appear in the target list.
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm29 AS SELECT COUNT(i) FROM mv_base_a GROUP BY i;
+ERROR:  GROUP BY expression not appearing in select list is not supported on incrementally maintainable materialized view
+-- experssions containing an aggregate is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm30 AS SELECT sum(i)*0.5 FROM mv_base_a;
+ERROR:  expression containing an aggregate in it is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm31 AS SELECT sum(i)/sum(j) FROM mv_base_a;
+ERROR:  expression containing an aggregate in it is not supported on incrementally maintainable materialized view
+-- VALUES is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_only_values1 AS values(1);
+ERROR:  VALUES is not supported on incrementally maintainable materialized view
+-- base table which has row level security
+DROP USER IF EXISTS ivm_admin;
+NOTICE:  role "ivm_admin" does not exist, skipping
+DROP USER IF EXISTS ivm_user;
+NOTICE:  role "ivm_user" does not exist, skipping
+CREATE USER ivm_admin;
+CREATE USER ivm_user;
+SET SESSION AUTHORIZATION ivm_admin;
+CREATE TABLE rls_tbl(id int, data text, owner name);
+INSERT INTO rls_tbl VALUES
+  (1,'foo','ivm_user'),
+  (2,'bar','postgres');
+CREATE TABLE num_tbl(id int, num text);
+INSERT INTO num_tbl VALUES
+  (1,'one'),
+  (2,'two'),
+  (3,'three'),
+  (4,'four');
+CREATE POLICY rls_tbl_policy ON rls_tbl FOR SELECT TO PUBLIC USING(owner = current_user);
+CREATE POLICY rls_tbl_policy2 ON rls_tbl FOR INSERT TO PUBLIC WITH CHECK(current_user LIKE 'ivm_%');
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+GRANT ALL on rls_tbl TO PUBLIC;
+GRANT ALL on num_tbl TO PUBLIC;
+SET SESSION AUTHORIZATION ivm_user;
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls AS SELECT * FROM rls_tbl;
+NOTICE:  could not create an index on materialized view "ivm_rls" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+ id | data |  owner   
+----+------+----------
+  1 | foo  | ivm_user
+(1 row)
+
+INSERT INTO rls_tbl VALUES
+  (3,'baz','ivm_user'),
+  (4,'qux','postgres');
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+ id | data |  owner   
+----+------+----------
+  1 | foo  | ivm_user
+  3 | baz  | ivm_user
+(2 rows)
+
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls2 AS SELECT * FROM rls_tbl JOIN num_tbl USING(id);
+NOTICE:  could not create an index on materialized view "ivm_rls2" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+RESET SESSION AUTHORIZATION;
+WITH
+ x AS (UPDATE rls_tbl SET data = data || '_2' where id in (3,4)),
+ y AS (UPDATE num_tbl SET num = num || '_2' where id in (3,4))
+SELECT;
+--
+(1 row)
+
+SELECT * FROM ivm_rls2 ORDER BY 1,2,3;
+ id | data  |  owner   |   num   
+----+-------+----------+---------
+  1 | foo   | ivm_user | one
+  3 | baz_2 | ivm_user | three_2
+(2 rows)
+
+DROP TABLE rls_tbl CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to materialized view ivm_rls
+drop cascades to materialized view ivm_rls2
+DROP TABLE num_tbl CASCADE;
+DROP USER ivm_user;
+DROP USER ivm_admin;
+DROP TABLE mv_base_b CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to materialized view mv_ivm_1
+drop cascades to view b_view
+drop cascades to materialized view b_mview
+DROP TABLE mv_base_a CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0..b0350f47dd 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -86,7 +86,7 @@ test: brin_bloom brin_multi
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort incremental_matview
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/sql/incremental_matview.sql b/src/test/regress/sql/incremental_matview.sql
new file mode 100644
index 0000000000..311e9b96fb
--- /dev/null
+++ b/src/test/regress/sql/incremental_matview.sql
@@ -0,0 +1,400 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) WITH NO DATA;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+REFRESH MATERIALIZED VIEW mv_ivm_1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+-- immediate maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+
+-- rename of IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_rename AS SELECT DISTINCT * FROM mv_base_a;
+ALTER MATERIALIZED VIEW mv_ivm_rename RENAME COLUMN __ivm_count__ TO xxx;
+DROP MATERIALIZED VIEW mv_ivm_rename;
+
+-- unique index on IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_unique AS SELECT DISTINCT * FROM mv_base_a;
+CREATE UNIQUE INDEX ON mv_ivm_unique(__ivm_count__);
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__));
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__ + 1));
+DROP MATERIALIZED VIEW mv_ivm_unique;
+
+-- some query syntax
+BEGIN;
+CREATE FUNCTION ivm_func() RETURNS int LANGUAGE 'sql'
+       AS 'SELECT 1' IMMUTABLE;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_func AS SELECT * FROM ivm_func();
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_no_tbl AS SELECT 1;
+ROLLBACK;
+
+-- result of materialized view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ROLLBACK;
+
+-- support SUM(), COUNT() and AVG() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i), AVG(j) FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ROLLBACK;
+
+-- support COUNT(*) aggregate function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(*) FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j), COUNT(j), AVG(j) FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+INSERT INTO mv_base_a VALUES(6,60);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ROLLBACK;
+
+-- resolved issue: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support MIN(), MAX() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT i, MIN(j), MAX(j)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES
+  (1,11), (1,12),
+  (2,21), (2,22),
+  (3,31), (3,32),
+  (4,41), (4,42),
+  (5,51), (5,52);
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support MIN(), MAX() aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT MIN(j), MAX(j)  FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+INSERT INTO mv_base_a VALUES
+  (0,0), (6,60), (7,70);
+SELECT * FROM mv_ivm_min_max;
+DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
+SELECT * FROM mv_ivm_min_max;
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+ROLLBACK;
+
+-- support self join view and multiple change on the same table
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1, 10), (2, 20), (3, 30);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_self(v1, v2) AS
+ SELECT t1.v, t2.v FROM base_t AS t1 JOIN base_t AS t2 ON t1.i = t2.i;
+SELECT * FROM mv_self ORDER BY v1;
+INSERT INTO base_t VALUES (4,40);
+DELETE FROM base_t WHERE i = 1;
+UPDATE base_t SET v = v*10 WHERE i=2;
+SELECT * FROM mv_self ORDER BY v1;
+WITH
+ ins_t1 AS (INSERT INTO base_t VALUES (5,50) RETURNING 1),
+ ins_t2 AS (INSERT INTO base_t VALUES (6,60) RETURNING 1),
+ upd_t AS (UPDATE base_t SET v = v + 100  RETURNING 1),
+ dlt_t AS (DELETE FROM base_t WHERE i IN (4,5)  RETURNING 1)
+SELECT NULL;
+SELECT * FROM mv_self ORDER BY v1;
+ROLLBACK;
+
+-- support simultaneous table changes
+BEGIN;
+CREATE TABLE base_r (i int, v int);
+CREATE TABLE base_s (i int, v int);
+INSERT INTO base_r VALUES (1, 10), (2, 20), (3, 30);
+INSERT INTO base_s VALUES (1, 100), (2, 200), (3, 300);
+CREATE INCREMENTAL MATERIALIZED VIEW mv(v1, v2) AS
+ SELECT r.v, s.v FROM base_r AS r JOIN base_s AS s USING(i);
+SELECT * FROM mv ORDER BY v1;
+WITH
+ ins_r AS (INSERT INTO base_r VALUES (1,11) RETURNING 1),
+ ins_r2 AS (INSERT INTO base_r VALUES (3,33) RETURNING 1),
+ ins_s AS (INSERT INTO base_s VALUES (2,222) RETURNING 1),
+ upd_r AS (UPDATE base_r SET v = v + 1000 WHERE i = 2 RETURNING 1),
+ dlt_s AS (DELETE FROM base_s WHERE i = 3 RETURNING 1)
+SELECT NULL;
+SELECT * FROM mv ORDER BY v1;
+ROLLBACK;
+
+-- support foreign reference constraints
+BEGIN;
+CREATE TABLE ri1 (i int PRIMARY KEY);
+CREATE TABLE ri2 (i int PRIMARY KEY REFERENCES ri1(i) ON UPDATE CASCADE ON DELETE CASCADE, v int);
+INSERT INTO ri1 VALUES (1),(2),(3);
+INSERT INTO ri2 VALUES (1),(2),(3);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ri(i1, i2) AS
+ SELECT ri1.i, ri2.i FROM ri1 JOIN ri2 USING(i);
+SELECT * FROM mv_ri ORDER BY i1;
+UPDATE ri1 SET i=10 where i=1;
+DELETE FROM ri1 WHERE i=2;
+SELECT * FROM mv_ri ORDER BY i2;
+ROLLBACK;
+
+-- views including NULL
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1,10),(2, NULL);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+SELECT * FROM mv ORDER BY i;
+UPDATE base_t SET v = 20 WHERE i = 2;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+SELECT * FROM mv ORDER BY i;
+INSERT INTO base_t VALUES (1),(NULL);
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (1, 10), (1, 20);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, sum(v) FROM base_t GROUP BY i;
+SELECT * FROM mv ORDER BY i;
+UPDATE base_t SET v = v * 10;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (NULL, 3), (NULL, 4), (NULL, 5);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, min(v), max(v) FROM base_t GROUP BY i;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 1;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 3;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 5;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+-- IMMV containing user defined type
+BEGIN;
+
+CREATE TYPE mytype;
+CREATE FUNCTION mytype_in(cstring)
+ RETURNS mytype AS 'int4in'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_out(mytype)
+ RETURNS cstring AS 'int4out'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE TYPE mytype (
+ LIKE = int4,
+ INPUT = mytype_in,
+ OUTPUT = mytype_out
+);
+
+CREATE FUNCTION mytype_eq(mytype, mytype)
+ RETURNS bool AS 'int4eq'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_lt(mytype, mytype)
+ RETURNS bool AS 'int4lt'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_cmp(mytype, mytype)
+ RETURNS integer AS 'btint4cmp'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+
+CREATE OPERATOR = (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_eq);
+CREATE OPERATOR < (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_lt);
+
+CREATE OPERATOR CLASS mytype_ops
+ DEFAULT FOR TYPE mytype USING btree AS
+ OPERATOR        1       <,
+ OPERATOR        3       = ,
+ FUNCTION		1		mytype_cmp(mytype,mytype);
+
+CREATE TABLE t_mytype (x mytype);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_mytype AS
+ SELECT * FROM t_mytype;
+INSERT INTO t_mytype VALUES ('1'::mytype);
+SELECT * FROM mv_mytype;
+
+ROLLBACK;
+
+-- outer join is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i;
+-- CTE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS
+    WITH b AS ( SELECT * FROM mv_base_b) SELECT a.i,a.j FROM mv_base_a a, b WHERE a.i = b.i;
+-- contain system column
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm01 AS SELECT i,j,xmin FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT i,j,xmin::text AS x_min FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm06 AS SELECT i,j,xidsend(xmin) AS x_min FROM mv_base_a;
+-- contain subquery
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm03 AS SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT * FROM mv_base_b) b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm05 AS SELECT i,j, (SELECT k FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+-- contain ORDER BY
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm07 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) ORDER BY i,j,k;
+-- contain HAVING
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm08 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) GROUP BY i,j,k HAVING SUM(i) > 5;
+
+-- contain view or materialized view
+CREATE VIEW b_view AS SELECT i,k FROM mv_base_b;
+CREATE MATERIALIZED VIEW b_mview AS SELECT i,k FROM mv_base_b;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm07 AS SELECT a.i,a.j FROM mv_base_a a,b_view b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm08 AS SELECT a.i,a.j FROM mv_base_a a,b_mview b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm09 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm10 AS SELECT a.i,a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) OR a.i > 5;
+
+-- contain mutable functions
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+
+-- LIMIT/OFFSET is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm13 AS SELECT i,j FROM mv_base_a LIMIT 10 OFFSET 5;
+
+-- DISTINCT ON is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm14 AS SELECT DISTINCT ON(i) i, j FROM mv_base_a;
+
+-- TABLESAMPLE clause is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm15 AS SELECT i, j FROM mv_base_a TABLESAMPLE SYSTEM(50);
+
+-- window functions are not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm16 AS SELECT *, cume_dist() OVER (ORDER BY i) AS rank FROM mv_base_a;
+
+-- aggregate function with some options is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm17 AS SELECT COUNT(*) FILTER(WHERE i < 3) FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm18 AS SELECT COUNT(DISTINCT i)  FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm19 AS SELECT array_agg(j ORDER BY i DESC) FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm20 AS SELECT i,SUM(j) FROM mv_base_a GROUP BY GROUPING SETS((i),());
+
+-- inheritance parent is not supported
+BEGIN;
+CREATE TABLE parent (i int, v int);
+CREATE TABLE child_a(options text) INHERITS(parent);
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm21 AS SELECT * FROM parent;
+ROLLBACK;
+
+-- UNION statement is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm22 AS SELECT i,j FROM mv_base_a UNION ALL SELECT i,k FROM mv_base_b;;
+
+-- empty target list is not allowed with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm25 AS SELECT FROM mv_base_a;
+
+-- FOR UPDATE/SHARE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm26 AS SELECT i,j FROM mv_base_a FOR UPDATE;
+
+-- tartget list cannot contain ivm column that start with '__ivm'
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm28 AS SELECT i AS "__ivm_count__" FROM mv_base_a;
+
+-- expressions specified in GROUP BY must appear in the target list.
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm29 AS SELECT COUNT(i) FROM mv_base_a GROUP BY i;
+
+-- experssions containing an aggregate is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm30 AS SELECT sum(i)*0.5 FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm31 AS SELECT sum(i)/sum(j) FROM mv_base_a;
+
+-- VALUES is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_only_values1 AS values(1);
+
+-- base table which has row level security
+DROP USER IF EXISTS ivm_admin;
+DROP USER IF EXISTS ivm_user;
+CREATE USER ivm_admin;
+CREATE USER ivm_user;
+SET SESSION AUTHORIZATION ivm_admin;
+
+CREATE TABLE rls_tbl(id int, data text, owner name);
+INSERT INTO rls_tbl VALUES
+  (1,'foo','ivm_user'),
+  (2,'bar','postgres');
+CREATE TABLE num_tbl(id int, num text);
+INSERT INTO num_tbl VALUES
+  (1,'one'),
+  (2,'two'),
+  (3,'three'),
+  (4,'four');
+CREATE POLICY rls_tbl_policy ON rls_tbl FOR SELECT TO PUBLIC USING(owner = current_user);
+CREATE POLICY rls_tbl_policy2 ON rls_tbl FOR INSERT TO PUBLIC WITH CHECK(current_user LIKE 'ivm_%');
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+GRANT ALL on rls_tbl TO PUBLIC;
+GRANT ALL on num_tbl TO PUBLIC;
+
+SET SESSION AUTHORIZATION ivm_user;
+
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls AS SELECT * FROM rls_tbl;
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+INSERT INTO rls_tbl VALUES
+  (3,'baz','ivm_user'),
+  (4,'qux','postgres');
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls2 AS SELECT * FROM rls_tbl JOIN num_tbl USING(id);
+
+RESET SESSION AUTHORIZATION;
+
+WITH
+ x AS (UPDATE rls_tbl SET data = data || '_2' where id in (3,4)),
+ y AS (UPDATE num_tbl SET num = num || '_2' where id in (3,4))
+SELECT;
+SELECT * FROM ivm_rls2 ORDER BY 1,2,3;
+
+DROP TABLE rls_tbl CASCADE;
+DROP TABLE num_tbl CASCADE;
+DROP USER ivm_user;
+DROP USER ivm_admin;
+
+DROP TABLE mv_base_b CASCADE;
+DROP TABLE mv_base_a CASCADE;
-- 
2.17.1

v24-0010-Add-documentations-about-Incremental-View-Mainte.patchtext/x-diff; name=v24-0010-Add-documentations-about-Incremental-View-Mainte.patchDownload
From 2a74369276e799a0e093cd9e1f24504b0b545a33 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:25:34 +0900
Subject: [PATCH v24 10/15] Add documentations about Incremental View
 Maintenance

---
 doc/src/sgml/catalogs.sgml                    |  24 +-
 .../sgml/ref/create_materialized_view.sgml    | 131 +++++-
 .../sgml/ref/refresh_materialized_view.sgml   |   6 +-
 doc/src/sgml/rules.sgml                       | 443 ++++++++++++++++++
 4 files changed, 599 insertions(+), 5 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 00b648a433..68a1377b6f 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2188,6 +2188,15 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>relisivm</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if materialized view enables incremental view maintenance
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>relrewrite</structfield> <type>oid</type>
@@ -3472,6 +3481,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para>
      </listitem>
     </varlistentry>
+
+    <varlistentry>
+     <term><symbol>DEPENDENCY_IMMV</symbol> (<literal>m</literal>)</term>
+     <listitem>
+      <para>
+       The dependent object was created as part of creation of the Materialized
+       View with Incremental View Maintenance reference, and is really just a 
+       part of its internal implementation. The dependent object must not be
+       dropped unless the materialized view is also dropped.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
 
    Other dependency flavors might be needed in future.
@@ -3855,7 +3876,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <structfield>extrelocatable</structfield> <type>bool</type>
       </para>
       <para>
-       True if extension can be relocated to another schema
+      True for materialized views which are enabled for incremental
+      view maintenance (IVM).
       </para></entry>
      </row>
 
diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml
index d8c48252f4..8a440ca810 100644
--- a/doc/src/sgml/ref/create_materialized_view.sgml
+++ b/doc/src/sgml/ref/create_materialized_view.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
+CREATE [ INCREMENTAL ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
     [ (<replaceable>column_name</replaceable> [, ...] ) ]
     [ USING <replaceable class="parameter">method</replaceable> ]
     [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
@@ -60,6 +60,132 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
   <title>Parameters</title>
 
   <variablelist>
+   <varlistentry>
+    <term><literal>INCREMENTAL</literal></term>
+    <listitem>
+     <para>
+      If specified, some triggers are automatically created so that the rows
+      of the materialized view are immediately updated when base tables of the
+      materialized view are updated. In general, this allows faster update of
+      the materialized view at a price of slower update of the base tables
+      because the triggers will be invoked. We call this form of materialized
+      view as "Incrementally Maintainable Materialized View" (IMMV).
+     </para>
+     <para>
+      When <acronym>IMMV</acronym> is defined, a unique index is created on the view
+      automatically if possible.  If the view definition query has a GROUP BY clause,
+      a unique index is created on the columns of GROUP BY expressions.  Also, if the
+      view has DISTINCT clause, a unique index is created on all columns in the target
+      list.  Otherwise, if the view contains all primary key attritubes of its base
+      tables in the target list, a unique index is created on these attritubes.  In
+      other cases, no index is created.
+     </para>
+     <para>
+      There are restrictions of query definitions allowed to use this
+      option. The following are supported in query definitions for IMMV:
+      <itemizedlist>
+
+       <listitem>
+        <para>
+         Inner joins (including self-joins).
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Some built-in aggregate functions (count, sum, avg, min, max) without a HAVING
+         clause. 
+        </para>
+        </listitem>
+      </itemizedlist>
+
+      Unsupported queries with this option include the following:
+
+      <itemizedlist>
+       <listitem>
+        <para>
+         Outer joins.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Sub-queries.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Aggregate functions other than built-in count, sum, avg, min and max.
+        </para>
+       </listitem>
+       <listitem>
+        <para>
+         Aggregate functions with a HAVING clause.
+        </para>
+       </listitem>
+       <listitem>
+        <para>
+         DISTINCT ON, WINDOW, VALUES, LIMIT and OFFSET clause.
+        </para>
+       </listitem>
+      </itemizedlist>
+
+      Other restrictions include:
+      <itemizedlist>
+
+       <listitem>
+        <para>
+         IMMVs must be based on simple base tables. It's not supported to
+         create them on top of views or materialized views.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         When the TRUNCATE command is executed on a base table,
+         no changes are made to the materialized view.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         It is not supported to include system columns in an IMMV.
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+ERROR:  system column is not supported with IVM
+         </programlisting>
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Non-immutable functions are not supported.
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+ERROR:  functions in IMMV must be marked IMMUTABLE
+         </programlisting>
+        </para>
+        </listitem>
+
+       <listitem>
+        <para>
+         IMMVs do not support expressions that contains aggregates
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Logical replication does not support IMMVs.
+        </para>
+       </listitem>
+
+      </itemizedlist>
+
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>IF NOT EXISTS</literal></term>
     <listitem>
@@ -153,7 +279,8 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
       This clause specifies whether or not the materialized view should be
       populated at creation time.  If not, the materialized view will be
       flagged as unscannable and cannot be queried until <command>REFRESH
-      MATERIALIZED VIEW</command> is used.
+      MATERIALIZED VIEW</command> is used.  Also, if the view is IMMV,
+      triggers for maintaining the view are not created.
      </para>
     </listitem>
    </varlistentry>
diff --git a/doc/src/sgml/ref/refresh_materialized_view.sgml b/doc/src/sgml/ref/refresh_materialized_view.sgml
index 3bf8884447..dc81853057 100644
--- a/doc/src/sgml/ref/refresh_materialized_view.sgml
+++ b/doc/src/sgml/ref/refresh_materialized_view.sgml
@@ -35,9 +35,11 @@ REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] <replaceable class="parameter">name</
    owner of the materialized view.  The old contents are discarded.  If
    <literal>WITH DATA</literal> is specified (or defaults) the backing query
    is executed to provide the new data, and the materialized view is left in a
-   scannable state.  If <literal>WITH NO DATA</literal> is specified no new
+   scannable state.  Also, if the view is an incrementally maintainable materialized
+   view (IMMV) and was unpopulated, triggers for maintaining
+   the view are created.  If <literal>WITH NO DATA</literal> is specified no new
    data is generated and the materialized view is left in an unscannable
-   state.
+   state.  If the view is IMMV, the triggers are dropped.
   </para>
   <para>
    <literal>CONCURRENTLY</literal> and <literal>WITH NO DATA</literal> may not
diff --git a/doc/src/sgml/rules.sgml b/doc/src/sgml/rules.sgml
index 6065b1c2a3..264fdefc37 100644
--- a/doc/src/sgml/rules.sgml
+++ b/doc/src/sgml/rules.sgml
@@ -1093,6 +1093,449 @@ SELECT word FROM words ORDER BY word &lt;-&gt; 'caterpiler' LIMIT 10;
 
 </sect1>
 
+<sect1 id="rules-ivm">
+<title>Incremental View Maintenance</title>
+
+<indexterm zone="rules-ivm">
+ <primary>incremental view maintenance</primary>
+</indexterm>
+
+<indexterm zone="rules-ivm">
+ <primary>materialized view</primary>
+ <secondary>incremental view maintenance</secondary>
+</indexterm>
+
+<indexterm zone="rules-ivm">
+ <primary>view</primary>
+ <secondary>incremental view maintenance</secondary>
+</indexterm>
+
+<sect2 id="rules-ivm-overview">
+<title>Overview</title>
+
+<para>
+    Incremental View Maintenance (<acronym>IVM</acronym>) is a way to make
+    materialized views up-to-date in which only incremental changes are computed
+    and applied on views rather than recomputing the contents from scratch as
+    <command>REFRESH MATERIALIZED VIEW</command> does.  <acronym>IVM</acronym>
+    can update materialized views more efficiently than recomputation when only
+    small parts of the view are changed.
+</para>
+
+<para>
+    There are two approaches with regard to timing of view maintenance:
+    immediate and deferred.  In immediate maintenance, views are updated in the
+    same transaction that its base table is modified.  In deferred maintenance,
+    views are updated after the transaction is committed, for example, when the
+    view is accessed, as a response to user command like <command>REFRESH
+    MATERIALIZED VIEW</command>, or periodically in background, and so on.
+    <productname>PostgreSQL</productname> currently implements only a kind of
+    immediate maintenance, in which materialized views are updated immediately
+    in AFTER triggers when a base table is modified.
+</para>
+
+<para>
+    To create materialized views supporting <acronym>IVM</acronym>, use the
+    <command>CREATE INCREMENTAL MATERIALIZED VIEW</command>, for example:
+<programlisting>
+CREATE <emphasis>INCREMENTAL</emphasis> MATERIALIZED VIEW mymatview AS SELECT * FROM mytab;
+</programlisting>
+    When a materialized view is created with the <literal>INCREMENTAL</literal>
+    keyword, some triggers are automatically created so that the view's contents are
+    immediately updated when its base tables are modified. We call this form
+    of materialized view an Incrementally Maintainable Materialized View
+    (<acronym>IMMV</acronym>).
+<programlisting>
+postgres=# CREATE INCREMENTAL MATERIALIZED VIEW m AS SELECT * FROM t0;
+NOTICE:  could not create an index on materialized view "m" automatically
+HINT:  Create an index on the materialized view for effcient incremental maintenance.
+SELECT 3
+postgres=# SELECT * FROM m;
+ i
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+postgres=# INSERT INTO t0 VALUES (4);
+INSERT 0 1
+postgres=# SELECT * FROM m; -- automatically updated
+ i
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+</programlisting>
+</para>
+
+<para>
+    Some <acronym>IMMV</acronym>s have hidden columns which are added
+    automatically when a materialized view is created. Their name starts
+    with <literal>__ivm_</literal> and they contain information required
+    for maintaining the <acronym>IMMV</acronym>. Such columns are not visible
+    when the <acronym>IMMV</acronym> is accessed by <literal>SELECT *</literal>
+    but are visible if the column name is explicitly specified in the target
+    list. We can also see the hidden columns in <literal>\d</literal>
+    meta-commands of <command>psql</command> commands.
+</para>
+
+<para>
+    In general, <acronym>IMMV</acronym>s allow faster updates of materialized
+    views at the price of slower updates to their base tables. Updates of
+    <acronym>IMMV</acronym> is slower because triggers will be invoked and the
+    view is updated in triggers per modification statement.
+</para>
+
+<para>
+    For example, suppose a normal materialized view defined as below:
+
+<programlisting>
+test=# CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm AS
+        SELECT a.aid, b.bid, a.abalance, b.bbalance
+        FROM pgbench_accounts a JOIN pgbench_branches b USING(bid);
+SELECT 10000000
+
+</programlisting>
+
+    Updating a tuple in a base table of this materialized view is rapid but the
+   <command>REFRESH MATERIALIZED VIEW</command> command on this view takes a long time:
+
+<programlisting>
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 0.990 ms
+
+test=# REFRESH MATERIALIZED VIEW mv_normal ;
+REFRESH MATERIALIZED VIEW
+Time: 33533.952 ms (00:33.534)
+</programlisting>
+</para>
+
+<para>
+    On the other hand, after creating <acronym>IMMV</acronym> with the same view
+    definition as below:
+
+<programlisting>
+test=# CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm AS
+        SELECT a.aid, b.bid, a.abalance, b.bbalance
+        FROM pgbench_accounts a JOIN pgbench_branches b USING(bid);
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+NOTICE:  created index "mv_ivm_index" on materialized view "mv_ivm"
+</programlisting>
+
+    updating a tuple in a base table takes more than the normal view,
+    but its content is updated automatically and this is faster than the
+    <command>REFRESH MATERIALIZED VIEW</command> command.
+
+<programlisting>
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 13.068 ms
+</programlisting>
+
+</para>
+
+<para>
+    Appropriate indexes on <acronym>IMMV</acronym>s are necessary for
+    efficient <acronym>IVM</acronym> because it looks for tuples to be
+    updated in <acronym>IMMV</acronym>.  If there are no indexes, it
+    will take a long time.
+</para>
+
+<para>
+    Therefore, when <acronym>IMMV</acronym> is defined, a unique index is created on the view
+    automatically if possible.  If the view definition query has a GROUP BY clause, a unique
+    index is created on the columns of GROUP BY expressions.  Also, if the view has DISTINCT
+    clause, a unique index is created on all columns in the target list. Otherwise, if the
+    view contains all primary key attritubes of its base tables in the target list, a unique
+    index is created on these attritubes.  In other cases, no index is created.
+</para>
+
+<para>
+    In the previous example, a unique index "mv_ivm_index" is created on aid and bid
+    columns of materialized view "mv_ivm", and this enables the rapid update of the view.
+    Dropping this index make updating the view take a loger time.
+<programlisting>
+test=# DROP INDEX mv_ivm_index;
+DROP INDEX
+Time: 67.081 ms
+
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 16386.245 ms (00:16.386)
+</programlisting>
+
+</para>
+
+<para>
+    <acronym>IVM</acronym> is effective when we want to keep a materialized
+    view up-to-date and small fraction of a base table is modified
+    infrequently.  Due to the overhead of immediate maintenance, <acronym>IVM</acronym>
+    is not effective when a base table is modified frequently.  Also, when a
+    large part of a base table is modified or large data is inserted into a
+    base table, <acronym>IVM</acronym> is not effective and the cost of
+    maintenance can be larger than the <command>REFRESH MATERIALIZED VIEW</command>
+    command. In such situation, we can use <command>REFRESH MATERIALIZED VIEW</command>
+    and specify <literal>WITH NO DATA</literal> to disable immediate
+    maintenance before modifying a base table. After a base table modification,
+    execute the <command>REFRESH MATERIALIZED VIEW</command> (with <literal>WITH DATA</literal>)
+    command to refresh the view data and enable immediate maintenance.
+</para>
+
+</sect2>
+
+<sect2>
+<title>Supported View Definitions and Restrictions</title>
+
+<para>
+    Currently, we can create <acronym>IMMV</acronym>s using inner joins, and some
+    aggregates. However, several restrictions apply to the definition of IMMV.
+</para>
+
+<sect3>
+<title>Joins</title>
+<para>
+    Inner joins including self-join are supported. Outer joins are not supported.
+</para>
+</sect3>
+
+<sect3>
+<title>Aggregates</title>
+<para>
+    Supported aggregate functions are <function>count</function>, <function>sum</function>,
+    <function>avg</function>, <function>min</function>, and <function>max</function>.
+    Currently, only built-in aggregate functions are supported and user defined
+    aggregates cannot be used.  When a base table is modified, the new aggregated
+    values are incrementally calculated using the old aggregated values and values
+    of related hidden columns stored in <acronym>IMMV</acronym>.
+</para>
+
+<para>
+     Note that for <function>min</function> or <function>max</function>, the new values
+     could be re-calculated from base tables with regard to the affected groups when a
+     tuple containing the current minimal or maximal values are deleted from a base table.
+     Therefore, it can takes a long time to update an <acronym>IMMV</acronym> containing
+     these functions.
+</para>
+
+<para>
+    Also note that using <function>sum</function> or <function>avg</function> on
+    <type>real</type> (<type>float4</type>) type or <type>double precision</type>
+    (<type>float8</type>) type in <acronym>IMMV</acronym> is unsafe. This is
+    because aggregated values in <acronym>IMMV</acronym> can become different from
+    results calculated from base tables due to the limited precision of these types.
+    To avoid this problem, use the <type>numeric</type> type instead.
+</para>
+
+    <sect4>
+    <title>Restrictions on Aggregates</title>
+    <para>
+        There are the following restrictions:
+    <itemizedlist>
+        <listitem>
+        <para>
+            If we have a <literal>GROUP BY</literal> clause, expressions specified in
+               <literal>GROUP BY</literal> must appear in the target list.  This is
+               how tuples to be updated in the <acronym>IMMV</acronym> are identified.
+               These attributes are used as scan keys for searching tuples in the
+               <acronym>IMMV</acronym>, so indexes on them are required for efficient
+               <acronym>IVM</acronym>.
+        </para>
+        </listitem>
+
+        <listitem>
+        <para>
+            <literal>HAVING</literal> clause cannot be used.
+        </para>
+        </listitem>
+    </itemizedlist>
+    </para>
+    </sect4>
+</sect3>
+
+<sect3>
+<title>Other General Restrictions</title>
+<para>
+    There are other restrictions which generally apply to <acronym>IMMV</acronym>:
+    <itemizedlist>
+        <listitem>
+          <para>
+           Sub-queries cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+           CTEs cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+           Window functions cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s must be based on simple base tables.  It's not
+               supported to create them on top of views or materialized views.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            LIMIT and OFFSET clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s cannot contain system columns.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s cannot contain non-immutable functions.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            UNION/INTERSECT/EXCEPT clauses cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            DISTINCT ON clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            TABLESAMPLE parameter cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            inheritance parent tables cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            VALUES clause cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <literal>GROUPING SETS</literal> and <literal>FILTER</literal> clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            FOR UPDATE/SHARE cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            targetlist cannot contain columns whose name start with <literal>__ivm_</literal>.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            targetlist cannot contain expressions which contain an aggregate in it.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+              Logical replication is not supported, that is, even when a base table
+               at a publisher node is modified, <acronym>IMMV</acronym>s at subscriber
+               nodes are not updated.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            When the <literal>TRUNCATE</literal> command is executed on a base table,
+               nothing is changed on the <acronym>IMMV</acronym>.
+          </para>
+        </listitem>
+
+    </itemizedlist>
+</para>
+</sect3>
+
+</sect2>
+
+<sect2>
+<title><literal>DISTINCT</literal></title>
+
+<para>
+    <productname>PostgreSQL</productname> supports <acronym>IMMV</acronym> with
+    <literal>DISTINCT</literal>.  For example, suppose a <acronym>IMMV</acronym>
+    defined with <literal>DISTINCT</literal> on a base table containing duplicate
+    tuples.  When tuples are deleted from the base table, a tuple in the view is
+    deleted if and only if the multiplicity of the tuple becomes zero.  Moreover,
+    when tuples are inserted into the base table, a tuple is inserted into the
+    view only if the same tuple doesn't already exist in it.
+</para>
+
+<para>
+    Physically, an <acronym>IMMV</acronym> defined with <literal>DISTINCT</literal>
+    contains tuples after eliminating duplicates, and the multiplicity of each tuple
+    is stored in a hidden column named <literal>__ivm_count__</literal>.
+</para>
+</sect2>
+
+<sect2>
+<title>Concurrent Transactions</title>
+<para>
+    Suppose an <acronym>IMMV</acronym> is defined on two base tables and each
+    table was modified in different a concurrent transaction simultaneously.
+    In the transaction which was committed first, <acronym>IMMV</acronym> can
+    be updated considering only the change which happened in this transaction.
+    On the other hand, in order to update the view correctly in the transaction
+    which was committed later, we need to know the changes occurred in
+    both transactions.  For this reason, <literal>ExclusiveLock</literal>
+    is held on an <acronym>IMMV</acronym> immediately after a base table is
+    modified in <literal>READ COMMITTED</literal> mode to make sure that
+    the <acronym>IMMV</acronym> is updated in the latter transaction after
+    the former transaction is committed.  In <literal>REPEATABLE READ</literal>
+    or <literal>SERIALIZABLE</literal> mode, an error is raised immediately
+    if lock acquisition fails because any changes which occurred in
+    other transactions are not be visible in these modes and
+    <acronym>IMMV</acronym> cannot be updated correctly in such situations.
+    However, as an exception if the view has only one base table, 
+    the lock held on thew view is <literal>RowExclusiveLock</literal>.
+</para>
+</sect2>
+
+<sect2>
+<title>Row Level Security</title>
+<para>
+    If some base tables have row level security policy, rows that are not visible
+    to the materialized view's owner are excluded from the result.  In addition, such
+    rows are excluded as well when views are incrementally maintained.  However, if a
+    new policy is defined or policies are changed after the materialized view was created,
+    the new policy will not be applied to the view contents.  To apply the new policy,
+    you need to refresh materialized views.
+</para>
+</sect2>
+
+</sect1>
+
 <sect1 id="rules-update">
 <title>Rules on <command>INSERT</command>, <command>UPDATE</command>, and <command>DELETE</command></title>
 
-- 
2.17.1

#204r.takahashi_2@fujitsu.com
r.takahashi_2@fujitsu.com
In reply to: Yugo NAGATA (#198)
RE: Implementing Incremental View Maintenance

Hi Nagata-san,

Sorry for late reply.

However, even if we create triggers recursively on the parents or children, we would still
need more consideration. This is because we will have to convert the format of tuple of
modified table to the format of the table specified in the view for cases that the parent
and some children have different format.

I think supporting partitioned tables can be left for the next release.

OK. I understand.
In the v24-patch, creating IVM on partions or partition table is prohibited.
It is OK but it should be documented.

Perhaps, the following statement describe this.
If so, I think the definition of "simple base table" is ambiguous for some users.

+         IMMVs must be based on simple base tables. It's not supported to
+         create them on top of views or materialized views.

DEPENDENCY_IMMV was added to clear that a certain trigger is related to IMMV.
We dropped the IVM trigger and its dependencies from IMMV when REFRESH ... WITH NO DATA
is executed. Without the new deptype, we may accidentally delete a dependency created
with an intention other than the IVM trigger.

OK. I understand.

I think it is harder than you expected. When an IMMV is switched to a normal
materialized view, we needs to drop hidden columns (__ivm_count__ etc.), and in
the opposite case, we need to create them again. The former (IMMV->IVM) might be
easer, but for the latter (IVM->IMMV) I wonder we would need to re-create
IMMV.

OK. I understand.

Regards,
Ryohei Takahashi

#205r.takahashi_2@fujitsu.com
r.takahashi_2@fujitsu.com
In reply to: Yugo NAGATA (#199)
RE: Implementing Incremental View Maintenance

Hi Nagata-san,

Ok. I'll fix _copyIntoClause() and _equalIntoClause() as well as _readIntoClause()
and _outIntoClause().

OK.

ivm=# create table t (c1 int, c2 int);
CREATE TABLE
ivm=# create incremental materialized view ivm_t as select distinct c1 from t;
NOTICE: created index "ivm_t_index" on materialized view "ivm_t"
SELECT 0

Then I executed pg_dump.

In the dump, the following SQLs appear.

CREATE INCREMENTAL MATERIALIZED VIEW public.ivm_t AS
SELECT DISTINCT t.c1
FROM public.t
WITH NO DATA;

ALTER TABLE ONLY public.ivm_t
ADD CONSTRAINT ivm_t_index UNIQUE (c1);

If I execute psql with the result of pg_dump, following error occurs.

ERROR: ALTER action ADD CONSTRAINT cannot be performed on relation

"ivm_t"

DETAIL: This operation is not supported for materialized views.

Good catch! It was my mistake creating unique constraints on IMMV in spite of
we cannot defined them via SQL. I'll fix it to use unique indexes instead of
constraints.

I checked the same procedure on v24 patch.
But following error occurs instead of the original error.

ERROR: relation "ivm_t_index" already exists

Regards,
Ryohei Takahashi

#206Yugo NAGATA
nagata@sraoss.co.jp
In reply to: r.takahashi_2@fujitsu.com (#204)
Re: Implementing Incremental View Maintenance

Hello Takahashi-san,

On Wed, 24 Nov 2021 04:27:13 +0000
"r.takahashi_2@fujitsu.com" <r.takahashi_2@fujitsu.com> wrote:

Hi Nagata-san,

Sorry for late reply.

However, even if we create triggers recursively on the parents or children, we would still
need more consideration. This is because we will have to convert the format of tuple of
modified table to the format of the table specified in the view for cases that the parent
and some children have different format.

I think supporting partitioned tables can be left for the next release.

OK. I understand.
In the v24-patch, creating IVM on partions or partition table is prohibited.
It is OK but it should be documented.

Perhaps, the following statement describe this.
If so, I think the definition of "simple base table" is ambiguous for some users.

+         IMMVs must be based on simple base tables. It's not supported to
+         create them on top of views or materialized views.

Oh, I forgot to fix the documentation. I'll fix it.

Ragards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#207Yugo NAGATA
nagata@sraoss.co.jp
In reply to: r.takahashi_2@fujitsu.com (#205)
Re: Implementing Incremental View Maintenance

On Wed, 24 Nov 2021 04:31:25 +0000
"r.takahashi_2@fujitsu.com" <r.takahashi_2@fujitsu.com> wrote:

ivm=# create table t (c1 int, c2 int);
CREATE TABLE
ivm=# create incremental materialized view ivm_t as select distinct c1 from t;
NOTICE: created index "ivm_t_index" on materialized view "ivm_t"
SELECT 0

Then I executed pg_dump.

In the dump, the following SQLs appear.

CREATE INCREMENTAL MATERIALIZED VIEW public.ivm_t AS
SELECT DISTINCT t.c1
FROM public.t
WITH NO DATA;

ALTER TABLE ONLY public.ivm_t
ADD CONSTRAINT ivm_t_index UNIQUE (c1);

If I execute psql with the result of pg_dump, following error occurs.

ERROR: ALTER action ADD CONSTRAINT cannot be performed on relation

"ivm_t"

DETAIL: This operation is not supported for materialized views.

Good catch! It was my mistake creating unique constraints on IMMV in spite of
we cannot defined them via SQL. I'll fix it to use unique indexes instead of
constraints.

I checked the same procedure on v24 patch.
But following error occurs instead of the original error.

ERROR: relation "ivm_t_index" already exists

Thank you for pointing out it!

Hmmm, an index is created when IMMV is defined, so CREAE INDEX called
after this would fail... Maybe, we should not create any index automatically
if IMMV is created WITH NO DATA.

I'll fix it after some investigation.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#208Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#207)
Re: Implementing Incremental View Maintenance

Hi hackers,

This is a response to a comment in "Commitfest 2021-11 Patch Triage - Part 1" [1]/messages/by-id/6EDAAF93-1663-41D0-9148-76739104943E@yesql.se.

2138: Incremental Materialized View Maintenance
===============================================
There seems to be concensus on the thread that this is a feature that we want,
and after initial design discussions there seems to be no disagreements with
the approach taken. The patch was marked ready for committer almost a year
ago, but have since been needs review (which seems correct). The size of the
patchset and the length of the thread make it hard to gauge just far away it
is, maybe the author or a review can summarize the current state and outline
what is left for it to be committable.

[1]: /messages/by-id/6EDAAF93-1663-41D0-9148-76739104943E@yesql.se

I'll describe recent discussions and current status of this thread.

* Recent Discussions and Current status

1.
Previously, we proposed a patchset that supports outer-joins, some sub-queries
and CTEs. However, aiming to reduce the size of the patchset, I proposed to omit
these features from the first version of the patch in my post at 2021-08-02 [2]/messages/by-id/20210802152834.ecbaba6e17d1957547c3a55d@sraoss.co.jp.

Currently, we are proposing Incremental View Maintenance feature for PostgreSQL 15
that supports following queries in the view definition query.

- inner joins including self-join
- DISTINCT and views with tuple duplicates
- some built-in aggregate functions (count, sum, agv, min, and max)

Is it OK? Although there has been no opposite opinion, we want to confirm it.

[2]: /messages/by-id/20210802152834.ecbaba6e17d1957547c3a55d@sraoss.co.jp

2.
Recently, There was a suggestion that we should support partitioned tables from
Ryohei Takahashi, but I decided to not support it in the first release of IVM.
Takahshi-san agreed with it, and the documentation will be fixed soon [3]/messages/by-id/20211125154717.777e9d35ddde5f2e0d5d8355@sraoss.co.jp.

[3]: /messages/by-id/20211125154717.777e9d35ddde5f2e0d5d8355@sraoss.co.jp

3.
Takahashi-san pointed out that restoring pg_dump results causes an error. I am fixing
it now.[4]/messages/by-id/20211125163710.2f32ae3d4be5d5f9ade020b6@sraoss.co.jp

[4]: /messages/by-id/20211125163710.2f32ae3d4be5d5f9ade020b6@sraoss.co.jp

The remaining is the summary of our proposal of IVM feature, its design, and past discussions.

---------------------------------------------------------------------------------------
* Features

Incremental View Maintenance (IVM) is a way to make materialized views
up-to-date by computing only incremental changes and applying them on
views. IVM is more efficient than REFRESH MATERIALIZED VIEW when
only small parts of the view are changed.

This patchset provides a feature that allows materialized views to be
updated automatically and incrementally just after a underlying table
is modified.

You can create an incementally maintainable materialized view (IMMV)
by using CREATE INCREMENTAL MATERIALIZED VIEW command.

The followings are supported in view definition queries:
- SELECT ... FROM ... WHERE ..., joins (inner joins, self-joins)
- some built-in aggregate functions (count, sum, avg, min, max)
- GROUP BY clause
- DISTINCT clause

Views can contain multiple tuples with the same content (duplicate tuples).

The following are not supported in a view definition:
- Outer joins
- Aggregates otehr than above, window functions, HAVING
- Sub-queries, CTEs
- Set operations (UNION, INTERSECT, EXCEPT)
- DISTINCT ON, ORDER BY, LIMIT, OFFSET

Also, a view definition query cannot contain other views, materialized views,
foreign tables, partitioned tables, partitions, VALUES, non-immutable functions,
system columns, or expressions that contains aggregates.

---------------------------------------------------------------------------------------
* Design

An IMMV is maintained using statement-level AFTER triggers. When an IMMV is
created, triggers are automatically created on all base tables contained in the
view definition query.

When a table is modified, the change that occurred in the table are extracted
as transition tables in the AFTER triggers. Then, changes that will occur in
the view are calculated by a rewritten view dequery in which the modified table
is replaced with the transition table. For example, if the view is defined as
"SELECT * FROM R, S", and tuples inserted into R are stored in a transiton table
dR, the tuples that will be inserted into the view are calculated as the result
of "SELECT * FROM dR, S".

** Multiple Tables Modification

Multiple tables can be modified in a statement when using triggers, foreign key
constraint, or modifying CTEs. When multiple tables are modified, we need
the state of tables before the modification. For example, when some tuples,
dR and dS, are inserted into R and S respectively, the tuples that will be
inserted into the view are calculated by the following two queries:

"SELECT * FROM dR, S_pre"
"SELECT * FROM R, dS"

where S_pre is the table before the modification, R is the current state of
table, that is, after the modification. This pre-update states of table
is calculated by filtering inserted tuples using cmin/xmin system columns,
and appending deleted tuples which are contained in the old transition table.
This is implemented in get_prestate_rte().

Transition tables for each modification are collected in each AFTER trigger
function call. Then, the view maintenance is performed in the last call of
the trigger.

In the original PostgreSQL, tuplestores of transition tables are freed at the
end of each nested query. However, their lifespan needs to be prolonged to
the end of the out-most query in order to maintain the view in the last AFTER
trigger. For this purpose, SetTransitionTablePreserved is added in trigger.c.

** Duplicate Tulpes

When calculating changes that will occur in the view (= delta tables),
multiplicity of tuples are calculated by using count(*).

When deleting tuples from the view, tuples to be deleted are identified by
joining the delta table with the view, and the tuples are deleted as many as
specified multiplicity by numbered using row_number() function.
This is implemented in apply_old_delta().

When inserting tuples into the view, tuples are duplicated to the specified
multiplicity using generate_series() function. This is implemented in
apply_new_delta().

** DISTINCT clause

When DISTINCT is used, the view has a hidden column __ivm_count__ that
stores multiplicity for tuples. When tuples are deleted from or inserted into
the view, the values of __ivm_count__ column is decreased or increased as many
as specified multiplicity. Eventually, when the values becomes zero, the
corresponding tuple is deleted from the view. This is implemented in
apply_old_delta_with_count() and apply_new_delta_with_count().

** Aggregates

Built-in count sum, avg, min, and max are supported. Whether a given
aggregate function can be used or not is checked by using its OID in
check_aggregate_supports_ivm().

When creating a materialized view containing aggregates, in addition
to __ivm_count__, more than one hidden columns for each aggregate are
added to the target list. For example, columns for storing sum(x),
count(x) are added if we have avg(x). When the view is maintained,
aggregated values are updated using these hidden columns, also hidden
columns are updated at the same time.

The maintenance of aggregated view is performed in
apply_old_delta_with_count() and apply_new_delta_with_count(). The SET
clauses for updating columns are generated by append_set_clause_*().

If the view has min(x) or max(x) and the minimum or maximal value is
deleted from a table, we need to update the value to the new min/max
recalculated from the tables rather than incremental computation. This
is performed in recalc_and_set_values().

---------------------------------------------------------------------------------------
* Discussion

** Aggregate support

There were a few suggestions that general aggregate functions should be
supported [5]/messages/by-id/20191128140333.GA25947@alvherre.pgsql[6]/messages/by-id/CAM-w4HOvDrL4ou6m=592zUiKGVzTcOpNj-d_cJqzL00fdsS5kg@mail.gmail.com, which may be possible by extending pg_aggregate catalog.
However, we decided to left supporting general aggregates to the future work [7]/messages/by-id/20201016193034.9a4c44c79fc1eca7babe093e@sraoss.co.jp
because it would need substantial works and make the patch more complex and
bigger. There has been no opposite opinion on this.

[5]: /messages/by-id/20191128140333.GA25947@alvherre.pgsql
[6]: /messages/by-id/CAM-w4HOvDrL4ou6m=592zUiKGVzTcOpNj-d_cJqzL00fdsS5kg@mail.gmail.com
[7]: /messages/by-id/20201016193034.9a4c44c79fc1eca7babe093e@sraoss.co.jp

** Hidden columns

Columns starting with "__ivm_" are hidden columns that doesn't appear when a
view is accessed by "SELECT * FROM ....". For this aim, parse_relation.c is
fixed. There was a proposal to enable hidden columns by adding a new flag to
pg_attribute [8]/messages/by-id/CAEepm=3ZHh=p0nEEnVbs1Dig_UShPzHUcMNAqvDQUgYgcDo-pA@mail.gmail.com, but this thread is no longer active, so we decided to check
the hidden column by its name [9]/messages/by-id/20201016193034.9a4c44c79fc1eca7babe093e@sraoss.co.jp.

[8]: /messages/by-id/CAEepm=3ZHh=p0nEEnVbs1Dig_UShPzHUcMNAqvDQUgYgcDo-pA@mail.gmail.com
[9]: /messages/by-id/20201016193034.9a4c44c79fc1eca7babe093e@sraoss.co.jp

** Concurrent Transactions

When the view definition has more than one table, we acquire an exclusive
lock before the view maintenance in order to avoid inconsistent results.
This behavior was explained in [10]/messages/by-id/20200909092752.c91758a1bec3479668e82643@sraoss.co.jp. The lock was improved to use weaker lock
when the view has only one table based on a suggestion from Konstantin Knizhnik [11]/messages/by-id/5663f5f0-48af-686c-bf3c-62d279567e2a@postgrespro.ru.

[10]: /messages/by-id/20200909092752.c91758a1bec3479668e82643@sraoss.co.jp
[11]: /messages/by-id/5663f5f0-48af-686c-bf3c-62d279567e2a@postgrespro.ru

** Automatic Index Creation

When a view is created, a unique index is automatically created if
possible, that is, if the view definition query has a GROUP BY or
DISTINCT, or if the view contains all primary key attributes of
its base tables in the target list. It is necessary for efficient
view maintenance. This feature is based on a suggestion from
Konstantin Knizhnik [12]/messages/by-id/89729da8-9042-7ea0-95af-e415df6da14d@postgrespro.ru.

[12]: /messages/by-id/89729da8-9042-7ea0-95af-e415df6da14d@postgrespro.ru

** Others

There are some other changes in core for IVM implementation.
There has been no opposite opinion on any ever.

- syntax

The command to create an incrementally maintainable materialized
view (IMMV) is "CREATE INCREMENTAL MATERIALIZED VIEW". The new
keyword "INCREMENTAL" is added.

- pg_class

A new attribue "relisivm" is added to pg_class to indicate
that the relation is an IMMV.

- deptype

DEPENDENCY_IMMV(m) was added to pg_depend as a new deptype. This is necessary
to clear that a certain trigger is related to IMMV, especially when We dropped
IVM triggers from the view when REFRESH ... WITH NO DATA is executed [13]/messages/by-id/20210922185343.548883e81b8baef14a0193c5@sraoss.co.jp.

[13]: /messages/by-id/20210922185343.548883e81b8baef14a0193c5@sraoss.co.jp

---------------------------------------------------------------------------------------

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#209Julien Rouhaud
rjuju123@gmail.com
In reply to: Yugo NAGATA (#207)
Re: Implementing Incremental View Maintenance

Hi,

On Thu, Nov 25, 2021 at 04:37:10PM +0900, Yugo NAGATA wrote:

On Wed, 24 Nov 2021 04:31:25 +0000
"r.takahashi_2@fujitsu.com" <r.takahashi_2@fujitsu.com> wrote:

I checked the same procedure on v24 patch.
But following error occurs instead of the original error.

ERROR: relation "ivm_t_index" already exists

Thank you for pointing out it!

Hmmm, an index is created when IMMV is defined, so CREAE INDEX called
after this would fail... Maybe, we should not create any index automatically
if IMMV is created WITH NO DATA.

I'll fix it after some investigation.

Are you still investigating on that problem? Also, the patchset doesn't apply
anymore:
http://cfbot.cputube.org/patch_36_2138.log
=== Applying patches on top of PostgreSQL commit ID a18b6d2dc288dfa6e7905ede1d4462edd6a8af47 ===
[...]
=== applying patch ./v24-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patch
patching file src/bin/pg_dump/pg_dump.c
Hunk #1 FAILED at 6393.
Hunk #2 FAILED at 6596.
Hunk #3 FAILED at 6719.
Hunk #4 FAILED at 6796.
Hunk #5 succeeded at 14953 (offset -915 lines).
4 out of 5 hunks FAILED -- saving rejects to file src/bin/pg_dump/pg_dump.c.rej

There isn't any answer to your following email summarizing the feature yet, so
I'm not sure what should be the status of this patch, as there's no ideal
category for that. For now I'll change the patch to Waiting on Author on the
cf app, feel free to switch it back to Needs Review if you think it's more
suitable, at least for the design discussion need.

#210Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Julien Rouhaud (#209)
Re: Implementing Incremental View Maintenance

Hi,

On Thu, 13 Jan 2022 18:23:42 +0800
Julien Rouhaud <rjuju123@gmail.com> wrote:

Hi,

On Thu, Nov 25, 2021 at 04:37:10PM +0900, Yugo NAGATA wrote:

On Wed, 24 Nov 2021 04:31:25 +0000
"r.takahashi_2@fujitsu.com" <r.takahashi_2@fujitsu.com> wrote:

I checked the same procedure on v24 patch.
But following error occurs instead of the original error.

ERROR: relation "ivm_t_index" already exists

Thank you for pointing out it!

Hmmm, an index is created when IMMV is defined, so CREAE INDEX called
after this would fail... Maybe, we should not create any index automatically
if IMMV is created WITH NO DATA.

I'll fix it after some investigation.

Are you still investigating on that problem? Also, the patchset doesn't apply
anymore:

I attached the updated and rebased patch set.

I fixed to not create a unique index when an IMMV is created WITH NO DATA.
Instead, the index is created by REFRESH WITH DATA only when the same one
is not created yet.

Also, I fixed the documentation to describe that foreign tables and partitioned
tables are not supported according with Takahashi-san's suggestion.

There isn't any answer to your following email summarizing the feature yet, so
I'm not sure what should be the status of this patch, as there's no ideal
category for that. For now I'll change the patch to Waiting on Author on the
cf app, feel free to switch it back to Needs Review if you think it's more
suitable, at least for the design discussion need.

I changed the status to Needs Review.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#211Zhihong Yu
zyu@yugabyte.com
In reply to: Yugo NAGATA (#210)
Re: Implementing Incremental View Maintenance

On Thu, Feb 3, 2022 at 8:28 AM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi,

On Thu, 13 Jan 2022 18:23:42 +0800
Julien Rouhaud <rjuju123@gmail.com> wrote:

Hi,

On Thu, Nov 25, 2021 at 04:37:10PM +0900, Yugo NAGATA wrote:

On Wed, 24 Nov 2021 04:31:25 +0000
"r.takahashi_2@fujitsu.com" <r.takahashi_2@fujitsu.com> wrote:

I checked the same procedure on v24 patch.
But following error occurs instead of the original error.

ERROR: relation "ivm_t_index" already exists

Thank you for pointing out it!

Hmmm, an index is created when IMMV is defined, so CREAE INDEX called
after this would fail... Maybe, we should not create any index

automatically

if IMMV is created WITH NO DATA.

I'll fix it after some investigation.

Are you still investigating on that problem? Also, the patchset doesn't

apply

anymore:

I attached the updated and rebased patch set.

I fixed to not create a unique index when an IMMV is created WITH NO DATA.
Instead, the index is created by REFRESH WITH DATA only when the same one
is not created yet.

Also, I fixed the documentation to describe that foreign tables and
partitioned
tables are not supported according with Takahashi-san's suggestion.

There isn't any answer to your following email summarizing the feature

yet, so

I'm not sure what should be the status of this patch, as there's no ideal
category for that. For now I'll change the patch to Waiting on Author

on the

cf app, feel free to switch it back to Needs Review if you think it's

more

suitable, at least for the design discussion need.

I changed the status to Needs Review.

Hi,

Did you intend to attach updated patch ?

I don't seem to find any.

FYI

#212Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Zhihong Yu (#211)
10 attachment(s)
Re: Implementing Incremental View Maintenance

On Thu, 3 Feb 2022 08:48:00 -0800
Zhihong Yu <zyu@yugabyte.com> wrote:

On Thu, Feb 3, 2022 at 8:28 AM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi,

On Thu, 13 Jan 2022 18:23:42 +0800
Julien Rouhaud <rjuju123@gmail.com> wrote:

Hi,

On Thu, Nov 25, 2021 at 04:37:10PM +0900, Yugo NAGATA wrote:

On Wed, 24 Nov 2021 04:31:25 +0000
"r.takahashi_2@fujitsu.com" <r.takahashi_2@fujitsu.com> wrote:

I checked the same procedure on v24 patch.
But following error occurs instead of the original error.

ERROR: relation "ivm_t_index" already exists

Thank you for pointing out it!

Hmmm, an index is created when IMMV is defined, so CREAE INDEX called
after this would fail... Maybe, we should not create any index

automatically

if IMMV is created WITH NO DATA.

I'll fix it after some investigation.

Are you still investigating on that problem? Also, the patchset doesn't

apply

anymore:

I attached the updated and rebased patch set.

I fixed to not create a unique index when an IMMV is created WITH NO DATA.
Instead, the index is created by REFRESH WITH DATA only when the same one
is not created yet.

Also, I fixed the documentation to describe that foreign tables and
partitioned
tables are not supported according with Takahashi-san's suggestion.

There isn't any answer to your following email summarizing the feature

yet, so

I'm not sure what should be the status of this patch, as there's no ideal
category for that. For now I'll change the patch to Waiting on Author

on the

cf app, feel free to switch it back to Needs Review if you think it's

more

suitable, at least for the design discussion need.

I changed the status to Needs Review.

Hi,

Did you intend to attach updated patch ?

I don't seem to find any.

Oops, I attached. Thanks!

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

v25-0010-Add-documentations-about-Incremental-View-Mainte.patchtext/x-diff; name=v25-0010-Add-documentations-about-Incremental-View-Mainte.patchDownload
From 92f8d5a1a88693cbef7df20c972b9413e2a0fc13 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:25:34 +0900
Subject: [PATCH v25 10/15] Add documentations about Incremental View
 Maintenance

---
 doc/src/sgml/catalogs.sgml                    |  24 +-
 .../sgml/ref/create_materialized_view.sgml    | 131 +++++-
 .../sgml/ref/refresh_materialized_view.sgml   |   6 +-
 doc/src/sgml/rules.sgml                       | 443 ++++++++++++++++++
 4 files changed, 599 insertions(+), 5 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 7d5b0b1656..7c07ac8ece 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2188,6 +2188,15 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>relisivm</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if materialized view enables incremental view maintenance
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>relrewrite</structfield> <type>oid</type>
@@ -3474,6 +3483,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para>
      </listitem>
     </varlistentry>
+
+    <varlistentry>
+     <term><symbol>DEPENDENCY_IMMV</symbol> (<literal>m</literal>)</term>
+     <listitem>
+      <para>
+       The dependent object was created as part of creation of the Materialized
+       View with Incremental View Maintenance reference, and is really just a 
+       part of its internal implementation. The dependent object must not be
+       dropped unless the materialized view is also dropped.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
 
    Other dependency flavors might be needed in future.
@@ -3857,7 +3878,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <structfield>extrelocatable</structfield> <type>bool</type>
       </para>
       <para>
-       True if extension can be relocated to another schema
+      True for materialized views which are enabled for incremental
+      view maintenance (IVM).
       </para></entry>
      </row>
 
diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml
index 0d2fea2b97..c3bd46571f 100644
--- a/doc/src/sgml/ref/create_materialized_view.sgml
+++ b/doc/src/sgml/ref/create_materialized_view.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
+CREATE [ INCREMENTAL ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
     [ (<replaceable>column_name</replaceable> [, ...] ) ]
     [ USING <replaceable class="parameter">method</replaceable> ]
     [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
@@ -60,6 +60,132 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
   <title>Parameters</title>
 
   <variablelist>
+   <varlistentry>
+    <term><literal>INCREMENTAL</literal></term>
+    <listitem>
+     <para>
+      If specified, some triggers are automatically created so that the rows
+      of the materialized view are immediately updated when base tables of the
+      materialized view are updated. In general, this allows faster update of
+      the materialized view at a price of slower update of the base tables
+      because the triggers will be invoked. We call this form of materialized
+      view as "Incrementally Maintainable Materialized View" (IMMV).
+     </para>
+     <para>
+      When <acronym>IMMV</acronym> is defined, a unique index is created on the view
+      automatically if possible.  If the view definition query has a GROUP BY clause,
+      a unique index is created on the columns of GROUP BY expressions.  Also, if the
+      view has DISTINCT clause, a unique index is created on all columns in the target
+      list.  Otherwise, if the view contains all primary key attritubes of its base
+      tables in the target list, a unique index is created on these attritubes.  In
+      other cases, no index is created.
+     </para>
+     <para>
+      There are restrictions of query definitions allowed to use this
+      option. The following are supported in query definitions for IMMV:
+      <itemizedlist>
+
+       <listitem>
+        <para>
+         Inner joins (including self-joins).
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Some built-in aggregate functions (count, sum, avg, min, max) without a HAVING
+         clause. 
+        </para>
+        </listitem>
+      </itemizedlist>
+
+      Unsupported queries with this option include the following:
+
+      <itemizedlist>
+       <listitem>
+        <para>
+         Outer joins.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Sub-queries.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Aggregate functions other than built-in count, sum, avg, min and max.
+        </para>
+       </listitem>
+       <listitem>
+        <para>
+         Aggregate functions with a HAVING clause.
+        </para>
+       </listitem>
+       <listitem>
+        <para>
+         DISTINCT ON, WINDOW, VALUES, LIMIT and OFFSET clause.
+        </para>
+       </listitem>
+      </itemizedlist>
+
+      Other restrictions include:
+      <itemizedlist>
+
+       <listitem>
+        <para>
+         IMMVs must be based on simple base tables. It's not supported to
+         create them on top of views or materialized views.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         When the TRUNCATE command is executed on a base table,
+         no changes are made to the materialized view.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         It is not supported to include system columns in an IMMV.
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+ERROR:  system column is not supported with IVM
+         </programlisting>
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Non-immutable functions are not supported.
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+ERROR:  functions in IMMV must be marked IMMUTABLE
+         </programlisting>
+        </para>
+        </listitem>
+
+       <listitem>
+        <para>
+         IMMVs do not support expressions that contains aggregates
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Logical replication does not support IMMVs.
+        </para>
+       </listitem>
+
+      </itemizedlist>
+
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>IF NOT EXISTS</literal></term>
     <listitem>
@@ -155,7 +281,8 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
       This clause specifies whether or not the materialized view should be
       populated at creation time.  If not, the materialized view will be
       flagged as unscannable and cannot be queried until <command>REFRESH
-      MATERIALIZED VIEW</command> is used.
+      MATERIALIZED VIEW</command> is used.  Also, if the view is IMMV,
+      triggers for maintaining the view are not created.
      </para>
     </listitem>
    </varlistentry>
diff --git a/doc/src/sgml/ref/refresh_materialized_view.sgml b/doc/src/sgml/ref/refresh_materialized_view.sgml
index 675d6090f3..e0d15fdce0 100644
--- a/doc/src/sgml/ref/refresh_materialized_view.sgml
+++ b/doc/src/sgml/ref/refresh_materialized_view.sgml
@@ -35,9 +35,11 @@ REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] <replaceable class="parameter">name</
    owner of the materialized view.  The old contents are discarded.  If
    <literal>WITH DATA</literal> is specified (or defaults) the backing query
    is executed to provide the new data, and the materialized view is left in a
-   scannable state.  If <literal>WITH NO DATA</literal> is specified no new
+   scannable state.  Also, if the view is an incrementally maintainable materialized
+   view (IMMV) and was unpopulated, triggers for maintaining
+   the view are created.  If <literal>WITH NO DATA</literal> is specified no new
    data is generated and the materialized view is left in an unscannable
-   state.
+   state.  If the view is IMMV, the triggers are dropped.
   </para>
   <para>
    <literal>CONCURRENTLY</literal> and <literal>WITH NO DATA</literal> may not
diff --git a/doc/src/sgml/rules.sgml b/doc/src/sgml/rules.sgml
index 4aa4e00e01..13a64eab7f 100644
--- a/doc/src/sgml/rules.sgml
+++ b/doc/src/sgml/rules.sgml
@@ -1090,6 +1090,449 @@ SELECT word FROM words ORDER BY word &lt;-&gt; 'caterpiler' LIMIT 10;
 
 </sect1>
 
+<sect1 id="rules-ivm">
+<title>Incremental View Maintenance</title>
+
+<indexterm zone="rules-ivm">
+ <primary>incremental view maintenance</primary>
+</indexterm>
+
+<indexterm zone="rules-ivm">
+ <primary>materialized view</primary>
+ <secondary>incremental view maintenance</secondary>
+</indexterm>
+
+<indexterm zone="rules-ivm">
+ <primary>view</primary>
+ <secondary>incremental view maintenance</secondary>
+</indexterm>
+
+<sect2 id="rules-ivm-overview">
+<title>Overview</title>
+
+<para>
+    Incremental View Maintenance (<acronym>IVM</acronym>) is a way to make
+    materialized views up-to-date in which only incremental changes are computed
+    and applied on views rather than recomputing the contents from scratch as
+    <command>REFRESH MATERIALIZED VIEW</command> does.  <acronym>IVM</acronym>
+    can update materialized views more efficiently than recomputation when only
+    small parts of the view are changed.
+</para>
+
+<para>
+    There are two approaches with regard to timing of view maintenance:
+    immediate and deferred.  In immediate maintenance, views are updated in the
+    same transaction that its base table is modified.  In deferred maintenance,
+    views are updated after the transaction is committed, for example, when the
+    view is accessed, as a response to user command like <command>REFRESH
+    MATERIALIZED VIEW</command>, or periodically in background, and so on.
+    <productname>PostgreSQL</productname> currently implements only a kind of
+    immediate maintenance, in which materialized views are updated immediately
+    in AFTER triggers when a base table is modified.
+</para>
+
+<para>
+    To create materialized views supporting <acronym>IVM</acronym>, use the
+    <command>CREATE INCREMENTAL MATERIALIZED VIEW</command>, for example:
+<programlisting>
+CREATE <emphasis>INCREMENTAL</emphasis> MATERIALIZED VIEW mymatview AS SELECT * FROM mytab;
+</programlisting>
+    When a materialized view is created with the <literal>INCREMENTAL</literal>
+    keyword, some triggers are automatically created so that the view's contents are
+    immediately updated when its base tables are modified. We call this form
+    of materialized view an Incrementally Maintainable Materialized View
+    (<acronym>IMMV</acronym>).
+<programlisting>
+postgres=# CREATE INCREMENTAL MATERIALIZED VIEW m AS SELECT * FROM t0;
+NOTICE:  could not create an index on materialized view "m" automatically
+HINT:  Create an index on the materialized view for effcient incremental maintenance.
+SELECT 3
+postgres=# SELECT * FROM m;
+ i
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+postgres=# INSERT INTO t0 VALUES (4);
+INSERT 0 1
+postgres=# SELECT * FROM m; -- automatically updated
+ i
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+</programlisting>
+</para>
+
+<para>
+    Some <acronym>IMMV</acronym>s have hidden columns which are added
+    automatically when a materialized view is created. Their name starts
+    with <literal>__ivm_</literal> and they contain information required
+    for maintaining the <acronym>IMMV</acronym>. Such columns are not visible
+    when the <acronym>IMMV</acronym> is accessed by <literal>SELECT *</literal>
+    but are visible if the column name is explicitly specified in the target
+    list. We can also see the hidden columns in <literal>\d</literal>
+    meta-commands of <command>psql</command> commands.
+</para>
+
+<para>
+    In general, <acronym>IMMV</acronym>s allow faster updates of materialized
+    views at the price of slower updates to their base tables. Updates of
+    <acronym>IMMV</acronym> is slower because triggers will be invoked and the
+    view is updated in triggers per modification statement.
+</para>
+
+<para>
+    For example, suppose a normal materialized view defined as below:
+
+<programlisting>
+test=# CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm AS
+        SELECT a.aid, b.bid, a.abalance, b.bbalance
+        FROM pgbench_accounts a JOIN pgbench_branches b USING(bid);
+SELECT 10000000
+
+</programlisting>
+
+    Updating a tuple in a base table of this materialized view is rapid but the
+   <command>REFRESH MATERIALIZED VIEW</command> command on this view takes a long time:
+
+<programlisting>
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 0.990 ms
+
+test=# REFRESH MATERIALIZED VIEW mv_normal ;
+REFRESH MATERIALIZED VIEW
+Time: 33533.952 ms (00:33.534)
+</programlisting>
+</para>
+
+<para>
+    On the other hand, after creating <acronym>IMMV</acronym> with the same view
+    definition as below:
+
+<programlisting>
+test=# CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm AS
+        SELECT a.aid, b.bid, a.abalance, b.bbalance
+        FROM pgbench_accounts a JOIN pgbench_branches b USING(bid);
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+NOTICE:  created index "mv_ivm_index" on materialized view "mv_ivm"
+</programlisting>
+
+    updating a tuple in a base table takes more than the normal view,
+    but its content is updated automatically and this is faster than the
+    <command>REFRESH MATERIALIZED VIEW</command> command.
+
+<programlisting>
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 13.068 ms
+</programlisting>
+
+</para>
+
+<para>
+    Appropriate indexes on <acronym>IMMV</acronym>s are necessary for
+    efficient <acronym>IVM</acronym> because it looks for tuples to be
+    updated in <acronym>IMMV</acronym>.  If there are no indexes, it
+    will take a long time.
+</para>
+
+<para>
+    Therefore, when <acronym>IMMV</acronym> is defined, a unique index is created on the view
+    automatically if possible.  If the view definition query has a GROUP BY clause, a unique
+    index is created on the columns of GROUP BY expressions.  Also, if the view has DISTINCT
+    clause, a unique index is created on all columns in the target list. Otherwise, if the
+    view contains all primary key attritubes of its base tables in the target list, a unique
+    index is created on these attritubes.  In other cases, no index is created.
+</para>
+
+<para>
+    In the previous example, a unique index "mv_ivm_index" is created on aid and bid
+    columns of materialized view "mv_ivm", and this enables the rapid update of the view.
+    Dropping this index make updating the view take a loger time.
+<programlisting>
+test=# DROP INDEX mv_ivm_index;
+DROP INDEX
+Time: 67.081 ms
+
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 16386.245 ms (00:16.386)
+</programlisting>
+
+</para>
+
+<para>
+    <acronym>IVM</acronym> is effective when we want to keep a materialized
+    view up-to-date and small fraction of a base table is modified
+    infrequently.  Due to the overhead of immediate maintenance, <acronym>IVM</acronym>
+    is not effective when a base table is modified frequently.  Also, when a
+    large part of a base table is modified or large data is inserted into a
+    base table, <acronym>IVM</acronym> is not effective and the cost of
+    maintenance can be larger than the <command>REFRESH MATERIALIZED VIEW</command>
+    command. In such situation, we can use <command>REFRESH MATERIALIZED VIEW</command>
+    and specify <literal>WITH NO DATA</literal> to disable immediate
+    maintenance before modifying a base table. After a base table modification,
+    execute the <command>REFRESH MATERIALIZED VIEW</command> (with <literal>WITH DATA</literal>)
+    command to refresh the view data and enable immediate maintenance.
+</para>
+
+</sect2>
+
+<sect2>
+<title>Supported View Definitions and Restrictions</title>
+
+<para>
+    Currently, we can create <acronym>IMMV</acronym>s using inner joins, and some
+    aggregates. However, several restrictions apply to the definition of IMMV.
+</para>
+
+<sect3>
+<title>Joins</title>
+<para>
+    Inner joins including self-join are supported. Outer joins are not supported.
+</para>
+</sect3>
+
+<sect3>
+<title>Aggregates</title>
+<para>
+    Supported aggregate functions are <function>count</function>, <function>sum</function>,
+    <function>avg</function>, <function>min</function>, and <function>max</function>.
+    Currently, only built-in aggregate functions are supported and user defined
+    aggregates cannot be used.  When a base table is modified, the new aggregated
+    values are incrementally calculated using the old aggregated values and values
+    of related hidden columns stored in <acronym>IMMV</acronym>.
+</para>
+
+<para>
+     Note that for <function>min</function> or <function>max</function>, the new values
+     could be re-calculated from base tables with regard to the affected groups when a
+     tuple containing the current minimal or maximal values are deleted from a base table.
+     Therefore, it can takes a long time to update an <acronym>IMMV</acronym> containing
+     these functions.
+</para>
+
+<para>
+    Also note that using <function>sum</function> or <function>avg</function> on
+    <type>real</type> (<type>float4</type>) type or <type>double precision</type>
+    (<type>float8</type>) type in <acronym>IMMV</acronym> is unsafe. This is
+    because aggregated values in <acronym>IMMV</acronym> can become different from
+    results calculated from base tables due to the limited precision of these types.
+    To avoid this problem, use the <type>numeric</type> type instead.
+</para>
+
+    <sect4>
+    <title>Restrictions on Aggregates</title>
+    <para>
+        There are the following restrictions:
+    <itemizedlist>
+        <listitem>
+        <para>
+            If we have a <literal>GROUP BY</literal> clause, expressions specified in
+               <literal>GROUP BY</literal> must appear in the target list.  This is
+               how tuples to be updated in the <acronym>IMMV</acronym> are identified.
+               These attributes are used as scan keys for searching tuples in the
+               <acronym>IMMV</acronym>, so indexes on them are required for efficient
+               <acronym>IVM</acronym>.
+        </para>
+        </listitem>
+
+        <listitem>
+        <para>
+            <literal>HAVING</literal> clause cannot be used.
+        </para>
+        </listitem>
+    </itemizedlist>
+    </para>
+    </sect4>
+</sect3>
+
+<sect3>
+<title>Other General Restrictions</title>
+<para>
+    There are other restrictions which generally apply to <acronym>IMMV</acronym>:
+    <itemizedlist>
+        <listitem>
+          <para>
+           Sub-queries cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+           CTEs cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+           Window functions cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s must be based on simple base tables.  It's not
+               supported to create them on top of views, materialized views, foreign tables, inhe.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            LIMIT and OFFSET clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s cannot contain system columns.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s cannot contain non-immutable functions.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            UNION/INTERSECT/EXCEPT clauses cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            DISTINCT ON clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            TABLESAMPLE parameter cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            inheritance parent tables cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            VALUES clause cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <literal>GROUPING SETS</literal> and <literal>FILTER</literal> clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            FOR UPDATE/SHARE cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            targetlist cannot contain columns whose name start with <literal>__ivm_</literal>.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            targetlist cannot contain expressions which contain an aggregate in it.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+              Logical replication is not supported, that is, even when a base table
+               at a publisher node is modified, <acronym>IMMV</acronym>s at subscriber
+               nodes are not updated.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            When the <literal>TRUNCATE</literal> command is executed on a base table,
+               nothing is changed on the <acronym>IMMV</acronym>.
+          </para>
+        </listitem>
+
+    </itemizedlist>
+</para>
+</sect3>
+
+</sect2>
+
+<sect2>
+<title><literal>DISTINCT</literal></title>
+
+<para>
+    <productname>PostgreSQL</productname> supports <acronym>IMMV</acronym> with
+    <literal>DISTINCT</literal>.  For example, suppose a <acronym>IMMV</acronym>
+    defined with <literal>DISTINCT</literal> on a base table containing duplicate
+    tuples.  When tuples are deleted from the base table, a tuple in the view is
+    deleted if and only if the multiplicity of the tuple becomes zero.  Moreover,
+    when tuples are inserted into the base table, a tuple is inserted into the
+    view only if the same tuple doesn't already exist in it.
+</para>
+
+<para>
+    Physically, an <acronym>IMMV</acronym> defined with <literal>DISTINCT</literal>
+    contains tuples after eliminating duplicates, and the multiplicity of each tuple
+    is stored in a hidden column named <literal>__ivm_count__</literal>.
+</para>
+</sect2>
+
+<sect2>
+<title>Concurrent Transactions</title>
+<para>
+    Suppose an <acronym>IMMV</acronym> is defined on two base tables and each
+    table was modified in different a concurrent transaction simultaneously.
+    In the transaction which was committed first, <acronym>IMMV</acronym> can
+    be updated considering only the change which happened in this transaction.
+    On the other hand, in order to update the view correctly in the transaction
+    which was committed later, we need to know the changes occurred in
+    both transactions.  For this reason, <literal>ExclusiveLock</literal>
+    is held on an <acronym>IMMV</acronym> immediately after a base table is
+    modified in <literal>READ COMMITTED</literal> mode to make sure that
+    the <acronym>IMMV</acronym> is updated in the latter transaction after
+    the former transaction is committed.  In <literal>REPEATABLE READ</literal>
+    or <literal>SERIALIZABLE</literal> mode, an error is raised immediately
+    if lock acquisition fails because any changes which occurred in
+    other transactions are not be visible in these modes and
+    <acronym>IMMV</acronym> cannot be updated correctly in such situations.
+    However, as an exception if the view has only one base table, 
+    the lock held on thew view is <literal>RowExclusiveLock</literal>.
+</para>
+</sect2>
+
+<sect2>
+<title>Row Level Security</title>
+<para>
+    If some base tables have row level security policy, rows that are not visible
+    to the materialized view's owner are excluded from the result.  In addition, such
+    rows are excluded as well when views are incrementally maintained.  However, if a
+    new policy is defined or policies are changed after the materialized view was created,
+    the new policy will not be applied to the view contents.  To apply the new policy,
+    you need to refresh materialized views.
+</para>
+</sect2>
+
+</sect1>
+
 <sect1 id="rules-update">
 <title>Rules on <command>INSERT</command>, <command>UPDATE</command>, and <command>DELETE</command></title>
 
-- 
2.17.1

v25-0009-Add-regression-tests-for-Incremental-View-Mainte.patchtext/x-diff; name=v25-0009-Add-regression-tests-for-Incremental-View-Mainte.patchDownload
From 5e5bec039a30f115db744938e38ee98f46efe172 Mon Sep 17 00:00:00 2001
From: Takuma Hoshiai <takuma.hoshiai@gmail.com>
Date: Wed, 10 Mar 2021 11:11:13 +0900
Subject: [PATCH v25 09/15] Add regression tests for Incremental View
 Maintenance

---
 .../regress/expected/incremental_matview.out  | 812 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/incremental_matview.sql  | 400 +++++++++
 3 files changed, 1213 insertions(+), 1 deletion(-)
 create mode 100644 src/test/regress/expected/incremental_matview.out
 create mode 100644 src/test/regress/sql/incremental_matview.sql

diff --git a/src/test/regress/expected/incremental_matview.out b/src/test/regress/expected/incremental_matview.out
new file mode 100644
index 0000000000..7b10fcddbe
--- /dev/null
+++ b/src/test/regress/expected/incremental_matview.out
@@ -0,0 +1,812 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) WITH NO DATA;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ERROR:  materialized view "mv_ivm_1" has not been populated
+HINT:  Use the REFRESH MATERIALIZED VIEW command.
+REFRESH MATERIALIZED VIEW mv_ivm_1;
+NOTICE:  could not create an index on materialized view "mv_ivm_1" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- immediate maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- rename of IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_rename AS SELECT DISTINCT * FROM mv_base_a;
+NOTICE:  created index "mv_ivm_rename_index" on materialized view "mv_ivm_rename"
+ALTER MATERIALIZED VIEW mv_ivm_rename RENAME COLUMN __ivm_count__ TO xxx;
+ERROR:  IVM column can not be renamed
+DROP MATERIALIZED VIEW mv_ivm_rename;
+-- unique index on IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_unique AS SELECT DISTINCT * FROM mv_base_a;
+NOTICE:  created index "mv_ivm_unique_index" on materialized view "mv_ivm_unique"
+CREATE UNIQUE INDEX ON mv_ivm_unique(__ivm_count__);
+ERROR:  unique index creation on IVM columns is not supported
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__));
+ERROR:  unique index creation on IVM columns is not supported
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__ + 1));
+ERROR:  unique index creation on IVM columns is not supported
+DROP MATERIALIZED VIEW mv_ivm_unique;
+-- some query syntax
+BEGIN;
+CREATE FUNCTION ivm_func() RETURNS int LANGUAGE 'sql'
+       AS 'SELECT 1' IMMUTABLE;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_func AS SELECT * FROM ivm_func();
+NOTICE:  could not create an index on materialized view "mv_ivm_func" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_no_tbl AS SELECT 1;
+NOTICE:  could not create an index on materialized view "mv_ivm_no_tbl" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+ROLLBACK;
+-- result of materialized view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_duplicate" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+NOTICE:  created index "mv_ivm_distinct_index" on materialized view "mv_ivm_distinct"
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 20
+ 30
+ 40
+ 50
+(6 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+ROLLBACK;
+-- support SUM(), COUNT() and AVG() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i), AVG(j) FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_agg_index" on materialized view "mv_ivm_agg"
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 | 120 |     2 | 60.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg          
+---+-----+-------+----------------------
+ 1 |  10 |     1 |  10.0000000000000000
+ 2 | 220 |     2 | 110.0000000000000000
+ 3 |  30 |     1 |  30.0000000000000000
+ 4 |  40 |     1 |  40.0000000000000000
+ 5 |  50 |     1 |  50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support COUNT(*) aggregate function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(*) FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_agg_index" on materialized view "mv_ivm_agg"
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 |  20 |     1
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 | 120 |     2
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+ROLLBACK;
+-- support aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j), COUNT(j), AVG(j) FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_group" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count |         avg         
+-----+-------+---------------------
+ 150 |     5 | 30.0000000000000000
+(1 row)
+
+INSERT INTO mv_base_a VALUES(6,60);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count |         avg         
+-----+-------+---------------------
+ 210 |     6 | 35.0000000000000000
+(1 row)
+
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count | avg 
+-----+-------+-----
+     |     0 |    
+(1 row)
+
+ROLLBACK;
+-- resolved issue: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+NOTICE:  created index "mv_ivm_avg_bug_index" on materialized view "mv_ivm_avg_bug"
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     3 |  3.3333333333333333
+ 2 |  80 |     3 | 26.6666666666666667
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support MIN(), MAX() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT i, MIN(j), MAX(j)  FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_min_max_index" on materialized view "mv_ivm_min_max"
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  10 |  10
+ 2 |  20 |  20
+ 3 |  30 |  30
+ 4 |  40 |  40
+ 5 |  50 |  50
+(5 rows)
+
+INSERT INTO mv_base_a VALUES
+  (1,11), (1,12),
+  (2,21), (2,22),
+  (3,31), (3,32),
+  (4,41), (4,42),
+  (5,51), (5,52);
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  10 |  12
+ 2 |  20 |  22
+ 3 |  30 |  32
+ 4 |  40 |  42
+ 5 |  50 |  52
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  11 |  12
+ 2 |  20 |  22
+ 3 |  30 |  31
+ 4 |  40 |  42
+ 5 |  50 |  52
+(5 rows)
+
+ROLLBACK;
+-- support MIN(), MAX() aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT MIN(j), MAX(j)  FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_min_max" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+  10 |  50
+(1 row)
+
+INSERT INTO mv_base_a VALUES
+  (0,0), (6,60), (7,70);
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+   0 |  70
+(1 row)
+
+DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+  10 |  60
+(1 row)
+
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+     |    
+(1 row)
+
+ROLLBACK;
+-- support self join view and multiple change on the same table
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1, 10), (2, 20), (3, 30);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_self(v1, v2) AS
+ SELECT t1.v, t2.v FROM base_t AS t1 JOIN base_t AS t2 ON t1.i = t2.i;
+NOTICE:  could not create an index on materialized view "mv_self" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_self ORDER BY v1;
+ v1 | v2 
+----+----
+ 10 | 10
+ 20 | 20
+ 30 | 30
+(3 rows)
+
+INSERT INTO base_t VALUES (4,40);
+DELETE FROM base_t WHERE i = 1;
+UPDATE base_t SET v = v*10 WHERE i=2;
+SELECT * FROM mv_self ORDER BY v1;
+ v1  | v2  
+-----+-----
+  30 |  30
+  40 |  40
+ 200 | 200
+(3 rows)
+
+WITH
+ ins_t1 AS (INSERT INTO base_t VALUES (5,50) RETURNING 1),
+ ins_t2 AS (INSERT INTO base_t VALUES (6,60) RETURNING 1),
+ upd_t AS (UPDATE base_t SET v = v + 100  RETURNING 1),
+ dlt_t AS (DELETE FROM base_t WHERE i IN (4,5)  RETURNING 1)
+SELECT NULL;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT * FROM mv_self ORDER BY v1;
+ v1  | v2  
+-----+-----
+  50 |  50
+  60 |  60
+ 130 | 130
+ 300 | 300
+(4 rows)
+
+ROLLBACK;
+-- support simultaneous table changes
+BEGIN;
+CREATE TABLE base_r (i int, v int);
+CREATE TABLE base_s (i int, v int);
+INSERT INTO base_r VALUES (1, 10), (2, 20), (3, 30);
+INSERT INTO base_s VALUES (1, 100), (2, 200), (3, 300);
+CREATE INCREMENTAL MATERIALIZED VIEW mv(v1, v2) AS
+ SELECT r.v, s.v FROM base_r AS r JOIN base_s AS s USING(i);
+NOTICE:  could not create an index on materialized view "mv" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY v1;
+ v1 | v2  
+----+-----
+ 10 | 100
+ 20 | 200
+ 30 | 300
+(3 rows)
+
+WITH
+ ins_r AS (INSERT INTO base_r VALUES (1,11) RETURNING 1),
+ ins_r2 AS (INSERT INTO base_r VALUES (3,33) RETURNING 1),
+ ins_s AS (INSERT INTO base_s VALUES (2,222) RETURNING 1),
+ upd_r AS (UPDATE base_r SET v = v + 1000 WHERE i = 2 RETURNING 1),
+ dlt_s AS (DELETE FROM base_s WHERE i = 3 RETURNING 1)
+SELECT NULL;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT * FROM mv ORDER BY v1;
+  v1  | v2  
+------+-----
+   10 | 100
+   11 | 100
+ 1020 | 200
+ 1020 | 222
+(4 rows)
+
+ROLLBACK;
+-- support foreign reference constraints
+BEGIN;
+CREATE TABLE ri1 (i int PRIMARY KEY);
+CREATE TABLE ri2 (i int PRIMARY KEY REFERENCES ri1(i) ON UPDATE CASCADE ON DELETE CASCADE, v int);
+INSERT INTO ri1 VALUES (1),(2),(3);
+INSERT INTO ri2 VALUES (1),(2),(3);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ri(i1, i2) AS
+ SELECT ri1.i, ri2.i FROM ri1 JOIN ri2 USING(i);
+NOTICE:  created index "mv_ri_index" on materialized view "mv_ri"
+SELECT * FROM mv_ri ORDER BY i1;
+ i1 | i2 
+----+----
+  1 |  1
+  2 |  2
+  3 |  3
+(3 rows)
+
+UPDATE ri1 SET i=10 where i=1;
+DELETE FROM ri1 WHERE i=2;
+SELECT * FROM mv_ri ORDER BY i2;
+ i1 | i2 
+----+----
+  3 |  3
+ 10 | 10
+(2 rows)
+
+ROLLBACK;
+-- views including NULL
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1,10),(2, NULL);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+NOTICE:  could not create an index on materialized view "mv" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY i;
+ i | v  
+---+----
+ 1 | 10
+ 2 |   
+(2 rows)
+
+UPDATE base_t SET v = 20 WHERE i = 2;
+SELECT * FROM mv ORDER BY i;
+ i | v  
+---+----
+ 1 | 10
+ 2 | 20
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+NOTICE:  could not create an index on materialized view "mv" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY i;
+ i 
+---
+(0 rows)
+
+INSERT INTO base_t VALUES (1),(NULL);
+SELECT * FROM mv ORDER BY i;
+ i 
+---
+ 1
+  
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (1, 10), (1, 20);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, sum(v) FROM base_t GROUP BY i;
+NOTICE:  created index "mv_index" on materialized view "mv"
+SELECT * FROM mv ORDER BY i;
+ i | sum 
+---+-----
+ 1 |  30
+   |   3
+(2 rows)
+
+UPDATE base_t SET v = v * 10;
+SELECT * FROM mv ORDER BY i;
+ i | sum 
+---+-----
+ 1 | 300
+   |  30
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (NULL, 3), (NULL, 4), (NULL, 5);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, min(v), max(v) FROM base_t GROUP BY i;
+NOTICE:  created index "mv_index" on materialized view "mv"
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   1 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 1;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 3;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 5;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   4
+(1 row)
+
+ROLLBACK;
+-- IMMV containing user defined type
+BEGIN;
+CREATE TYPE mytype;
+CREATE FUNCTION mytype_in(cstring)
+ RETURNS mytype AS 'int4in'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+NOTICE:  return type mytype is only a shell
+CREATE FUNCTION mytype_out(mytype)
+ RETURNS cstring AS 'int4out'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+NOTICE:  argument type mytype is only a shell
+CREATE TYPE mytype (
+ LIKE = int4,
+ INPUT = mytype_in,
+ OUTPUT = mytype_out
+);
+CREATE FUNCTION mytype_eq(mytype, mytype)
+ RETURNS bool AS 'int4eq'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_lt(mytype, mytype)
+ RETURNS bool AS 'int4lt'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_cmp(mytype, mytype)
+ RETURNS integer AS 'btint4cmp'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE OPERATOR = (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_eq);
+CREATE OPERATOR < (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_lt);
+CREATE OPERATOR CLASS mytype_ops
+ DEFAULT FOR TYPE mytype USING btree AS
+ OPERATOR        1       <,
+ OPERATOR        3       = ,
+ FUNCTION		1		mytype_cmp(mytype,mytype);
+CREATE TABLE t_mytype (x mytype);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_mytype AS
+ SELECT * FROM t_mytype;
+NOTICE:  could not create an index on materialized view "mv_mytype" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+INSERT INTO t_mytype VALUES ('1'::mytype);
+SELECT * FROM mv_mytype;
+ x 
+---
+ 1
+(1 row)
+
+ROLLBACK;
+-- outer join is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i;
+ERROR:  OUTER JOIN is not supported on incrementally maintainable materialized view
+-- CTE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS
+    WITH b AS ( SELECT * FROM mv_base_b) SELECT a.i,a.j FROM mv_base_a a, b WHERE a.i = b.i;
+ERROR:  CTE is not supported on incrementally maintainable materialized view
+-- contain system column
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm01 AS SELECT i,j,xmin FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT i,j,xmin::text AS x_min FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm06 AS SELECT i,j,xidsend(xmin) AS x_min FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+-- contain subquery
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm03 AS SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT * FROM mv_base_b) b WHERE a.i = b.i;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm05 AS SELECT i,j, (SELECT k FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+-- contain ORDER BY
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm07 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) ORDER BY i,j,k;
+ERROR:  ORDER BY clause is not supported on incrementally maintainable materialized view
+-- contain HAVING
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm08 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) GROUP BY i,j,k HAVING SUM(i) > 5;
+ERROR:   HAVING clause is not supported on incrementally maintainable materialized view
+-- contain view or materialized view
+CREATE VIEW b_view AS SELECT i,k FROM mv_base_b;
+CREATE MATERIALIZED VIEW b_mview AS SELECT i,k FROM mv_base_b;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm07 AS SELECT a.i,a.j FROM mv_base_a a,b_view b WHERE a.i = b.i;
+ERROR:  VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm08 AS SELECT a.i,a.j FROM mv_base_a a,b_mview b WHERE a.i = b.i;
+ERROR:  VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm09 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm10 AS SELECT a.i,a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) OR a.i > 5;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+-- contain mutable functions
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+ERROR:  mutable function is not supported on incrementally maintainable materialized view
+HINT:  functions must be marked IMMUTABLE
+-- LIMIT/OFFSET is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm13 AS SELECT i,j FROM mv_base_a LIMIT 10 OFFSET 5;
+ERROR:  LIMIT/OFFSET clause is not supported on incrementally maintainable materialized view
+-- DISTINCT ON is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm14 AS SELECT DISTINCT ON(i) i, j FROM mv_base_a;
+ERROR:  DISTINCT ON is not supported on incrementally maintainable materialized view
+-- TABLESAMPLE clause is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm15 AS SELECT i, j FROM mv_base_a TABLESAMPLE SYSTEM(50);
+ERROR:  TABLESAMPLE clause is not supported on incrementally maintainable materialized view
+-- window functions are not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm16 AS SELECT *, cume_dist() OVER (ORDER BY i) AS rank FROM mv_base_a;
+ERROR:  window functions are not supported on incrementally maintainable materialized view
+-- aggregate function with some options is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm17 AS SELECT COUNT(*) FILTER(WHERE i < 3) FROM mv_base_a;
+ERROR:  aggregate function with FILTER clause is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm18 AS SELECT COUNT(DISTINCT i)  FROM mv_base_a;
+ERROR:  aggregate function with DISTINCT arguments is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm19 AS SELECT array_agg(j ORDER BY i DESC) FROM mv_base_a;
+ERROR:  aggregate function with ORDER clause is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm20 AS SELECT i,SUM(j) FROM mv_base_a GROUP BY GROUPING SETS((i),());
+ERROR:  GROUPING SETS, ROLLUP, or CUBE clauses is not supported on incrementally maintainable materialized view
+-- inheritance parent is not supported
+BEGIN;
+CREATE TABLE parent (i int, v int);
+CREATE TABLE child_a(options text) INHERITS(parent);
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm21 AS SELECT * FROM parent;
+ERROR:  inheritance parent is not supported on incrementally maintainable materialized view
+ROLLBACK;
+-- UNION statement is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm22 AS SELECT i,j FROM mv_base_a UNION ALL SELECT i,k FROM mv_base_b;;
+ERROR:  UNION/INTERSECT/EXCEPT statements are not supported on incrementally maintainable materialized view
+-- empty target list is not allowed with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm25 AS SELECT FROM mv_base_a;
+ERROR:  empty target list is not supported on incrementally maintainable materialized view
+-- FOR UPDATE/SHARE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm26 AS SELECT i,j FROM mv_base_a FOR UPDATE;
+ERROR:  FOR UPDATE/SHARE clause is not supported on incrementally maintainable materialized view
+-- tartget list cannot contain ivm column that start with '__ivm'
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm28 AS SELECT i AS "__ivm_count__" FROM mv_base_a;
+ERROR:  column name __ivm_count__ is not supported on incrementally maintainable materialized view
+-- expressions specified in GROUP BY must appear in the target list.
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm29 AS SELECT COUNT(i) FROM mv_base_a GROUP BY i;
+ERROR:  GROUP BY expression not appearing in select list is not supported on incrementally maintainable materialized view
+-- experssions containing an aggregate is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm30 AS SELECT sum(i)*0.5 FROM mv_base_a;
+ERROR:  expression containing an aggregate in it is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm31 AS SELECT sum(i)/sum(j) FROM mv_base_a;
+ERROR:  expression containing an aggregate in it is not supported on incrementally maintainable materialized view
+-- VALUES is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_only_values1 AS values(1);
+ERROR:  VALUES is not supported on incrementally maintainable materialized view
+-- base table which has row level security
+DROP USER IF EXISTS ivm_admin;
+NOTICE:  role "ivm_admin" does not exist, skipping
+DROP USER IF EXISTS ivm_user;
+NOTICE:  role "ivm_user" does not exist, skipping
+CREATE USER ivm_admin;
+CREATE USER ivm_user;
+SET SESSION AUTHORIZATION ivm_admin;
+CREATE TABLE rls_tbl(id int, data text, owner name);
+INSERT INTO rls_tbl VALUES
+  (1,'foo','ivm_user'),
+  (2,'bar','postgres');
+CREATE TABLE num_tbl(id int, num text);
+INSERT INTO num_tbl VALUES
+  (1,'one'),
+  (2,'two'),
+  (3,'three'),
+  (4,'four');
+CREATE POLICY rls_tbl_policy ON rls_tbl FOR SELECT TO PUBLIC USING(owner = current_user);
+CREATE POLICY rls_tbl_policy2 ON rls_tbl FOR INSERT TO PUBLIC WITH CHECK(current_user LIKE 'ivm_%');
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+GRANT ALL on rls_tbl TO PUBLIC;
+GRANT ALL on num_tbl TO PUBLIC;
+SET SESSION AUTHORIZATION ivm_user;
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls AS SELECT * FROM rls_tbl;
+NOTICE:  could not create an index on materialized view "ivm_rls" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+ id | data |  owner   
+----+------+----------
+  1 | foo  | ivm_user
+(1 row)
+
+INSERT INTO rls_tbl VALUES
+  (3,'baz','ivm_user'),
+  (4,'qux','postgres');
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+ id | data |  owner   
+----+------+----------
+  1 | foo  | ivm_user
+  3 | baz  | ivm_user
+(2 rows)
+
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls2 AS SELECT * FROM rls_tbl JOIN num_tbl USING(id);
+NOTICE:  could not create an index on materialized view "ivm_rls2" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+RESET SESSION AUTHORIZATION;
+WITH
+ x AS (UPDATE rls_tbl SET data = data || '_2' where id in (3,4)),
+ y AS (UPDATE num_tbl SET num = num || '_2' where id in (3,4))
+SELECT;
+--
+(1 row)
+
+SELECT * FROM ivm_rls2 ORDER BY 1,2,3;
+ id | data  |  owner   |   num   
+----+-------+----------+---------
+  1 | foo   | ivm_user | one
+  3 | baz_2 | ivm_user | three_2
+(2 rows)
+
+DROP TABLE rls_tbl CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to materialized view ivm_rls
+drop cascades to materialized view ivm_rls2
+DROP TABLE num_tbl CASCADE;
+DROP USER ivm_user;
+DROP USER ivm_admin;
+DROP TABLE mv_base_b CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to materialized view mv_ivm_1
+drop cascades to view b_view
+drop cascades to materialized view b_mview
+DROP TABLE mv_base_a CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 861c30a73a..a838d176dd 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -89,7 +89,7 @@ test: brin_bloom brin_multi
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort create_role
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort create_role incremental_matview
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/sql/incremental_matview.sql b/src/test/regress/sql/incremental_matview.sql
new file mode 100644
index 0000000000..311e9b96fb
--- /dev/null
+++ b/src/test/regress/sql/incremental_matview.sql
@@ -0,0 +1,400 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) WITH NO DATA;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+REFRESH MATERIALIZED VIEW mv_ivm_1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+-- immediate maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+
+-- rename of IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_rename AS SELECT DISTINCT * FROM mv_base_a;
+ALTER MATERIALIZED VIEW mv_ivm_rename RENAME COLUMN __ivm_count__ TO xxx;
+DROP MATERIALIZED VIEW mv_ivm_rename;
+
+-- unique index on IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_unique AS SELECT DISTINCT * FROM mv_base_a;
+CREATE UNIQUE INDEX ON mv_ivm_unique(__ivm_count__);
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__));
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__ + 1));
+DROP MATERIALIZED VIEW mv_ivm_unique;
+
+-- some query syntax
+BEGIN;
+CREATE FUNCTION ivm_func() RETURNS int LANGUAGE 'sql'
+       AS 'SELECT 1' IMMUTABLE;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_func AS SELECT * FROM ivm_func();
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_no_tbl AS SELECT 1;
+ROLLBACK;
+
+-- result of materialized view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ROLLBACK;
+
+-- support SUM(), COUNT() and AVG() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i), AVG(j) FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ROLLBACK;
+
+-- support COUNT(*) aggregate function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(*) FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j), COUNT(j), AVG(j) FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+INSERT INTO mv_base_a VALUES(6,60);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ROLLBACK;
+
+-- resolved issue: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support MIN(), MAX() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT i, MIN(j), MAX(j)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES
+  (1,11), (1,12),
+  (2,21), (2,22),
+  (3,31), (3,32),
+  (4,41), (4,42),
+  (5,51), (5,52);
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support MIN(), MAX() aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT MIN(j), MAX(j)  FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+INSERT INTO mv_base_a VALUES
+  (0,0), (6,60), (7,70);
+SELECT * FROM mv_ivm_min_max;
+DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
+SELECT * FROM mv_ivm_min_max;
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+ROLLBACK;
+
+-- support self join view and multiple change on the same table
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1, 10), (2, 20), (3, 30);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_self(v1, v2) AS
+ SELECT t1.v, t2.v FROM base_t AS t1 JOIN base_t AS t2 ON t1.i = t2.i;
+SELECT * FROM mv_self ORDER BY v1;
+INSERT INTO base_t VALUES (4,40);
+DELETE FROM base_t WHERE i = 1;
+UPDATE base_t SET v = v*10 WHERE i=2;
+SELECT * FROM mv_self ORDER BY v1;
+WITH
+ ins_t1 AS (INSERT INTO base_t VALUES (5,50) RETURNING 1),
+ ins_t2 AS (INSERT INTO base_t VALUES (6,60) RETURNING 1),
+ upd_t AS (UPDATE base_t SET v = v + 100  RETURNING 1),
+ dlt_t AS (DELETE FROM base_t WHERE i IN (4,5)  RETURNING 1)
+SELECT NULL;
+SELECT * FROM mv_self ORDER BY v1;
+ROLLBACK;
+
+-- support simultaneous table changes
+BEGIN;
+CREATE TABLE base_r (i int, v int);
+CREATE TABLE base_s (i int, v int);
+INSERT INTO base_r VALUES (1, 10), (2, 20), (3, 30);
+INSERT INTO base_s VALUES (1, 100), (2, 200), (3, 300);
+CREATE INCREMENTAL MATERIALIZED VIEW mv(v1, v2) AS
+ SELECT r.v, s.v FROM base_r AS r JOIN base_s AS s USING(i);
+SELECT * FROM mv ORDER BY v1;
+WITH
+ ins_r AS (INSERT INTO base_r VALUES (1,11) RETURNING 1),
+ ins_r2 AS (INSERT INTO base_r VALUES (3,33) RETURNING 1),
+ ins_s AS (INSERT INTO base_s VALUES (2,222) RETURNING 1),
+ upd_r AS (UPDATE base_r SET v = v + 1000 WHERE i = 2 RETURNING 1),
+ dlt_s AS (DELETE FROM base_s WHERE i = 3 RETURNING 1)
+SELECT NULL;
+SELECT * FROM mv ORDER BY v1;
+ROLLBACK;
+
+-- support foreign reference constraints
+BEGIN;
+CREATE TABLE ri1 (i int PRIMARY KEY);
+CREATE TABLE ri2 (i int PRIMARY KEY REFERENCES ri1(i) ON UPDATE CASCADE ON DELETE CASCADE, v int);
+INSERT INTO ri1 VALUES (1),(2),(3);
+INSERT INTO ri2 VALUES (1),(2),(3);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ri(i1, i2) AS
+ SELECT ri1.i, ri2.i FROM ri1 JOIN ri2 USING(i);
+SELECT * FROM mv_ri ORDER BY i1;
+UPDATE ri1 SET i=10 where i=1;
+DELETE FROM ri1 WHERE i=2;
+SELECT * FROM mv_ri ORDER BY i2;
+ROLLBACK;
+
+-- views including NULL
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1,10),(2, NULL);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+SELECT * FROM mv ORDER BY i;
+UPDATE base_t SET v = 20 WHERE i = 2;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+SELECT * FROM mv ORDER BY i;
+INSERT INTO base_t VALUES (1),(NULL);
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (1, 10), (1, 20);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, sum(v) FROM base_t GROUP BY i;
+SELECT * FROM mv ORDER BY i;
+UPDATE base_t SET v = v * 10;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (NULL, 3), (NULL, 4), (NULL, 5);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, min(v), max(v) FROM base_t GROUP BY i;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 1;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 3;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 5;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+-- IMMV containing user defined type
+BEGIN;
+
+CREATE TYPE mytype;
+CREATE FUNCTION mytype_in(cstring)
+ RETURNS mytype AS 'int4in'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_out(mytype)
+ RETURNS cstring AS 'int4out'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE TYPE mytype (
+ LIKE = int4,
+ INPUT = mytype_in,
+ OUTPUT = mytype_out
+);
+
+CREATE FUNCTION mytype_eq(mytype, mytype)
+ RETURNS bool AS 'int4eq'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_lt(mytype, mytype)
+ RETURNS bool AS 'int4lt'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_cmp(mytype, mytype)
+ RETURNS integer AS 'btint4cmp'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+
+CREATE OPERATOR = (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_eq);
+CREATE OPERATOR < (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_lt);
+
+CREATE OPERATOR CLASS mytype_ops
+ DEFAULT FOR TYPE mytype USING btree AS
+ OPERATOR        1       <,
+ OPERATOR        3       = ,
+ FUNCTION		1		mytype_cmp(mytype,mytype);
+
+CREATE TABLE t_mytype (x mytype);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_mytype AS
+ SELECT * FROM t_mytype;
+INSERT INTO t_mytype VALUES ('1'::mytype);
+SELECT * FROM mv_mytype;
+
+ROLLBACK;
+
+-- outer join is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i;
+-- CTE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS
+    WITH b AS ( SELECT * FROM mv_base_b) SELECT a.i,a.j FROM mv_base_a a, b WHERE a.i = b.i;
+-- contain system column
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm01 AS SELECT i,j,xmin FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT i,j,xmin::text AS x_min FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm06 AS SELECT i,j,xidsend(xmin) AS x_min FROM mv_base_a;
+-- contain subquery
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm03 AS SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT * FROM mv_base_b) b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm05 AS SELECT i,j, (SELECT k FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+-- contain ORDER BY
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm07 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) ORDER BY i,j,k;
+-- contain HAVING
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm08 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) GROUP BY i,j,k HAVING SUM(i) > 5;
+
+-- contain view or materialized view
+CREATE VIEW b_view AS SELECT i,k FROM mv_base_b;
+CREATE MATERIALIZED VIEW b_mview AS SELECT i,k FROM mv_base_b;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm07 AS SELECT a.i,a.j FROM mv_base_a a,b_view b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm08 AS SELECT a.i,a.j FROM mv_base_a a,b_mview b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm09 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm10 AS SELECT a.i,a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) OR a.i > 5;
+
+-- contain mutable functions
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+
+-- LIMIT/OFFSET is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm13 AS SELECT i,j FROM mv_base_a LIMIT 10 OFFSET 5;
+
+-- DISTINCT ON is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm14 AS SELECT DISTINCT ON(i) i, j FROM mv_base_a;
+
+-- TABLESAMPLE clause is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm15 AS SELECT i, j FROM mv_base_a TABLESAMPLE SYSTEM(50);
+
+-- window functions are not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm16 AS SELECT *, cume_dist() OVER (ORDER BY i) AS rank FROM mv_base_a;
+
+-- aggregate function with some options is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm17 AS SELECT COUNT(*) FILTER(WHERE i < 3) FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm18 AS SELECT COUNT(DISTINCT i)  FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm19 AS SELECT array_agg(j ORDER BY i DESC) FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm20 AS SELECT i,SUM(j) FROM mv_base_a GROUP BY GROUPING SETS((i),());
+
+-- inheritance parent is not supported
+BEGIN;
+CREATE TABLE parent (i int, v int);
+CREATE TABLE child_a(options text) INHERITS(parent);
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm21 AS SELECT * FROM parent;
+ROLLBACK;
+
+-- UNION statement is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm22 AS SELECT i,j FROM mv_base_a UNION ALL SELECT i,k FROM mv_base_b;;
+
+-- empty target list is not allowed with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm25 AS SELECT FROM mv_base_a;
+
+-- FOR UPDATE/SHARE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm26 AS SELECT i,j FROM mv_base_a FOR UPDATE;
+
+-- tartget list cannot contain ivm column that start with '__ivm'
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm28 AS SELECT i AS "__ivm_count__" FROM mv_base_a;
+
+-- expressions specified in GROUP BY must appear in the target list.
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm29 AS SELECT COUNT(i) FROM mv_base_a GROUP BY i;
+
+-- experssions containing an aggregate is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm30 AS SELECT sum(i)*0.5 FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm31 AS SELECT sum(i)/sum(j) FROM mv_base_a;
+
+-- VALUES is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_only_values1 AS values(1);
+
+-- base table which has row level security
+DROP USER IF EXISTS ivm_admin;
+DROP USER IF EXISTS ivm_user;
+CREATE USER ivm_admin;
+CREATE USER ivm_user;
+SET SESSION AUTHORIZATION ivm_admin;
+
+CREATE TABLE rls_tbl(id int, data text, owner name);
+INSERT INTO rls_tbl VALUES
+  (1,'foo','ivm_user'),
+  (2,'bar','postgres');
+CREATE TABLE num_tbl(id int, num text);
+INSERT INTO num_tbl VALUES
+  (1,'one'),
+  (2,'two'),
+  (3,'three'),
+  (4,'four');
+CREATE POLICY rls_tbl_policy ON rls_tbl FOR SELECT TO PUBLIC USING(owner = current_user);
+CREATE POLICY rls_tbl_policy2 ON rls_tbl FOR INSERT TO PUBLIC WITH CHECK(current_user LIKE 'ivm_%');
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+GRANT ALL on rls_tbl TO PUBLIC;
+GRANT ALL on num_tbl TO PUBLIC;
+
+SET SESSION AUTHORIZATION ivm_user;
+
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls AS SELECT * FROM rls_tbl;
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+INSERT INTO rls_tbl VALUES
+  (3,'baz','ivm_user'),
+  (4,'qux','postgres');
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls2 AS SELECT * FROM rls_tbl JOIN num_tbl USING(id);
+
+RESET SESSION AUTHORIZATION;
+
+WITH
+ x AS (UPDATE rls_tbl SET data = data || '_2' where id in (3,4)),
+ y AS (UPDATE num_tbl SET num = num || '_2' where id in (3,4))
+SELECT;
+SELECT * FROM ivm_rls2 ORDER BY 1,2,3;
+
+DROP TABLE rls_tbl CASCADE;
+DROP TABLE num_tbl CASCADE;
+DROP USER ivm_user;
+DROP USER ivm_admin;
+
+DROP TABLE mv_base_b CASCADE;
+DROP TABLE mv_base_a CASCADE;
-- 
2.17.1

v25-0008-Add-aggregates-support-in-IVM.patchtext/x-diff; name=v25-0008-Add-aggregates-support-in-IVM.patchDownload
From 86c176d94eb621dca8e19d75a4a081cc21b227fb Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Mon, 2 Aug 2021 14:59:27 +0900
Subject: [PATCH v25 08/15] Add aggregates support in IVM
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Currently, count, sum, avg, min and max are supported.

As a restriction, expressions specified in GROUP BY must appear in
the target list because tuples to be updated in IMMV are identified
by using this group key. However, in the case of aggregates without
GROUP BY, there is only one tuple in the view, so keys are not uses
to identify tuples.

When creating a IMMV, in addition to __ivm_count column, some hidden
columns for each aggregate are added to the target list. For example,
names of these hidden columns are ivm_count_avg and ivm_sum_avg for
the average function, and so on.

In the case of views without aggregate functions, only the number of
tuple multiplicities in __ivm_count__ column are updated at incremental
maintenance. On the other hand, in the case of view with aggregates,
the aggregated values and related hidden columns are also updated. The
way of update depends the kind of aggregate function. Specifically,
sum and count are updated by simply adding or subtracting delta value
calculated from delta tables. avg is updated by using values of sum
and count stored in views as hidden columns and deltas calculated
from delta tables.

In min or max cases, it becomes more complicated. For an example of min,
when tuples are inserted, the smaller value between the current min value
in the view and the value calculated from the new delta table is used.
When tuples are deleted, if the current min value in the view equals to
the min in the old delta table, we need re-computation the latest min
value from base tables. Otherwise, the current value in the view remains.

As to sum, avg, min, and max (any aggregate functions except count),
NULL in input values is ignored, and this returns a null value when no
rows are selected. To support this specification, the number of not-NULL
input values is counted and stored in views as a hidden column. In the
case of count(), count(x) returns zero when no rows are selected, and
count(*) doesn't ignore NULL input. These specification are also supported.
---
 src/backend/commands/createas.c |  299 ++++++++-
 src/backend/commands/matview.c  | 1016 ++++++++++++++++++++++++++++++-
 src/include/commands/createas.h |    1 +
 3 files changed, 1281 insertions(+), 35 deletions(-)

diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 4b73965e56..c58cacec05 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/regproc.h"
 #include "utils/rel.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
@@ -80,6 +81,11 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_intorel;
 
+typedef struct
+{
+	bool	has_agg;
+} check_ivm_restriction_context;
+
 /* utility functions for CTAS definition creation */
 static ObjectAddress create_ctas_internal(List *attrList, IntoClause *into);
 static ObjectAddress create_ctas_nodata(List *tlist, IntoClause *into);
@@ -94,8 +100,9 @@ static void CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid mat
 									 Relids *relids, bool ex_lock);
 static void CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock);
 static void check_ivm_restriction(Node *node);
-static bool check_ivm_restriction_walker(Node *node, void *context);
+static bool check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context);
 static Bitmapset *get_primary_key_attnos_from_query(Query *qry, List **constraintList);
+static bool check_aggregate_supports_ivm(Oid aggfnoid);
 
 /*
  * create_ctas_internal
@@ -431,6 +438,7 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
  * rewriteQueryForIMMV -- rewrite view definition query for IMMV
  *
  * count(*) is added for counting distinct tuples in views.
+ * Also, additional hidden columns are added for aggregate values.
  */
 Query *
 rewriteQueryForIMMV(Query *query, List *colNames)
@@ -445,14 +453,46 @@ rewriteQueryForIMMV(Query *query, List *colNames)
 	rewritten = copyObject(query);
 	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
 
-	/*
-	 * Convert DISTINCT to GROUP BY and add count(*) for counting distinct
-	 * tuples in views.
-	 */
-	if (rewritten->distinctClause)
+	/* group keys must be in targetlist */
+	if (rewritten->groupClause)
 	{
+		ListCell *lc;
+		foreach(lc, rewritten->groupClause)
+		{
+			SortGroupClause *scl = (SortGroupClause *) lfirst(lc);
+			TargetEntry *tle = get_sortgroupclause_tle(scl, rewritten->targetList);
+
+			if (tle->resjunk)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("GROUP BY expression not appearing in select list is not supported on incrementally maintainable materialized view")));
+		}
+	}
+	/* Convert DISTINCT to GROUP BY.  count(*) will be added afterward. */
+	else if (!rewritten->hasAggs && rewritten->distinctClause)
 		rewritten->groupClause = transformDistinctClause(NULL, &rewritten->targetList, rewritten->sortClause, false);
 
+	/* Add additional columns for aggregate values */
+	if (rewritten->hasAggs)
+	{
+		ListCell *lc;
+		List *aggs = NIL;
+		AttrNumber next_resno = list_length(rewritten->targetList) + 1;
+
+		foreach(lc, rewritten->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+			char *resname = (colNames == NIL ? tle->resname : strVal(list_nth(colNames, tle->resno - 1)));
+
+			if (IsA(tle->expr, Aggref))
+				makeIvmAggColumn(pstate, (Aggref *)tle->expr, resname, &next_resno, &aggs);
+		}
+		rewritten->targetList = list_concat(rewritten->targetList, aggs);
+	}
+
+	/* Add count(*) for counting distinct tuples in views */
+	if (rewritten->distinctClause || rewritten->hasAggs)
+	{
 		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
 		fn->agg_star = true;
 
@@ -469,6 +509,91 @@ rewriteQueryForIMMV(Query *query, List *colNames)
 	return rewritten;
 }
 
+/*
+ * makeIvmAggColumn -- make additional aggregate columns for IVM
+ *
+ * For an aggregate column specified by aggref, additional aggregate columns
+ * are added, which are used to calculate the new aggregate value in IMMV.
+ * An additional aggregate columns has a name based on resname
+ * (ex. ivm_count_resname), and resno specified by next_resno. The created
+ * columns are returned to aggs, and the resno for the next column is also
+ * returned to next_resno.
+ *
+ * Currently, an additional count() is created for aggref other than count.
+ * In addition, sum() is created for avg aggregate column.
+ */
+void
+makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber *next_resno, List **aggs)
+{
+	TargetEntry *tle_count;
+	Node *node;
+	FuncCall *fn;
+	Const	*dmy_arg = makeConst(INT4OID,
+								 -1,
+								 InvalidOid,
+								 sizeof(int32),
+								 Int32GetDatum(1),
+								 false,
+								 true); /* pass by value */
+	const char *aggname = get_func_name(aggref->aggfnoid);
+
+	/*
+	 * For aggregate functions except count, add count() func with the same arg parameters.
+	 * This count result is used for determining if the aggregate value should be NULL or not.
+	 * Also, add sum() func for avg because we need to calculate an average value as sum/count.
+	 *
+	 * XXX: If there are same expressions explicitly in the target list, we can use this instead
+	 * of adding new duplicated one.
+	 */
+	if (strcmp(aggname, "count") != 0)
+	{
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+
+		/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+		node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+		((Aggref *)node)->args = aggref->args;
+
+		tle_count = makeTargetEntry((Expr *) node,
+									*next_resno,
+									pstrdup(makeObjectName("__ivm_count",resname, "_")),
+									false);
+		*aggs = lappend(*aggs, tle_count);
+		(*next_resno)++;
+	}
+	if (strcmp(aggname, "avg") == 0)
+	{
+		List *dmy_args = NIL;
+		ListCell *lc;
+		foreach(lc, aggref->aggargtypes)
+		{
+			Oid		typeid = lfirst_oid(lc);
+			Type	type = typeidType(typeid);
+
+			Const *con = makeConst(typeid,
+								   -1,
+								   typeTypeCollation(type),
+								   typeLen(type),
+								   (Datum) 0,
+								   true,
+								   typeByVal(type));
+			dmy_args = lappend(dmy_args, con);
+			ReleaseSysCache(type);
+		}
+		fn = makeFuncCall(list_make1(makeString("sum")), NIL, COERCE_EXPLICIT_CALL, -1);
+
+		/* Make a Func with dummy args, and then override this by the original agg's args. */
+		node = ParseFuncOrColumn(pstate, fn->funcname, dmy_args, NULL, fn, false, -1);
+		((Aggref *)node)->args = aggref->args;
+
+		tle_count = makeTargetEntry((Expr *) node,
+									*next_resno,
+									pstrdup(makeObjectName("__ivm_sum",resname, "_")),
+									false);
+		*aggs = lappend(*aggs, tle_count);
+		(*next_resno)++;
+	}
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -922,11 +1047,13 @@ CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock
 static void
 check_ivm_restriction(Node *node)
 {
-	check_ivm_restriction_walker(node, NULL);
+	check_ivm_restriction_context context = {false};
+
+	check_ivm_restriction_walker(node, &context);
 }
 
 static bool
-check_ivm_restriction_walker(Node *node, void *context)
+check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context)
 {
 	if (node == NULL)
 		return false;
@@ -1007,6 +1134,8 @@ check_ivm_restriction_walker(Node *node, void *context)
 					}
 				}
 
+				context->has_agg |= qry->hasAggs;
+
 				/* restrictions for rtable */
 				foreach(lc, qry->rtable)
 				{
@@ -1055,7 +1184,7 @@ check_ivm_restriction_walker(Node *node, void *context)
 
 				}
 
-				query_tree_walker(qry, check_ivm_restriction_walker, NULL, QTW_IGNORE_RANGE_TABLE);
+				query_tree_walker(qry, check_ivm_restriction_walker, (void *) context, QTW_IGNORE_RANGE_TABLE);
 
 				break;
 			}
@@ -1066,8 +1195,12 @@ check_ivm_restriction_walker(Node *node, void *context)
 						ereport(ERROR,
 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								 errmsg("column name %s is not supported on incrementally maintainable materialized view", tle->resname)));
+				if (context->has_agg && !IsA(tle->expr, Aggref) && contain_aggs_of_level((Node *) tle->expr, 0))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("expression containing an aggregate in it is not supported on incrementally maintainable materialized view")));
 
-				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
 				break;
 			}
 		case T_JoinExpr:
@@ -1079,15 +1212,128 @@ check_ivm_restriction_walker(Node *node, void *context)
 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								 errmsg("OUTER JOIN is not supported on incrementally maintainable materialized view")));
 
-				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
+				break;
 			}
-			break;
-			expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+		case T_Aggref:
+			{
+				/* Check if this supports IVM */
+				Aggref *aggref = (Aggref *) node;
+				const char *aggname = format_procedure(aggref->aggfnoid);
+
+				if (aggref->aggfilter != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with FILTER clause is not supported on incrementally maintainable materialized view")));
+
+				if (aggref->aggdistinct != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with DISTINCT arguments is not supported on incrementally maintainable materialized view")));
+
+				if (aggref->aggorder != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with ORDER clause is not supported on incrementally maintainable materialized view")));
+
+				if (!check_aggregate_supports_ivm(aggref->aggfnoid))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function %s is not supported on incrementally maintainable materialized view", aggname)));
+				break;
+			}
+		default:
+			expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
 			break;
 	}
 	return false;
 }
 
+/*
+ * check_aggregate_supports_ivm
+ *
+ * Check if the given aggregate function is supporting IVM
+ */
+static bool
+check_aggregate_supports_ivm(Oid aggfnoid)
+{
+	switch (aggfnoid)
+	{
+		/* count */
+		case F_COUNT_ANY:
+		case F_COUNT_:
+
+		/* sum */
+		case F_SUM_INT8:
+		case F_SUM_INT4:
+		case F_SUM_INT2:
+		case F_SUM_FLOAT4:
+		case F_SUM_FLOAT8:
+		case F_SUM_MONEY:
+		case F_SUM_INTERVAL:
+		case F_SUM_NUMERIC:
+
+		/* avg */
+		case F_AVG_INT8:
+		case F_AVG_INT4:
+		case F_AVG_INT2:
+		case F_AVG_NUMERIC:
+		case F_AVG_FLOAT4:
+		case F_AVG_FLOAT8:
+		case F_AVG_INTERVAL:
+
+		/* min */
+		case F_MIN_ANYARRAY:
+		case F_MIN_INT8:
+		case F_MIN_INT4:
+		case F_MIN_INT2:
+		case F_MIN_OID:
+		case F_MIN_FLOAT4:
+		case F_MIN_FLOAT8:
+		case F_MIN_DATE:
+		case F_MIN_TIME:
+		case F_MIN_TIMETZ:
+		case F_MIN_MONEY:
+		case F_MIN_TIMESTAMP:
+		case F_MIN_TIMESTAMPTZ:
+		case F_MIN_INTERVAL:
+		case F_MIN_TEXT:
+		case F_MIN_NUMERIC:
+		case F_MIN_BPCHAR:
+		case F_MIN_TID:
+		case F_MIN_ANYENUM:
+		case F_MIN_INET:
+		case F_MIN_PG_LSN:
+
+		/* max */
+		case F_MAX_ANYARRAY:
+		case F_MAX_INT8:
+		case F_MAX_INT4:
+		case F_MAX_INT2:
+		case F_MAX_OID:
+		case F_MAX_FLOAT4:
+		case F_MAX_FLOAT8:
+		case F_MAX_DATE:
+		case F_MAX_TIME:
+		case F_MAX_TIMETZ:
+		case F_MAX_MONEY:
+		case F_MAX_TIMESTAMP:
+		case F_MAX_TIMESTAMPTZ:
+		case F_MAX_INTERVAL:
+		case F_MAX_TEXT:
+		case F_MAX_NUMERIC:
+		case F_MAX_BPCHAR:
+		case F_MAX_TID:
+		case F_MAX_ANYENUM:
+		case F_MAX_INET:
+		case F_MAX_PG_LSN:
+			return true;
+
+		default:
+			return false;
+	}
+}
+
 /*
  * CreateIndexOnIMMV
  *
@@ -1139,7 +1385,30 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel)
 	index->concurrent = false;
 	index->if_not_exists = false;
 
-	if (qry->distinctClause)
+
+	if (qry->groupClause)
+	{
+		/* create unique constraint on GROUP BY expression columns */
+		foreach(lc, qry->groupClause)
+		{
+			SortGroupClause *scl = (SortGroupClause *) lfirst(lc);
+			TargetEntry *tle = get_sortgroupclause_tle(scl, qry->targetList);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+			IndexElem  *iparam;
+
+			iparam = makeNode(IndexElem);
+			iparam->name = pstrdup(NameStr(attr->attname));
+			iparam->expr = NULL;
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->opclassopts = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+		}
+	}
+	else if (qry->distinctClause)
 	{
 		/* create unique constraint on all columns */
 		foreach(lc, qry->targetList)
@@ -1198,7 +1467,7 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel)
 					(errmsg("could not create an index on materialized view \"%s\" automatically",
 							RelationGetRelationName(matviewRel)),
 					 errdetail("This target list does not have all the primary key columns, "
-							   "or this view does not contain DISTINCT clause."),
+							   "or this view does not contain GROUP BY or DISTINCT clause."),
 					 errhint("Create an index on the materialized view for efficient incremental maintenance.")));
 			return;
 		}
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 943de5dfba..1f50aaa1b8 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -81,6 +81,32 @@ typedef struct
 
 #define MV_INIT_QUERYHASHSIZE	16
 
+/* MV query type codes */
+#define MV_PLAN_RECALC			1
+#define MV_PLAN_SET_VALUE		2
+
+/*
+ * MI_QueryKey
+ *
+ * The key identifying a prepared SPI plan in our query hashtable
+ */
+typedef struct MV_QueryKey
+{
+	Oid			matview_id;	/* OID of materialized view */
+	int32		query_type;	/* query type ID, see MV_PLAN_XXX above */
+} MV_QueryKey;
+
+/*
+ * MV_QueryHashEntry
+ *
+ * Hash entry for cached plans used to maintain materialized views.
+ */
+typedef struct MV_QueryHashEntry
+{
+	MV_QueryKey key;
+	SPIPlanPtr	plan;
+} MV_QueryHashEntry;
+
 /*
  * MV_TriggerHashEntry
  *
@@ -117,8 +143,16 @@ typedef struct MV_TriggerTable
 	RangeTblEntry *original_rte;	/* the original RTE saved before rewriting query */
 } MV_TriggerTable;
 
+static HTAB *mv_query_cache = NULL;
 static HTAB *mv_trigger_info = NULL;
 
+/* kind of IVM operation for the view */
+typedef enum
+{
+	IVM_ADD,
+	IVM_SUB
+} IvmOp;
+
 /* ENR name for materialized view delta */
 #define NEW_DELTA_ENRNAME "new_delta"
 #define OLD_DELTA_ENRNAME "old_delta"
@@ -152,7 +186,7 @@ static RangeTblEntry *get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *tabl
 				 QueryEnvironment *queryEnv);
 static RangeTblEntry *union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
 		   QueryEnvironment *queryEnv);
-static Query *rewrite_query_for_distinct(Query *query, ParseState *pstate);
+static Query *rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate);
 
 static void calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
 			DestReceiver *dest_old, DestReceiver *dest_new,
@@ -163,19 +197,48 @@ static Query *rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *
 static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
 			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
 			Query *query, bool use_count, char *count_colname);
+static void append_set_clause_for_count(const char *resname, StringInfo buf_old,
+							StringInfo buf_new,StringInfo aggs_list);
+static void append_set_clause_for_sum(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list);
+static void append_set_clause_for_avg(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list,
+						  const char *aggtype);
+static void append_set_clause_for_minmax(const char *resname, StringInfo buf_old,
+							 StringInfo buf_new, StringInfo aggs_list,
+							 bool is_min);
+static char *get_operation_string(IvmOp op, const char *col, const char *arg1, const char *arg2,
+					 const char* count_col, const char *castType);
+static char *get_null_condition_string(IvmOp op, const char *arg1, const char *arg2,
+						  const char* count_col);
 static void apply_old_delta(const char *matviewname, const char *deltaname_old,
 				List *keys);
 static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
-				List *keys, const char *count_colname);
+				List *keys, StringInfo aggs_list, StringInfo aggs_set,
+				List *minmax_list, List *is_min_list,
+				const char *count_colname,
+				SPITupleTable **tuptable_recalc, uint64 *num_recalc);
 static void apply_new_delta(const char *matviewname, const char *deltaname_new,
 				StringInfo target_list);
 static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
-				List *keys, StringInfo target_list, const char* count_colname);
+				List *keys, StringInfo target_list, StringInfo aggs_set,
+				const char* count_colname);
 static char *get_matching_condition_string(List *keys);
+static char *get_returning_string(List *minmax_list, List *is_min_list, List *keys);
+static char *get_minmax_recalc_condition_string(List *minmax_list, List *is_min_list);
+static char *get_select_for_recalc_string(List *keys);
+static void recalc_and_set_values(SPITupleTable *tuptable_recalc, int64 num_tuples,
+					  List *namelist, List *keys, Relation matviewRel);
+static SPIPlanPtr get_plan_for_recalc(Oid matviewOid, List *namelist, List *keys, Oid *keyTypes);
+static SPIPlanPtr get_plan_for_set_values(Oid matviewOid, char *matviewname, List *namelist,
+						Oid *valTypes);
 static void generate_equal(StringInfo querybuf, Oid opttype,
 			   const char *leftop, const char *rightop);
 
 static void mv_InitHashTables(void);
+static SPIPlanPtr mv_FetchPreparedPlan(MV_QueryKey *key);
+static void mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan);
+static void mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type);
 static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry);
 
 static List *get_securityQuals(Oid relId, int rt_index, Query *query);
@@ -1447,8 +1510,8 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
 	rewritten = rewrite_query_for_preupdate_state(rewritten, entry->tables,
 												  entry->xid, entry->cid,
 												  pstate);
-	/* Rewrite for DISTINCT clause */
-	rewritten = rewrite_query_for_distinct(rewritten, pstate);
+	/* Rewrite for DISTINCT clause and aggregates functions */
+	rewritten = rewrite_query_for_distinct_and_aggregates(rewritten, pstate);
 
 	/* Create tuplestores to store view deltas */
 	if (entry->has_old)
@@ -1499,7 +1562,7 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
 
 			count_colname = pstrdup("__ivm_count__");
 
-			if (query->distinctClause)
+			if (query->hasAggs || query->distinctClause)
 				use_count = true;
 
 			/* calculate delta tables */
@@ -1861,17 +1924,34 @@ union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
 }
 
 /*
- * rewrite_query_for_distinct
+ * rewrite_query_for_distinct_and_aggregates
  *
- * Rewrite query for counting DISTINCT clause.
+ * Rewrite query for counting DISTINCT clause and aggregate functions.
  */
 static Query *
-rewrite_query_for_distinct(Query *query, ParseState *pstate)
+rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate)
 {
 	TargetEntry *tle_count;
 	FuncCall *fn;
 	Node *node;
 
+	/* For aggregate views */
+	if (query->hasAggs)
+	{
+		ListCell *lc;
+		List *aggs = NIL;
+		AttrNumber next_resno = list_length(query->targetList) + 1;
+
+		foreach(lc, query->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+			if (IsA(tle->expr, Aggref))
+				makeIvmAggColumn(pstate, (Aggref *)tle->expr, tle->resname, &next_resno, &aggs);
+		}
+		query->targetList = list_concat(query->targetList, aggs);
+	}
+
 	/* Add count(*) for counting distinct tuples in views */
 	fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
 	fn->agg_star = true;
@@ -1940,6 +2020,8 @@ rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte
 	return query;
 }
 
+#define IVM_colname(type, col) makeObjectName("__ivm_" type, col, "_")
+
 /*
  * apply_delta
  *
@@ -1953,11 +2035,16 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 {
 	StringInfoData querybuf;
 	StringInfoData target_list_buf;
+	StringInfo	aggs_list_buf = NULL;
+	StringInfo	aggs_set_old = NULL;
+	StringInfo	aggs_set_new = NULL;
 	Relation	matviewRel;
 	char	   *matviewname;
 	ListCell	*lc;
 	int			i;
 	List	   *keys = NIL;
+	List	   *minmax_list = NIL;
+	List	   *is_min_list = NIL;
 
 
 	/*
@@ -1975,6 +2062,15 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 	initStringInfo(&querybuf);
 	initStringInfo(&target_list_buf);
 
+	if (query->hasAggs)
+	{
+		if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
+			aggs_set_old = makeStringInfo();
+		if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
+			aggs_set_new = makeStringInfo();
+		aggs_list_buf = makeStringInfo();
+	}
+
 	/* build string of target list */
 	for (i = 0; i < matviewRel->rd_att->natts; i++)
 	{
@@ -1998,7 +2094,65 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		if (tle->resjunk)
 			continue;
 
-		keys = lappend(keys, resname);
+		/*
+		 * For views without aggregates, all attributes are used as keys to identify a
+		 * tuple in a view.
+		 */
+		if (!query->hasAggs)
+			keys = lappend(keys, attr);
+
+		/* For views with aggregates, we need to build SET clause for updating aggregate
+		 * values. */
+		if (query->hasAggs && IsA(tle->expr, Aggref))
+		{
+			Aggref *aggref = (Aggref *) tle->expr;
+			const char *aggname = get_func_name(aggref->aggfnoid);
+
+			/*
+			 * We can use function names here because it is already checked if these
+			 * can be used in IMMV by its OID at the definition time.
+			 */
+
+			/* count */
+			if (!strcmp(aggname, "count"))
+				append_set_clause_for_count(resname, aggs_set_old, aggs_set_new, aggs_list_buf);
+
+			/* sum */
+			else if (!strcmp(aggname, "sum"))
+				append_set_clause_for_sum(resname, aggs_set_old, aggs_set_new, aggs_list_buf);
+
+			/* avg */
+			else if (!strcmp(aggname, "avg"))
+				append_set_clause_for_avg(resname, aggs_set_old, aggs_set_new, aggs_list_buf,
+										  format_type_be(aggref->aggtype));
+
+			/* min/max */
+			else if (!strcmp(aggname, "min") || !strcmp(aggname, "max"))
+			{
+				bool	is_min = (!strcmp(aggname, "min"));
+
+				append_set_clause_for_minmax(resname, aggs_set_old, aggs_set_new, aggs_list_buf, is_min);
+
+				/* make a resname list of min and max aggregates */
+				minmax_list = lappend(minmax_list, resname);
+				is_min_list = lappend_int(is_min_list, is_min);
+			}
+			else
+				elog(ERROR, "unsupported aggregate function: %s", aggname);
+		}
+	}
+
+	/* If we have GROUP BY clause, we use its entries as keys. */
+	if (query->hasAggs && query->groupClause)
+	{
+		foreach (lc, query->groupClause)
+		{
+			SortGroupClause *sgcl = (SortGroupClause *) lfirst(lc);
+			TargetEntry		*tle = get_sortgroupclause_tle(sgcl, query->targetList);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+
+			keys = lappend(keys, attr);
+		}
 	}
 
 	/* Start maintaining the materialized view. */
@@ -2012,6 +2166,8 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 	if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
 	{
 		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		SPITupleTable  *tuptable_recalc = NULL;
+		uint64			num_recalc;
 		int				rc;
 
 		/* convert tuplestores to ENR, and register for SPI */
@@ -2029,10 +2185,19 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		if (use_count)
 			/* apply old delta and get rows to be recalculated */
 			apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME,
-									   keys, count_colname);
+									   keys, aggs_list_buf, aggs_set_old,
+									   minmax_list, is_min_list,
+									   count_colname, &tuptable_recalc, &num_recalc);
 		else
 			apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys);
 
+		/*
+		 * If we have min or max, we might have to recalculate aggregate values from base tables
+		 * on some tuples. TIDs and keys such tuples are returned as a result of the above query.
+		 */
+		if (minmax_list && tuptable_recalc)
+			recalc_and_set_values(tuptable_recalc, num_recalc, minmax_list, keys, matviewRel);
+
 	}
 	/* For tuple insertion */
 	if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
@@ -2055,7 +2220,7 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		/* apply new delta */
 		if (use_count)
 			apply_new_delta_with_count(matviewname, NEW_DELTA_ENRNAME,
-								keys, &target_list_buf, count_colname);
+								keys, aggs_set_new, &target_list_buf, count_colname);
 		else
 			apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf);
 	}
@@ -2070,49 +2235,410 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		elog(ERROR, "SPI_finish failed");
 }
 
+/*
+ * append_set_clause_for_count
+ *
+ * Append SET clause string for count aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_count(const char *resname, StringInfo buf_old,
+							StringInfo buf_new,StringInfo aggs_list)
+{
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* resname = mv.resname - t.resname */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, resname, "mv", "t", NULL, NULL));
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* resname = mv.resname + diff.resname */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, resname, "mv", "diff", NULL, NULL));
+	}
+
+	appendStringInfo(aggs_list, ", %s",
+		quote_qualified_identifier("diff", resname)
+	);
+}
+
+/*
+ * append_set_clause_for_sum
+ *
+ * Append SET clause string for sum aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_sum(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list)
+{
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* sum = mv.sum - t.sum */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, resname, "mv", "t", count_col, NULL)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* sum = mv.sum + diff.sum */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, resname, "mv", "diff", count_col, NULL)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * append_set_clause_for_avg
+ *
+ * Append SET clause string for avg aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_avg(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list,
+						  const char *aggtype)
+{
+	char *sum_col = IVM_colname("sum", resname);
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* avg = (mv.sum - t.sum)::aggtype / (mv.count - t.count) */
+		appendStringInfo(buf_old,
+			", %s = %s OPERATOR(pg_catalog./) %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, aggtype),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+		/* sum = mv.sum - t.sum */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, sum_col),
+			get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, NULL)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* avg = (mv.sum + diff.sum)::aggtype / (mv.count + diff.count) */
+		appendStringInfo(buf_new,
+			", %s = %s OPERATOR(pg_catalog./) %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, aggtype),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+		/* sum = mv.sum + diff.sum */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, sum_col),
+			get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, NULL)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("sum", resname)),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * append_set_clause_for_minmax
+ *
+ * Append SET clause string for min or max aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ * is_min is true if this is min, false if not.
+ */
+static void
+append_set_clause_for_minmax(const char *resname, StringInfo buf_old,
+							 StringInfo buf_new, StringInfo aggs_list,
+							 bool is_min)
+{
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/*
+		 * If the new value doesn't became NULL then use the value remaining
+		 * in the view although this will be recomputated afterwords.
+		 */
+		appendStringInfo(buf_old,
+			", %s = CASE WHEN %s THEN NULL ELSE %s END",
+			quote_qualified_identifier(NULL, resname),
+			get_null_condition_string(IVM_SUB, "mv", "t", count_col),
+			quote_qualified_identifier("mv", resname)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/*
+		 * min = LEAST(mv.min, diff.min)
+		 * max = GREATEST(mv.max, diff.max)
+		 */
+		appendStringInfo(buf_new,
+			", %s = CASE WHEN %s THEN NULL ELSE %s(%s,%s) END",
+			quote_qualified_identifier(NULL, resname),
+			get_null_condition_string(IVM_ADD, "mv", "diff", count_col),
+
+			is_min ? "LEAST" : "GREATEST",
+			quote_qualified_identifier("mv", resname),
+			quote_qualified_identifier("diff", resname)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * get_operation_string
+ *
+ * Build a string to calculate the new aggregate values.
+ */
+static char *
+get_operation_string(IvmOp op, const char *col, const char *arg1, const char *arg2,
+					 const char* count_col, const char *castType)
+{
+	StringInfoData buf;
+	StringInfoData castString;
+	char   *col1 = quote_qualified_identifier(arg1, col);
+	char   *col2 = quote_qualified_identifier(arg2, col);
+	char	op_char = (op == IVM_SUB ? '-' : '+');
+
+	initStringInfo(&buf);
+	initStringInfo(&castString);
+
+	if (castType)
+		appendStringInfo(&castString, "::%s", castType);
+
+	if (!count_col)
+	{
+		/*
+		 * If the attributes don't have count columns then calc the result
+		 * by using the operator simply.
+		 */
+		appendStringInfo(&buf, "(%s OPERATOR(pg_catalog.%c) %s)%s",
+			col1, op_char, col2, castString.data);
+	}
+	else
+	{
+		/*
+		 * If the attributes have count columns then consider the condition
+		 * where the result becomes NULL.
+		 */
+		char *null_cond = get_null_condition_string(op, arg1, arg2, count_col);
+
+		appendStringInfo(&buf,
+			"(CASE WHEN %s THEN NULL "
+				"WHEN %s IS NULL THEN %s "
+				"WHEN %s IS NULL THEN %s "
+				"ELSE (%s OPERATOR(pg_catalog.%c) %s)%s END)",
+			null_cond,
+			col1, col2,
+			col2, col1,
+			col1, op_char, col2, castString.data
+		);
+	}
+
+	return buf.data;
+}
+
+/*
+ * get_null_condition_string
+ *
+ * Build a predicate string for CASE clause to check if an aggregate value
+ * will became NULL after the given operation is applied.
+ */
+static char *
+get_null_condition_string(IvmOp op, const char *arg1, const char *arg2,
+						  const char* count_col)
+{
+	StringInfoData null_cond;
+	initStringInfo(&null_cond);
+
+	switch (op)
+	{
+		case IVM_ADD:
+			appendStringInfo(&null_cond,
+				"%s OPERATOR(pg_catalog.=) 0 AND %s OPERATOR(pg_catalog.=) 0",
+				quote_qualified_identifier(arg1, count_col),
+				quote_qualified_identifier(arg2, count_col)
+			);
+			break;
+		case IVM_SUB:
+			appendStringInfo(&null_cond,
+				"%s OPERATOR(pg_catalog.=) %s",
+				quote_qualified_identifier(arg1, count_col),
+				quote_qualified_identifier(arg2, count_col)
+			);
+			break;
+		default:
+			elog(ERROR,"unknown operation");
+	}
+
+	return null_cond.data;
+}
+
+
 /*
  * apply_old_delta_with_count
  *
  * Execute a query for applying a delta table given by deltname_old
  * which contains tuples to be deleted from to a materialized view given by
  * matviewname.  This is used when counting is required, that is, the view
- * has aggregate or distinct.
+ * has aggregate or distinct. Also, when a table in EXISTS sub queries
+ * is modified.
+ *
+ * If the view desn't have aggregates or has GROUP BY, this requires a keys
+ * list to identify a tuple in the view. If the view has aggregates, this
+ * requires strings representing resnames of aggregates and SET clause for
+ * updating aggregate values.
+ *
+ * If the view has min or max aggregate, this requires a list of resnames of
+ * min/max aggregates and a list of boolean which represents which entries in
+ * minmax_list is min. These are necessary to check if we need to recalculate
+ * min or max aggregate values. In this case, this query returns TID and keys
+ * of tuples which need to be recalculated.  This result and the number of rows
+ * are stored in tuptables and num_recalc repectedly.
+ *
  */
 static void
 apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
-				List *keys, const char *count_colname)
+				List *keys, StringInfo aggs_list, StringInfo aggs_set,
+				List *minmax_list, List *is_min_list,
+				const char *count_colname,
+				SPITupleTable **tuptable_recalc, uint64 *num_recalc)
 {
 	StringInfoData	querybuf;
 	char   *match_cond;
+	char   *updt_returning = "";
+	char   *select_for_recalc = "SELECT";
+	bool	agg_without_groupby = (list_length(keys) == 0);
+
+	Assert(tuptable_recalc != NULL);
+	Assert(num_recalc != NULL);
 
 	/* build WHERE condition for searching tuples to be deleted */
 	match_cond = get_matching_condition_string(keys);
 
+	/*
+	 * We need a special RETURNING clause and SELECT statement for min/max to
+	 * check which tuple needs re-calculation from base tables.
+	 */
+	if (minmax_list)
+	{
+		updt_returning = get_returning_string(minmax_list, is_min_list, keys);
+		select_for_recalc = get_select_for_recalc_string(keys);
+	}
+
 	/* Search for matching tuples from the view and update or delete if found. */
 	initStringInfo(&querybuf);
 	appendStringInfo(&querybuf,
 					"WITH t AS ("			/* collecting tid of target tuples in the view */
 						"SELECT diff.%s, "			/* count column */
-								"(diff.%s OPERATOR(pg_catalog.=) mv.%s) AS for_dlt, "
+								"(diff.%s OPERATOR(pg_catalog.=) mv.%s AND %s) AS for_dlt, "
 								"mv.ctid "
+								"%s "				/* aggregate columns */
 						"FROM %s AS mv, %s AS diff "
 						"WHERE %s"					/* tuple matching condition */
 					"), updt AS ("			/* update a tuple if this is not to be deleted */
 						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.-) t.%s "
+											"%s"	/* SET clauses for aggregates */
 						"FROM t WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND NOT for_dlt "
+						"%s"						/* RETURNING clause for recalc infomation */
 					"), dlt AS ("			/* delete a tuple if this is to be deleted */
 						"DELETE FROM %s AS mv USING t "
 						"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt"
-					")",
+					") %s",							/* SELECT returning which tuples need to be recalculated */
 					count_colname,
-					count_colname, count_colname,
+					count_colname, count_colname, (agg_without_groupby ? "false" : "true"),
+					(aggs_list != NULL ? aggs_list->data : ""),
 					matviewname, deltaname_old,
 					match_cond,
 					matviewname, count_colname, count_colname, count_colname,
-					matviewname);
+					(aggs_set != NULL ? aggs_set->data : ""),
+					updt_returning,
+					matviewname,
+					select_for_recalc);
 
-	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_SELECT)
 		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+
+
+	/* Return tuples to be recalculated. */
+	if (minmax_list)
+	{
+		*tuptable_recalc = SPI_tuptable;
+		*num_recalc = SPI_processed;
+	}
+	else
+	{
+		*tuptable_recalc = NULL;
+		*num_recalc = 0;
+	}
 }
 
 /*
@@ -2172,10 +2698,15 @@ apply_old_delta(const char *matviewname, const char *deltaname_old,
  * matviewname.  This is used when counting is required, that is, the view
  * has aggregate or distinct. Also, when a table in EXISTS sub queries
  * is modified.
+ *
+ * If the view desn't have aggregates or has GROUP BY, this requires a keys
+ * list to identify a tuple in the view. If the view has aggregates, this
+ * requires strings representing SET clause for updating aggregate values.
  */
 static void
 apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
-				List *keys, StringInfo target_list, const char* count_colname)
+				List *keys, StringInfo aggs_set, StringInfo target_list,
+				const char* count_colname)
 {
 	StringInfoData	querybuf;
 	StringInfoData	returning_keys;
@@ -2206,6 +2737,7 @@ apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
 	appendStringInfo(&querybuf,
 					"WITH updt AS ("		/* update a tuple if this exists in the view */
 						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.+) diff.%s "
+											"%s "	/* SET clauses for aggregates */
 						"FROM %s AS diff "
 						"WHERE %s "					/* tuple matching condition */
 						"RETURNING %s"				/* returning keys of updated tuples */
@@ -2213,6 +2745,7 @@ apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
 						"SELECT %s FROM %s AS diff "
 						"WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE %s);",
 					matviewname, count_colname, count_colname, count_colname,
+					(aggs_set != NULL ? aggs_set->data : ""),
 					deltaname_new,
 					match_cond,
 					returning_keys.data,
@@ -2287,6 +2820,349 @@ get_matching_condition_string(List *keys)
 	return match_cond.data;
 }
 
+/*
+ * get_returning_string
+ *
+ * Build a string for RETURNING clause of UPDATE used in apply_old_delta_with_count.
+ * This clause returns ctid and a boolean value that indicates if we need to
+ * recalculate min or max value, for each updated row.
+ */
+static char *
+get_returning_string(List *minmax_list, List *is_min_list, List *keys)
+{
+	StringInfoData returning;
+	char		*recalc_cond;
+	ListCell	*lc;
+
+	Assert(minmax_list != NIL && is_min_list != NIL);
+	recalc_cond = get_minmax_recalc_condition_string(minmax_list, is_min_list);
+
+	initStringInfo(&returning);
+
+	appendStringInfo(&returning, "RETURNING mv.ctid AS tid, (%s) AS recalc", recalc_cond);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char *resname = NameStr(attr->attname);
+		appendStringInfo(&returning, ", %s", quote_qualified_identifier("mv", resname));
+	}
+
+	return returning.data;
+}
+
+/*
+ * get_minmax_recalc_condition_string
+ *
+ * Build a predicate string for checking if any min/max aggregate
+ * value needs to be recalculated.
+ */
+static char *
+get_minmax_recalc_condition_string(List *minmax_list, List *is_min_list)
+{
+	StringInfoData recalc_cond;
+	ListCell	*lc1, *lc2;
+
+	initStringInfo(&recalc_cond);
+
+	Assert (list_length(minmax_list) == list_length(is_min_list));
+
+	forboth (lc1, minmax_list, lc2, is_min_list)
+	{
+		char   *resname = (char *) lfirst(lc1);
+		bool	is_min = (bool) lfirst_int(lc2);
+		char   *op_str = (is_min ? ">=" : "<=");
+
+		appendStringInfo(&recalc_cond, "%s OPERATOR(pg_catalog.%s) %s",
+			quote_qualified_identifier("mv", resname),
+			op_str,
+			quote_qualified_identifier("t", resname)
+		);
+
+		if (lnext(minmax_list, lc1))
+			appendStringInfo(&recalc_cond, " OR ");
+	}
+
+	return recalc_cond.data;
+}
+
+/*
+ * get_select_for_recalc_string
+ *
+ * Build a query to return tid and keys of tuples which need
+ * recalculation. This is used as the result of the query
+ * built by apply_old_delta.
+ */
+static char *
+get_select_for_recalc_string(List *keys)
+{
+	StringInfoData qry;
+	ListCell	*lc;
+
+	initStringInfo(&qry);
+
+	appendStringInfo(&qry, "SELECT tid");
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		appendStringInfo(&qry, ", %s", NameStr(attr->attname));
+	}
+
+	appendStringInfo(&qry, " FROM updt WHERE recalc");
+
+	return qry.data;
+}
+
+/*
+ * recalc_and_set_values
+ *
+ * Recalculate tuples in a materialized from base tables and update these.
+ * The tuples which needs recalculation are specified by keys, and resnames
+ * of columns to be updated are specified by namelist. TIDs and key values
+ * are given by tuples in tuptable_recalc. Its first attribute must be TID
+ * and key values must be following this.
+ */
+static void
+recalc_and_set_values(SPITupleTable *tuptable_recalc, int64 num_tuples,
+					  List *namelist, List *keys, Relation matviewRel)
+{
+	TupleDesc   tupdesc_recalc = tuptable_recalc->tupdesc;
+	Oid		   *keyTypes = NULL, *types = NULL;
+	char	   *keyNulls = NULL, *nulls = NULL;
+	Datum	   *keyVals = NULL, *vals = NULL;
+	int			num_vals = list_length(namelist);
+	int			num_keys = list_length(keys);
+	uint64      i;
+	Oid			matviewOid;
+	char	   *matviewname;
+
+	matviewOid = RelationGetRelid(matviewRel);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	/* If we have keys, initialize arrays for them. */
+	if (keys)
+	{
+		keyTypes = palloc(sizeof(Oid) * num_keys);
+		keyNulls = palloc(sizeof(char) * num_keys);
+		keyVals = palloc(sizeof(Datum) * num_keys);
+		/* a tuple contains keys to be recalculated and ctid to be updated*/
+		Assert(tupdesc_recalc->natts == num_keys + 1);
+
+		/* Types of key attributes  */
+		for (i = 0; i < num_keys; i++)
+			keyTypes[i] = TupleDescAttr(tupdesc_recalc, i + 1)->atttypid;
+	}
+
+	/* allocate memory for all attribute names and tid */
+	types = palloc(sizeof(Oid) * (num_vals + 1));
+	nulls = palloc(sizeof(char) * (num_vals + 1));
+	vals = palloc(sizeof(Datum) * (num_vals + 1));
+
+	/* For each tuple which needs recalculation */
+	for (i = 0; i < num_tuples; i++)
+	{
+		int j;
+		bool isnull;
+		SPIPlanPtr plan;
+		SPITupleTable *tuptable_newvals;
+		TupleDesc   tupdesc_newvals;
+
+		/* Set group key values as parameters if needed. */
+		if (keys)
+		{
+			for (j = 0; j < num_keys; j++)
+			{
+				keyVals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, j + 2, &isnull);
+				if (isnull)
+					keyNulls[j] = 'n';
+				else
+					keyNulls[j] = ' ';
+			}
+		}
+
+		/*
+		 * Get recalculated values from base tables. The result must be
+		 * only one tuple thich contains the new values for specified keys.
+		 */
+		plan = get_plan_for_recalc(matviewOid, namelist, keys, keyTypes);
+		if (SPI_execute_plan(plan, keyVals, keyNulls, false, 0) != SPI_OK_SELECT)
+			elog(ERROR, "SPI_execute_plan");
+		if (SPI_processed != 1)
+			elog(ERROR, "SPI_execute_plan returned zero or more than one rows");
+
+		tuptable_newvals = SPI_tuptable;
+		tupdesc_newvals = tuptable_newvals->tupdesc;
+
+		Assert(tupdesc_newvals->natts == num_vals);
+
+		/* Set the new values as parameters */
+		for (j = 0; j < tupdesc_newvals->natts; j++)
+		{
+			if (i == 0)
+				types[j] = TupleDescAttr(tupdesc_newvals, j)->atttypid;
+
+			vals[j] = SPI_getbinval(tuptable_newvals->vals[0], tupdesc_newvals, j + 1, &isnull);
+			if (isnull)
+				nulls[j] = 'n';
+			else
+				nulls[j] = ' ';
+		}
+		/* Set TID of the view tuple to be updated as a parameter */
+		types[j] = TIDOID;
+		vals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, 1, &isnull);
+		nulls[j] = ' ';
+
+		/* Update the view tuple to the new values */
+		plan = get_plan_for_set_values(matviewOid, matviewname, namelist, types);
+		if (SPI_execute_plan(plan, vals, nulls, false, 0) != SPI_OK_UPDATE)
+			elog(ERROR, "SPI_execute_plan");
+	}
+}
+
+
+/*
+ * get_plan_for_recalc
+ *
+ * Create or fetch a plan for recalculating value in the view's target list
+ * from base tables using the definition query of materialized view specified
+ * by matviewOid. namelist is a list of resnames of values to be recalculated.
+ *
+ * keys is a list of keys to identify tuples to be recalculated if this is not
+ * empty. KeyTypes is an array of types of keys.
+ */
+static SPIPlanPtr
+get_plan_for_recalc(Oid matviewOid, List *namelist, List *keys, Oid *keyTypes)
+{
+	MV_QueryKey hash_key;
+	SPIPlanPtr	plan;
+
+	/* Fetch or prepare a saved plan for the recalculation */
+	mv_BuildQueryKey(&hash_key, matviewOid, MV_PLAN_RECALC);
+	if ((plan = mv_FetchPreparedPlan(&hash_key)) == NULL)
+	{
+		ListCell	   *lc;
+		StringInfoData	str;
+		char   *viewdef;
+
+		/* get view definition of matview */
+		viewdef = text_to_cstring((text *) DatumGetPointer(
+					DirectFunctionCall1(pg_get_viewdef, ObjectIdGetDatum(matviewOid))));
+		/* get rid of trailing semi-colon */
+		viewdef[strlen(viewdef)-1] = '\0';
+
+		/*
+		 * Build a query string for recalculating values. This is like
+		 *
+		 *  SELECT x1, x2, x3, ... FROM ( ... view definition query ...) mv
+		 *   WHERE (key1, key2, ...) = ($1, $2, ...);
+		 */
+
+		initStringInfo(&str);
+		appendStringInfo(&str, "SELECT ");
+		foreach (lc, namelist)
+		{
+			appendStringInfo(&str, "%s", (char *) lfirst(lc));
+			if (lnext(namelist, lc))
+				appendStringInfoString(&str, ", ");
+		}
+		appendStringInfo(&str, " FROM (%s) mv", viewdef);
+
+		if (keys)
+		{
+			int		i = 1;
+			char	paramname[16];
+
+			appendStringInfo(&str, " WHERE (");
+			foreach (lc, keys)
+			{
+				Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+				char   *resname = NameStr(attr->attname);
+				Oid		typid = attr->atttypid;
+
+				sprintf(paramname, "$%d", i);
+				appendStringInfo(&str, "(");
+				generate_equal(&str, typid, resname, paramname);
+				appendStringInfo(&str, " OR (%s IS NULL AND %s IS NULL))",
+								 resname, paramname);
+
+				if (lnext(keys, lc))
+					appendStringInfoString(&str, " AND ");
+				i++;
+			}
+			appendStringInfo(&str, ")");
+		}
+		else
+			keyTypes = NULL;
+
+		plan = SPI_prepare(str.data, list_length(keys), keyTypes);
+		if (plan == NULL)
+			elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), str.data);
+
+		SPI_keepplan(plan);
+		mv_HashPreparedPlan(&hash_key, plan);
+	}
+
+	return plan;
+}
+
+/*
+ * get_plan_for_set_values
+ *
+ * Create or fetch a plan for applying new values calculated by
+ * get_plan_for_recalc to a materialized view specified by matviewOid.
+ * matviewname is the name of the view.  namelist is a list of resnames
+ * of attributes to be updated, and valTypes is an array of types of the
+ * values.
+ */
+static SPIPlanPtr
+get_plan_for_set_values(Oid matviewOid, char *matviewname, List *namelist,
+						Oid *valTypes)
+{
+	MV_QueryKey	key;
+	SPIPlanPtr	plan;
+
+	/* Fetch or prepare a saved plan for the real check */
+	mv_BuildQueryKey(&key, matviewOid, MV_PLAN_SET_VALUE);
+	if ((plan = mv_FetchPreparedPlan(&key)) == NULL)
+	{
+		ListCell	  *lc;
+		StringInfoData str;
+		int		i;
+
+		/*
+		 * Build a query string for applying min/max values. This is like
+		 *
+		 *  UPDATE matviewname AS mv
+		 *   SET (x1, x2, x3, x4) = ($1, $2, $3, $4)
+		 *   WHERE ctid = $5;
+		 */
+
+		initStringInfo(&str);
+		appendStringInfo(&str, "UPDATE %s AS mv SET (", matviewname);
+		foreach (lc, namelist)
+		{
+			appendStringInfo(&str, "%s", (char *) lfirst(lc));
+			if (lnext(namelist, lc))
+				appendStringInfoString(&str, ", ");
+		}
+		appendStringInfo(&str, ") = ROW(");
+
+		for (i = 1; i <= list_length(namelist); i++)
+			appendStringInfo(&str, "%s$%d", (i==1 ? "" : ", "), i);
+
+		appendStringInfo(&str, ") WHERE ctid OPERATOR(pg_catalog.=) $%d", i);
+
+		plan = SPI_prepare(str.data, list_length(namelist) + 1, valTypes);
+		if (plan == NULL)
+			elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), str.data);
+
+		SPI_keepplan(plan);
+		mv_HashPreparedPlan(&key, plan);
+	}
+
+	return plan;
+}
+
 /*
  * generate_equals
  *
@@ -2320,6 +3196,13 @@ mv_InitHashTables(void)
 {
 	HASHCTL		ctl;
 
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(MV_QueryKey);
+	ctl.entrysize = sizeof(MV_QueryHashEntry);
+	mv_query_cache = hash_create("MV query cache",
+								 MV_INIT_QUERYHASHSIZE,
+								 &ctl, HASH_ELEM | HASH_BLOBS);
+
 	memset(&ctl, 0, sizeof(ctl));
 	ctl.keysize = sizeof(Oid);
 	ctl.entrysize = sizeof(MV_TriggerHashEntry);
@@ -2328,6 +3211,99 @@ mv_InitHashTables(void)
 								 &ctl, HASH_ELEM | HASH_BLOBS);
 }
 
+/*
+ * mv_FetchPreparedPlan
+ */
+static SPIPlanPtr
+mv_FetchPreparedPlan(MV_QueryKey *key)
+{
+	MV_QueryHashEntry *entry;
+	SPIPlanPtr	plan;
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_query_cache)
+		mv_InitHashTables();
+
+	/*
+	 * Lookup for the key
+	 */
+	entry = (MV_QueryHashEntry *) hash_search(mv_query_cache,
+											  (void *) key,
+											  HASH_FIND, NULL);
+	if (entry == NULL)
+		return NULL;
+
+	/*
+	 * Check whether the plan is still valid.  If it isn't, we don't want to
+	 * simply rely on plancache.c to regenerate it; rather we should start
+	 * from scratch and rebuild the query text too.  This is to cover cases
+	 * such as table/column renames.  We depend on the plancache machinery to
+	 * detect possible invalidations, though.
+	 *
+	 * CAUTION: this check is only trustworthy if the caller has already
+	 * locked both materialized views and base tables.
+	 */
+	plan = entry->plan;
+	if (plan && SPI_plan_is_valid(plan))
+		return plan;
+
+	/*
+	 * Otherwise we might as well flush the cached plan now, to free a little
+	 * memory space before we make a new one.
+	 */
+	entry->plan = NULL;
+	if (plan)
+		SPI_freeplan(plan);
+
+	return NULL;
+}
+
+/*
+ * mv_HashPreparedPlan
+ *
+ * Add another plan to our private SPI query plan hashtable.
+ */
+static void
+mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan)
+{
+	MV_QueryHashEntry *entry;
+	bool		found;
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_query_cache)
+		mv_InitHashTables();
+
+	/*
+	 * Add the new plan.  We might be overwriting an entry previously found
+	 * invalid by mv_FetchPreparedPlan.
+	 */
+	entry = (MV_QueryHashEntry *) hash_search(mv_query_cache,
+											  (void *) key,
+											  HASH_ENTER, &found);
+	Assert(!found || entry->plan == NULL);
+	entry->plan = plan;
+}
+
+/*
+ * mv_BuildQueryKey
+ *
+ * Construct a hashtable key for a prepared SPI plan for IVM.
+ */
+static void
+mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type)
+{
+	/*
+	 * We assume struct MV_QueryKey contains no padding bytes, else we'd need
+	 * to use memset to clear them.
+	 */
+	key->matview_id = matview_id;
+	key->query_type = query_type;
+}
+
 /*
  * AtAbort_IVM
  *
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index bcea9782d3..e36302845f 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -30,6 +30,7 @@ extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_cr
 extern void CreateIndexOnIMMV(Query *query, Relation matviewRel);
 
 extern Query *rewriteQueryForIMMV(Query *query, List *colNames);
+extern void makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber *next_resno, List **aggs);
 
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
 
-- 
2.17.1

v25-0007-Add-Incremental-View-Maintenance-support.patchtext/x-diff; name=v25-0007-Add-Incremental-View-Maintenance-support.patchDownload
From 5409cadc8ac813fa288fc2ab8c995094cbd1241d Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Tue, 22 Dec 2020 18:40:18 +0900
Subject: [PATCH v25 07/15] Add Incremental View Maintenance support

In this implementation, AFTER triggers are used to collect
tuplestores containing transition table contents. When multiple tables
are changed, multiple AFTER triggers are invoked, then the final AFTER
trigger performs actual update of the matview. In addition, BEFORE
triggers are also used to handle global information for view
maintenance. To calculate view deltas, we need both pre-state and
post-state of base tables. Post-update states are available in AFTER
trigger, and pre-update states can be calculated by filtering inserted
tuples using cmin/xmin system columns, and append deleted tuples which
are contained in an old transition table.

This patch also allows self-join, simultaneous updates of more than
one base table, and multiple updates of the same base table.

Incrementally Maintainable Materialized Views (IMMV) can contain
duplicated tuples. Also, DISTINCT clause is supported. When IMMV is
created with DISTINCT, multiplicity of tuples is counted and stored
in  "__ivm_count__" column, which is a hidden column of IMMV.
The value in __ivm_count__ is updated when IMMV is maintained
incrementally. A tuple in IMMV can be removed if and only if the
count becomes zero.
---
 src/backend/access/transam/xact.c   |    5 +
 src/backend/commands/createas.c     |  736 +++++++++++++
 src/backend/commands/indexcmds.c    |   40 +
 src/backend/commands/matview.c      | 1555 ++++++++++++++++++++++++++-
 src/backend/commands/tablecmds.c    |    9 +
 src/backend/nodes/copyfuncs.c       |    1 +
 src/backend/nodes/equalfuncs.c      |    1 +
 src/backend/nodes/outfuncs.c        |    1 +
 src/backend/nodes/readfuncs.c       |    1 +
 src/backend/parser/parse_relation.c |   18 +-
 src/backend/rewrite/rewriteDefine.c |    3 +-
 src/include/catalog/pg_proc.dat     |    8 +
 src/include/commands/createas.h     |    6 +
 src/include/commands/matview.h      |    8 +
 src/include/nodes/parsenodes.h      |    2 +
 15 files changed, 2354 insertions(+), 40 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index c9516e03fa..06b012b21f 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_enum.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/matview.h"
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "common/pg_prng.h"
@@ -2776,6 +2777,7 @@ AbortTransaction(void)
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
 	AtAbort_Twophase();
+	AtAbort_IVM();
 
 	/*
 	 * Advertise the fact that we aborted in pg_xact (assuming that we got as
@@ -5020,6 +5022,9 @@ AbortSubTransaction(void)
 	AbortBufferIO();
 	UnlockBuffers();
 
+	/* Clean up hash entries for incremental view maintenance */
+	AtAbort_IVM();
+
 	/* Reset WAL record construction state */
 	XLogResetInsertion();
 
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 9abbb6b555..4b73965e56 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -32,24 +32,41 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
 #include "catalog/toasting.h"
 #include "commands/createas.h"
+#include "commands/defrem.h"
 #include "commands/matview.h"
 #include "commands/prepare.h"
 #include "commands/tablecmds.h"
+#include "commands/tablespace.h"
+#include "commands/trigger.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "optimizer/clauses.h"
+#include "optimizer/optimizer.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_func.h"
+#include "parser/parse_type.h"
 #include "rewrite/rewriteHandler.h"
+#include "rewrite/rewriteManip.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
+#include "utils/syscache.h"
 
 typedef struct
 {
@@ -73,6 +90,12 @@ static bool intorel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void intorel_shutdown(DestReceiver *self);
 static void intorel_destroy(DestReceiver *self);
 
+static void CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid,
+									 Relids *relids, bool ex_lock);
+static void CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock);
+static void check_ivm_restriction(Node *node);
+static bool check_ivm_restriction_walker(Node *node, void *context);
+static Bitmapset *get_primary_key_attnos_from_query(Query *qry, List **constraintList);
 
 /*
  * create_ctas_internal
@@ -108,6 +131,8 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	create->oncommit = into->onCommit;
 	create->tablespacename = into->tableSpaceName;
 	create->if_not_exists = false;
+	/* Using Materialized view only */
+	create->ivm = into->ivm;
 	create->accessMethod = into->accessMethod;
 
 	/*
@@ -238,6 +263,7 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 	List	   *rewritten;
 	PlannedStmt *plan;
 	QueryDesc  *queryDesc;
+	Query	   *query_immv = NULL;
 
 	/* Check if the relation exists or not */
 	if (CreateTableAsRelExists(stmt))
@@ -282,6 +308,22 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 		save_nestlevel = NewGUCNestLevel();
 	}
 
+	if (is_matview && into->ivm)
+	{
+		/* check if the query is supported in IMMV definition */
+		if (contain_mutable_functions((Node *) query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("mutable function is not supported on incrementally maintainable materialized view"),
+					 errhint("functions must be marked IMMUTABLE")));
+
+		check_ivm_restriction((Node *) query);
+
+		/* For IMMV, we need to rewrite matview query */
+		query = rewriteQueryForIMMV(query, into->colNames);
+		query_immv = copyObject(query);
+	}
+
 	if (into->skipData)
 	{
 		/*
@@ -358,11 +400,75 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 
 		/* Restore userid and security context */
 		SetUserIdAndSecContext(save_userid, save_sec_context);
+
+		if (into->ivm)
+		{
+			Oid matviewOid = address.objectId;
+			Relation matviewRel = table_open(matviewOid, NoLock);
+
+			/*
+			 * Mark relisivm field, if it's a matview and into->ivm is true.
+			 */
+			SetMatViewIVMState(matviewRel, true);
+
+			if (!into->skipData)
+			{
+				/* Create an index on incremental maintainable materialized view, if possible */
+				CreateIndexOnIMMV((Query *) into->viewQuery, matviewRel);
+
+				/* Create triggers on incremental maintainable materialized view */
+				Assert(query_immv != NULL);
+				CreateIvmTriggersOnBaseTables(query_immv, matviewOid, true);
+			}
+			table_close(matviewRel, NoLock);
+		}
 	}
 
 	return address;
 }
 
+/*
+ * rewriteQueryForIMMV -- rewrite view definition query for IMMV
+ *
+ * count(*) is added for counting distinct tuples in views.
+ */
+Query *
+rewriteQueryForIMMV(Query *query, List *colNames)
+{
+	Query *rewritten;
+
+	TargetEntry *tle;
+	Node *node;
+	ParseState *pstate = make_parsestate(NULL);
+	FuncCall *fn;
+
+	rewritten = copyObject(query);
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	/*
+	 * Convert DISTINCT to GROUP BY and add count(*) for counting distinct
+	 * tuples in views.
+	 */
+	if (rewritten->distinctClause)
+	{
+		rewritten->groupClause = transformDistinctClause(NULL, &rewritten->targetList, rewritten->sortClause, false);
+
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+		fn->agg_star = true;
+
+		node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+		tle = makeTargetEntry((Expr *) node,
+								list_length(rewritten->targetList) + 1,
+								pstrdup("__ivm_count__"),
+								false);
+		rewritten->targetList = lappend(rewritten->targetList, tle);
+		rewritten->hasAggs = true;
+	}
+
+	return rewritten;
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -623,3 +729,633 @@ intorel_destroy(DestReceiver *self)
 {
 	pfree(self);
 }
+
+/*
+ * CreateIvmTriggersOnBaseTables -- create IVM triggers on all base tables
+ */
+void
+CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create)
+{
+	Relids	relids = NULL;
+	bool	ex_lock = false;
+	Index	first_rtindex = is_create ? 1 : PRS2_NEW_VARNO + 1;
+	RangeTblEntry *rte;
+
+	/* Immediately return if we don't have any base tables. */
+	if (list_length(qry->rtable) < first_rtindex)
+		return;
+
+	/*
+	 * If the view has more than one base tables, we need an exclusive lock
+	 * on the view so that the view would be maintained serially to avoid
+	 * the inconsistency that occurs when two base tables are modified in
+	 * concurrent transactions. However, if the view has only one table,
+	 * we can use a weaker lock.
+	 *
+	 * The type of lock should be determined here, because if we check the
+	 * view definition at maintenance time, we need to acquire a weaker lock,
+	 * and upgrading the lock level after this increases probability of
+	 * deadlock.
+	 */
+
+	rte = list_nth(qry->rtable, first_rtindex - 1);
+	if (list_length(qry->rtable) > first_rtindex ||
+		rte->rtekind != RTE_RELATION)
+		ex_lock = true;
+
+	CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *)qry, matviewOid, &relids, ex_lock);
+
+	bms_free(relids);
+}
+
+static void
+CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid,
+									 Relids *relids, bool ex_lock)
+{
+	if (node == NULL)
+		return;
+
+	/* This can recurse, so check for excessive recursion */
+	check_stack_depth();
+
+	switch (nodeTag(node))
+	{
+		case T_Query:
+			{
+				Query *query = (Query *) node;
+
+				CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *)query->jointree, matviewOid, relids, ex_lock);
+			}
+			break;
+
+		case T_RangeTblRef:
+			{
+				int			rti = ((RangeTblRef *) node)->rtindex;
+				RangeTblEntry *rte = rt_fetch(rti, qry->rtable);
+
+				if (rte->rtekind == RTE_RELATION && !bms_is_member(rte->relid, *relids))
+				{
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_BEFORE, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_BEFORE, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_BEFORE, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_AFTER, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_AFTER, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_AFTER, ex_lock);
+
+					*relids = bms_add_member(*relids, rte->relid);
+				}
+			}
+			break;
+
+		case T_FromExpr:
+			{
+				FromExpr   *f = (FromExpr *) node;
+				ListCell   *l;
+
+				foreach(l, f->fromlist)
+					CreateIvmTriggersOnBaseTablesRecurse(qry, lfirst(l), matviewOid, relids, ex_lock);
+			}
+			break;
+
+		case T_JoinExpr:
+			{
+				JoinExpr   *j = (JoinExpr *) node;
+
+				CreateIvmTriggersOnBaseTablesRecurse(qry, j->larg, matviewOid, relids, ex_lock);
+				CreateIvmTriggersOnBaseTablesRecurse(qry, j->rarg, matviewOid, relids, ex_lock);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
+	}
+}
+
+/*
+ * CreateIvmTrigger -- create IVM trigger on a base table
+ */
+static void
+CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock)
+{
+	ObjectAddress	refaddr;
+	ObjectAddress	address;
+	CreateTrigStmt *ivm_trigger;
+	List *transitionRels = NIL;
+
+	Assert(timing == TRIGGER_TYPE_BEFORE || timing == TRIGGER_TYPE_AFTER);
+
+	refaddr.classId = RelationRelationId;
+	refaddr.objectId = viewOid;
+	refaddr.objectSubId = 0;
+
+	ivm_trigger = makeNode(CreateTrigStmt);
+	ivm_trigger->relation = NULL;
+	ivm_trigger->row = false;
+
+	ivm_trigger->timing = timing;
+	ivm_trigger->events = type;
+
+	switch (type)
+	{
+		case TRIGGER_TYPE_INSERT:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_ins_before" : "IVM_trigger_ins_after");
+			break;
+		case TRIGGER_TYPE_DELETE:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_del_before" : "IVM_trigger_del_after");
+			break;
+		case TRIGGER_TYPE_UPDATE:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_upd_before" : "IVM_trigger_upd_after");
+			break;
+		default:
+			elog(ERROR, "unsupported trigger type");
+	}
+
+	if (timing == TRIGGER_TYPE_AFTER)
+	{
+		if (type == TRIGGER_TYPE_INSERT || type == TRIGGER_TYPE_UPDATE)
+		{
+			TriggerTransition *n = makeNode(TriggerTransition);
+			n->name = "__ivm_newtable";
+			n->isNew = true;
+			n->isTable = true;
+
+			transitionRels = lappend(transitionRels, n);
+		}
+		if (type == TRIGGER_TYPE_DELETE || type == TRIGGER_TYPE_UPDATE)
+		{
+			TriggerTransition *n = makeNode(TriggerTransition);
+			n->name = "__ivm_oldtable";
+			n->isNew = false;
+			n->isTable = true;
+
+			transitionRels = lappend(transitionRels, n);
+		}
+	}
+
+	ivm_trigger->funcname =
+		(timing == TRIGGER_TYPE_BEFORE ? SystemFuncName("IVM_immediate_before") : SystemFuncName("IVM_immediate_maintenance"));
+
+	ivm_trigger->columns = NIL;
+	ivm_trigger->transitionRels = transitionRels;
+	ivm_trigger->whenClause = NULL;
+	ivm_trigger->isconstraint = false;
+	ivm_trigger->deferrable = false;
+	ivm_trigger->initdeferred = false;
+	ivm_trigger->constrrel = NULL;
+	ivm_trigger->args = list_make2(
+		makeString(DatumGetPointer(DirectFunctionCall1(oidout, ObjectIdGetDatum(viewOid)))),
+		makeString(DatumGetPointer(DirectFunctionCall1(boolout, BoolGetDatum(ex_lock))))
+		);
+
+	address = CreateTrigger(ivm_trigger, NULL, relOid, InvalidOid, InvalidOid,
+						 InvalidOid, InvalidOid, InvalidOid, NULL, true, false);
+
+	recordDependencyOn(&address, &refaddr, DEPENDENCY_IMMV);
+
+	/* Make changes-so-far visible */
+	CommandCounterIncrement();
+}
+
+/*
+ * check_ivm_restriction --- look for specify nodes in the query tree
+ */
+static void
+check_ivm_restriction(Node *node)
+{
+	check_ivm_restriction_walker(node, NULL);
+}
+
+static bool
+check_ivm_restriction_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	/*
+	 * We currently don't support Sub-Query.
+	 */
+	if (IsA(node, SubPlan) || IsA(node, SubLink))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("subquery is not supported on incrementally maintainable materialized view")));
+
+	/* This can recurse, so check for excessive recursion */
+	check_stack_depth();
+
+	switch (nodeTag(node))
+	{
+		case T_Query:
+			{
+				Query *qry = (Query *)node;
+				ListCell   *lc;
+				List       *vars;
+
+				/* if contained CTE, return error */
+				if (qry->cteList != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("CTE is not supported on incrementally maintainable materialized view")));
+				if (qry->havingQual != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg(" HAVING clause is not supported on incrementally maintainable materialized view")));
+				if (qry->sortClause != NIL)	/* There is a possibility that we don't need to return an error */
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("ORDER BY clause is not supported on incrementally maintainable materialized view")));
+				if (qry->limitOffset != NULL || qry->limitCount != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("LIMIT/OFFSET clause is not supported on incrementally maintainable materialized view")));
+				if (qry->hasDistinctOn)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DISTINCT ON is not supported on incrementally maintainable materialized view")));
+				if (qry->hasWindowFuncs)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("window functions are not supported on incrementally maintainable materialized view")));
+				if (qry->groupingSets != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("GROUPING SETS, ROLLUP, or CUBE clauses is not supported on incrementally maintainable materialized view")));
+				if (qry->setOperations != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("UNION/INTERSECT/EXCEPT statements are not supported on incrementally maintainable materialized view")));
+				if (list_length(qry->targetList) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("empty target list is not supported on incrementally maintainable materialized view")));
+				if (qry->rowMarks != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("FOR UPDATE/SHARE clause is not supported on incrementally maintainable materialized view")));
+
+				/* system column restrictions */
+				vars = pull_vars_of_level((Node *) qry, 0);
+				foreach(lc, vars)
+				{
+					if (IsA(lfirst(lc), Var))
+					{
+						Var *var = (Var *) lfirst(lc);
+						/* if system column, return error */
+						if (var->varattno < 0)
+							ereport(ERROR,
+									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									 errmsg("system column is not supported on incrementally maintainable materialized view")));
+					}
+				}
+
+				/* restrictions for rtable */
+				foreach(lc, qry->rtable)
+				{
+					RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+					if (rte->subquery)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("subquery is not supported on incrementally maintainable materialized view")));
+
+					if (rte->tablesample != NULL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("TABLESAMPLE clause is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("partitioned table is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_RELATION && has_superclass(rte->relid))
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("partitions is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_RELATION && find_inheritance_children(rte->relid, NoLock) != NIL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("inheritance parent is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_FOREIGN_TABLE)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("foreign table is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_VIEW ||
+						rte->relkind == RELKIND_MATVIEW)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view")));
+
+					if (rte->rtekind == RTE_VALUES)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("VALUES is not supported on incrementally maintainable materialized view")));
+
+				}
+
+				query_tree_walker(qry, check_ivm_restriction_walker, NULL, QTW_IGNORE_RANGE_TABLE);
+
+				break;
+			}
+		case T_TargetEntry:
+			{
+				TargetEntry *tle = (TargetEntry *)node;
+				if (isIvmName(tle->resname))
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("column name %s is not supported on incrementally maintainable materialized view", tle->resname)));
+
+				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				break;
+			}
+		case T_JoinExpr:
+			{
+				JoinExpr *joinexpr = (JoinExpr *)node;
+
+				if (joinexpr->jointype > JOIN_INNER)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("OUTER JOIN is not supported on incrementally maintainable materialized view")));
+
+				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+			}
+			break;
+			expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+			break;
+	}
+	return false;
+}
+
+/*
+ * CreateIndexOnIMMV
+ *
+ * Create a unique index on incremental maintainable materialized view.
+ * If the view definition query has a GROUP BY clause, the index is created
+ * on the columns of GROUP BY expressions. Otherwise, if the view contains
+ * all primary key attritubes of its base tables in the target list, the index
+ * is created on these attritubes. In other cases, no index is created.
+ */
+void
+CreateIndexOnIMMV(Query *query, Relation matviewRel)
+{
+	Query *qry = (Query *) copyObject(query);
+	ListCell *lc;
+	IndexStmt  *index;
+	ObjectAddress address;
+	List *constraintList = NIL;
+	char		idxname[NAMEDATALEN];
+	List	   *indexoidlist = RelationGetIndexList(matviewRel);
+	ListCell   *indexoidscan;
+
+	snprintf(idxname, sizeof(idxname), "%s_index", RelationGetRelationName(matviewRel));
+
+	index = makeNode(IndexStmt);
+
+	index->unique = true;
+	index->primary = false;
+	index->isconstraint = false;
+	index->deferrable = false;
+	index->initdeferred = false;
+	index->idxname = idxname;
+	index->relation =
+		makeRangeVar(get_namespace_name(RelationGetNamespace(matviewRel)),
+					 pstrdup(RelationGetRelationName(matviewRel)),
+					 -1);
+	index->accessMethod = DEFAULT_INDEX_TYPE;
+	index->options = NIL;
+	index->tableSpace = get_tablespace_name(matviewRel->rd_rel->reltablespace);
+	index->whereClause = NULL;
+	index->indexParams = NIL;
+	index->indexIncludingParams = NIL;
+	index->excludeOpNames = NIL;
+	index->idxcomment = NULL;
+	index->indexOid = InvalidOid;
+	index->oldNode = InvalidOid;
+	index->oldCreateSubid = InvalidSubTransactionId;
+	index->oldFirstRelfilenodeSubid = InvalidSubTransactionId;
+	index->transformed = true;
+	index->concurrent = false;
+	index->if_not_exists = false;
+
+	if (qry->distinctClause)
+	{
+		/* create unique constraint on all columns */
+		foreach(lc, qry->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+			IndexElem  *iparam;
+
+			iparam = makeNode(IndexElem);
+			iparam->name = pstrdup(NameStr(attr->attname));
+			iparam->expr = NULL;
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->opclassopts = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+		}
+	}
+	else
+	{
+		Bitmapset *key_attnos;
+
+		/* create index on the base tables' primary key columns */
+		key_attnos = get_primary_key_attnos_from_query(qry, &constraintList);
+		if (key_attnos)
+		{
+			foreach(lc, qry->targetList)
+			{
+				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+				Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+
+				if (bms_is_member(tle->resno - FirstLowInvalidHeapAttributeNumber, key_attnos))
+				{
+					IndexElem  *iparam;
+
+					iparam = makeNode(IndexElem);
+					iparam->name = pstrdup(NameStr(attr->attname));
+					iparam->expr = NULL;
+					iparam->indexcolname = NULL;
+					iparam->collation = NIL;
+					iparam->opclass = NIL;
+					iparam->opclassopts = NIL;
+					iparam->ordering = SORTBY_DEFAULT;
+					iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+					index->indexParams = lappend(index->indexParams, iparam);
+				}
+			}
+
+		}
+		else
+		{
+			/* create no index, just notice that an appropriate index is necessary for efficient IVM */
+			ereport(NOTICE,
+					(errmsg("could not create an index on materialized view \"%s\" automatically",
+							RelationGetRelationName(matviewRel)),
+					 errdetail("This target list does not have all the primary key columns, "
+							   "or this view does not contain DISTINCT clause."),
+					 errhint("Create an index on the materialized view for efficient incremental maintenance.")));
+			return;
+		}
+	}
+
+
+	/* If we have a compatible index, we don't need to create another. */
+	foreach(indexoidscan, indexoidlist)
+	{
+		Oid			indexoid = lfirst_oid(indexoidscan);
+		Relation	indexRel;
+		bool		hasCompatibleIndex = false;
+
+		indexRel = index_open(indexoid, AccessShareLock);
+
+		if (CheckIndexCompatible(indexRel->rd_id,
+								index->accessMethod,
+								index->indexParams,
+								index->excludeOpNames))
+			hasCompatibleIndex = true;
+
+		index_close(indexRel, AccessShareLock);
+
+		if (hasCompatibleIndex)
+			return;
+	}
+
+	address = DefineIndex(RelationGetRelid(matviewRel),
+						  index,
+						  InvalidOid,
+						  InvalidOid,
+						  InvalidOid,
+						  false, true, false, false, true);
+
+	ereport(NOTICE,
+			(errmsg("created index \"%s\" on materialized view \"%s\"",
+					idxname, RelationGetRelationName(matviewRel))));
+
+
+	/*
+	 * Make dependencies so that the index is dropped if any base tables's
+	 * primary key is dropped.
+	 */
+	foreach(lc, constraintList)
+	{
+		Oid constraintOid = lfirst_oid(lc);
+		ObjectAddress	refaddr;
+
+		refaddr.classId = ConstraintRelationId;
+		refaddr.objectId = constraintOid;
+		refaddr.objectSubId = 0;
+
+		recordDependencyOn(&address, &refaddr, DEPENDENCY_NORMAL);
+	}
+}
+
+
+/*
+ * get_primary_key_attnos_from_query
+ *
+ * Identify the columns in base tables' primary keys in the target list.
+ *
+ * Returns a Bitmapset of the column attnos of the primary key's columns of
+ * tables that used in the query.  The attnos are offset by
+ * FirstLowInvalidHeapAttributeNumber as same as get_primary_key_attnos.
+ *
+ * If any table has no primary key or any primary key's columns is not in
+ * the target list, return NULL.  We also return NULL if any pkey constraint
+ * is deferrable.
+ *
+ * constraintList is set to a list of the OIDs of the pkey constraints.
+ */
+static Bitmapset *
+get_primary_key_attnos_from_query(Query *query, List **constraintList)
+{
+	List *key_attnos_list = NIL;
+	ListCell *lc;
+	int i;
+	Bitmapset *keys = NULL;
+	Relids	rels_in_from;
+	PlannerInfo root;
+
+
+	/*
+	 * Collect primary key attributes from all tables used in query. The key attributes
+	 * sets for each table are stored in key_attnos_list in order by RTE index.
+	 */
+	i = 1;
+	foreach(lc, query->rtable)
+	{
+		RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+		Bitmapset *key_attnos;
+		bool	has_pkey = true;
+
+		/* for tables, call get_primary_key_attnos */
+		if (r->rtekind == RTE_RELATION)
+		{
+			Oid constraintOid;
+			key_attnos = get_primary_key_attnos(r->relid, false, &constraintOid);
+			*constraintList = lappend_oid(*constraintList, constraintOid);
+			has_pkey = (key_attnos != NULL);
+		}
+		/* for other RTEs, store NULL into key_attnos_list */
+		else
+			key_attnos = NULL;
+
+		/*
+		 * If any table or subquery has no primary key or its pkey constraint is deferrable,
+		 * we cannot get key attributes for this query, so return NULL.
+		 */
+		if (!has_pkey)
+			return NULL;
+
+		key_attnos_list = lappend(key_attnos_list, key_attnos);
+	}
+
+	/* Collect key attributes appearing in the target list */
+	i = 1;
+	foreach(lc, query->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) flatten_join_alias_vars(query, lfirst(lc));
+
+		if (IsA(tle->expr, Var))
+		{
+			Var *var = (Var*) tle->expr;
+			Bitmapset *attnos = list_nth(key_attnos_list, var->varno - 1);
+
+			/* check if this attribute is from a base table's primary key */
+			if (bms_is_member(var->varattno - FirstLowInvalidHeapAttributeNumber, attnos))
+			{
+				/*
+				 * Remove found key attributes from key_attnos_list, and add this
+				 * to the result list.
+				 */
+				bms_del_member(attnos, var->varattno - FirstLowInvalidHeapAttributeNumber);
+				keys = bms_add_member(keys, i - FirstLowInvalidHeapAttributeNumber);
+			}
+		}
+		i++;
+	}
+
+	/* Collect relations appearing in the FROM clause */
+	rels_in_from = pull_varnos_of_level(&root, (Node *)query->jointree, 0);
+
+	/*
+	 * Check if all key attributes of relations in FROM are appearing in the target
+	 * list.  If an attribute remains in key_attnos_list in spite of the table is used
+	 * in FROM clause, the target is missing this key attribute, so we return NULL.
+	 */
+	i = 1;
+	foreach(lc, key_attnos_list)
+	{
+		Bitmapset *bms = (Bitmapset *)lfirst(lc);
+		if (!bms_is_empty(bms) && bms_is_member(i, rels_in_from))
+			return NULL;
+		i++;
+	}
+
+	return keys;
+}
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 42aacc8f0a..dfa4408d1c 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -36,6 +36,7 @@
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
+#include "commands/matview.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
@@ -1054,6 +1055,45 @@ DefineIndex(Oid relationId,
 	safe_index = indexInfo->ii_Expressions == NIL &&
 		indexInfo->ii_Predicate == NIL;
 
+	/*
+	 * We disallow unique indexes on IVM columns of IMMVs.
+	 */
+	if (RelationIsIVM(rel) && stmt->unique)
+	{
+		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
+		{
+			AttrNumber	attno = indexInfo->ii_IndexAttrNumbers[i];
+			if (attno > 0)
+			{
+				char *name = NameStr(TupleDescAttr(rel->rd_att, attno - 1)->attname);
+				if (name && isIvmName(name))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique index creation on IVM columns is not supported")));
+			}
+		}
+
+		if (indexInfo->ii_Expressions)
+		{
+			Bitmapset  *indexattrs = NULL;
+			int			varno = -1;
+
+			pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
+
+			while ((varno = bms_next_member(indexattrs, varno)) >= 0)
+			{
+				int attno = varno + FirstLowInvalidHeapAttributeNumber;
+				char *name = NameStr(TupleDescAttr(rel->rd_att, attno - 1)->attname);
+				if (name && isIvmName(name))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique index creation on IVM columns is not supported")));
+			}
+
+		}
+	}
+
+
 	/*
 	 * Report index creation if appropriate (delay this till after most of the
 	 * error checks)
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 05e7b60059..943de5dfba 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -25,26 +25,47 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_type.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "commands/cluster.h"
+#include "commands/defrem.h"
 #include "commands/matview.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
+#include "commands/createas.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "executor/tstoreReceiver.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_func.h"
 #include "parser/parse_relation.h"
+#include "parser/parse_type.h"
 #include "pgstat.h"
 #include "rewrite/rewriteHandler.h"
+#include "rewrite/rewriteManip.h"
+#include "rewrite/rowsecurity.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
 
 
 typedef struct
@@ -58,6 +79,50 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_transientrel;
 
+#define MV_INIT_QUERYHASHSIZE	16
+
+/*
+ * MV_TriggerHashEntry
+ *
+ * Hash entry for base tables on which IVM trigger is invoked
+ */
+typedef struct MV_TriggerHashEntry
+{
+	Oid	matview_id;			/* OID of the materialized view */
+	int	before_trig_count;	/* count of before triggers invoked */
+	int	after_trig_count;	/* count of after triggers invoked */
+
+	TransactionId	xid;	/* Transaction id before the first table is modified*/
+	CommandId		cid;	/* Command id before the first table is modified */
+
+	List   *tables;		/* List of MV_TriggerTable */
+	bool	has_old;	/* tuples are deleted from any table? */
+	bool	has_new;	/* tuples are inserted into any table? */
+} MV_TriggerHashEntry;
+
+/*
+ * MV_TriggerTable
+ *
+ * IVM related data for tables on which the trigger is invoked.
+ */
+typedef struct MV_TriggerTable
+{
+	Oid		table_id;			/* OID of the modified table */
+	List   *old_tuplestores;	/* tuplestores for deleted tuples */
+	List   *new_tuplestores;	/* tuplestores for inserted tuples */
+	List   *old_rtes;			/* RTEs of ENRs for old_tuplestores*/
+	List   *new_rtes;			/* RTEs of ENRs for new_tuplestores */
+
+	List   *rte_indexes;		/* List of RTE index of the modified table */
+	RangeTblEntry *original_rte;	/* the original RTE saved before rewriting query */
+} MV_TriggerTable;
+
+static HTAB *mv_trigger_info = NULL;
+
+/* ENR name for materialized view delta */
+#define NEW_DELTA_ENRNAME "new_delta"
+#define OLD_DELTA_ENRNAME "old_delta"
+
 static int	matview_maintenance_depth = 0;
 
 static void transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
@@ -65,7 +130,9 @@ static bool transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void transientrel_shutdown(DestReceiver *self);
 static void transientrel_destroy(DestReceiver *self);
 static uint64 refresh_matview_datafill(DestReceiver *dest, Query *query,
-									   const char *queryString);
+						 QueryEnvironment *queryEnv,
+						 TupleDesc *resultTupleDesc,
+						 const char *queryString);
 static char *make_temptable_name_n(char *tempname, int n);
 static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 								   int save_sec_context);
@@ -73,6 +140,45 @@ static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersist
 static bool is_usable_unique_index(Relation indexRel);
 static void OpenMatViewIncrementalMaintenance(void);
 static void CloseMatViewIncrementalMaintenance(void);
+static Query *get_matview_query(Relation matviewRel);
+
+static Query *rewrite_query_for_preupdate_state(Query *query, List *tables,
+								  TransactionId xid, CommandId cid,
+								  ParseState *pstate);
+static void register_delta_ENRs(ParseState *pstate, Query *query, List *tables);
+static char *make_delta_enr_name(const char *prefix, Oid relid, int count);
+static RangeTblEntry *get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
+				 TransactionId xid, CommandId cid,
+				 QueryEnvironment *queryEnv);
+static RangeTblEntry *union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
+		   QueryEnvironment *queryEnv);
+static Query *rewrite_query_for_distinct(Query *query, ParseState *pstate);
+
+static void calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
+			DestReceiver *dest_old, DestReceiver *dest_new,
+			TupleDesc *tupdesc_old, TupleDesc *tupdesc_new,
+			QueryEnvironment *queryEnv);
+static Query *rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte_index);
+
+static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
+			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
+			Query *query, bool use_count, char *count_colname);
+static void apply_old_delta(const char *matviewname, const char *deltaname_old,
+				List *keys);
+static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
+				List *keys, const char *count_colname);
+static void apply_new_delta(const char *matviewname, const char *deltaname_new,
+				StringInfo target_list);
+static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
+				List *keys, StringInfo target_list, const char* count_colname);
+static char *get_matching_condition_string(List *keys);
+static void generate_equal(StringInfo querybuf, Oid opttype,
+			   const char *leftop, const char *rightop);
+
+static void mv_InitHashTables(void);
+static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry);
+
+static List *get_securityQuals(Oid relId, int rt_index, Query *query);
 
 /*
  * SetMatViewPopulatedState
@@ -114,6 +220,46 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
 	CommandCounterIncrement();
 }
 
+/*
+ * SetMatViewIVMState
+ *		Mark a materialized view as IVM, or not.
+ *
+ * NOTE: caller must be holding an appropriate lock on the relation.
+ */
+void
+SetMatViewIVMState(Relation relation, bool newstate)
+{
+	Relation	pgrel;
+	HeapTuple	tuple;
+
+	Assert(relation->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Update relation's pg_class entry.  Crucial side-effect: other backends
+	 * (and this one too!) are sent SI message to make them rebuild relcache
+	 * entries.
+	 */
+	pgrel = table_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(relation)));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u",
+			 RelationGetRelid(relation));
+
+	((Form_pg_class) GETSTRUCT(tuple))->relisivm = newstate;
+
+	CatalogTupleUpdate(pgrel, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	table_close(pgrel, RowExclusiveLock);
+
+	/*
+	 * Advance command counter to make the updated pg_class row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+}
+
 /*
  * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command
  *
@@ -140,9 +286,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 {
 	Oid			matviewOid;
 	Relation	matviewRel;
-	RewriteRule *rule;
-	List	   *actions;
 	Query	   *dataQuery;
+	Query	   *viewQuery;
 	Oid			tableSpace;
 	Oid			relowner;
 	Oid			OIDNewHeap;
@@ -155,6 +300,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 	int			save_sec_context;
 	int			save_nestlevel;
 	ObjectAddress address;
+	bool oldPopulated;
 
 	/* Determine strength of lock needed. */
 	concurrent = stmt->concurrent;
@@ -167,6 +313,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										  lockmode, 0,
 										  RangeVarCallbackOwnsTable, NULL);
 	matviewRel = table_open(matviewOid, NoLock);
+	oldPopulated = RelationIsPopulated(matviewRel);
 
 	/* Make sure it is a materialized view. */
 	if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW)
@@ -188,32 +335,14 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 				 errmsg("%s and %s options cannot be used together",
 						"CONCURRENTLY", "WITH NO DATA")));
 
-	/*
-	 * Check that everything is correct for a refresh. Problems at this point
-	 * are internal errors, so elog is sufficient.
-	 */
-	if (matviewRel->rd_rel->relhasrules == false ||
-		matviewRel->rd_rules->numLocks < 1)
-		elog(ERROR,
-			 "materialized view \"%s\" is missing rewrite information",
-			 RelationGetRelationName(matviewRel));
-
-	if (matviewRel->rd_rules->numLocks > 1)
-		elog(ERROR,
-			 "materialized view \"%s\" has too many rules",
-			 RelationGetRelationName(matviewRel));
 
-	rule = matviewRel->rd_rules->rules[0];
-	if (rule->event != CMD_SELECT || !(rule->isInstead))
-		elog(ERROR,
-			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
-			 RelationGetRelationName(matviewRel));
+	viewQuery = get_matview_query(matviewRel);
 
-	actions = rule->actions;
-	if (list_length(actions) != 1)
-		elog(ERROR,
-			 "the rule for materialized view \"%s\" is not a single action",
-			 RelationGetRelationName(matviewRel));
+	/* For IMMV, we need to rewrite matview query */
+	if (!stmt->skipData && RelationIsIVM(matviewRel))
+		dataQuery = rewriteQueryForIMMV(viewQuery,NIL);
+	else
+		dataQuery = viewQuery;
 
 	/*
 	 * Check that there is a unique index with no WHERE clause on one or more
@@ -248,12 +377,6 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 					 errhint("Create a unique index with no WHERE clause on one or more columns of the materialized view.")));
 	}
 
-	/*
-	 * The stored query was rewritten at the time of the MV definition, but
-	 * has not been scribbled on by the planner.
-	 */
-	dataQuery = linitial_node(Query, actions);
-
 	/*
 	 * Check for active uses of the relation in the current transaction, such
 	 * as open scans.
@@ -294,6 +417,52 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 		relpersistence = matviewRel->rd_rel->relpersistence;
 	}
 
+	/* delete immv triggers */
+	if (RelationIsIVM(matviewRel) && stmt->skipData )
+	{
+		/* use deleted trigger */
+		Relation	depRel;
+		ScanKeyData key;
+		SysScanDesc scan;
+		HeapTuple	tup;
+		ObjectAddresses *immv_triggers;
+
+		immv_triggers = new_object_addresses();
+
+		/*
+		 * We save some cycles by opening pg_depend just once and passing the
+		 * Relation pointer down to all the recursive deletion steps.
+		 */
+		depRel = table_open(DependRelationId, RowExclusiveLock);
+
+		ScanKeyInit(&key,
+					Anum_pg_depend_refobjid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(matviewOid));
+
+		scan = systable_beginscan(depRel, DependReferenceIndexId, true,
+								  NULL, 1, &key);
+		while ((tup = systable_getnext(scan)) != NULL)
+		{
+			ObjectAddress obj;
+			Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
+
+			if (foundDep->deptype == DEPENDENCY_IMMV)
+			{
+				obj.classId = foundDep->classid;
+				obj.objectId = foundDep->objid;
+				obj.objectSubId = foundDep->refobjsubid;
+				add_exact_object_address(&obj, immv_triggers);
+			}
+		}
+		systable_endscan(scan);
+
+		performMultipleDeletions(immv_triggers, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
+
+		table_close(depRel, RowExclusiveLock);
+		free_object_addresses(immv_triggers);
+	}
+
 	/*
 	 * Create the transient table that will receive the regenerated data. Lock
 	 * it against access by any other process until commit (by which time it
@@ -313,7 +482,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 
 	/* Generate the data, if wanted. */
 	if (!stmt->skipData)
-		processed = refresh_matview_datafill(dest, dataQuery, queryString);
+		processed = refresh_matview_datafill(dest, dataQuery, NULL, NULL, queryString);
 
 	/* Make the matview match the newly generated data. */
 	if (concurrent)
@@ -348,6 +517,12 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 			pgstat_count_heap_insert(matviewRel, processed);
 	}
 
+	if (!stmt->skipData && RelationIsIVM(matviewRel) && !oldPopulated)
+	{
+		CreateIndexOnIMMV(viewQuery, matviewRel);
+		CreateIvmTriggersOnBaseTables(dataQuery, matviewOid, false);
+	}
+
 	table_close(matviewRel, NoLock);
 
 	/* Roll back any GUC changes */
@@ -382,6 +557,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  */
 static uint64
 refresh_matview_datafill(DestReceiver *dest, Query *query,
+						 QueryEnvironment *queryEnv,
+						 TupleDesc *resultTupleDesc,
 						 const char *queryString)
 {
 	List	   *rewritten;
@@ -418,7 +595,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 	/* Create a QueryDesc, redirecting output to our tuple receiver */
 	queryDesc = CreateQueryDesc(plan, queryString,
 								GetActiveSnapshot(), InvalidSnapshot,
-								dest, NULL, NULL, 0);
+								dest, NULL, queryEnv ? queryEnv: NULL, 0);
 
 	/* call ExecutorStart to prepare the plan for execution */
 	ExecutorStart(queryDesc, 0);
@@ -428,6 +605,9 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 
 	processed = queryDesc->estate->es_processed;
 
+	if (resultTupleDesc)
+		*resultTupleDesc = CreateTupleDescCopy(queryDesc->tupDesc);
+
 	/* and clean up */
 	ExecutorFinish(queryDesc);
 	ExecutorEnd(queryDesc);
@@ -942,3 +1122,1308 @@ CloseMatViewIncrementalMaintenance(void)
 	matview_maintenance_depth--;
 	Assert(matview_maintenance_depth >= 0);
 }
+
+/*
+ * get_matview_query - get the Query from a matview's _RETURN rule.
+ */
+static Query *
+get_matview_query(Relation matviewRel)
+{
+	RewriteRule *rule;
+	List * actions;
+
+	/*
+	 * Check that everything is correct for a refresh. Problems at this point
+	 * are internal errors, so elog is sufficient.
+	 */
+	if (matviewRel->rd_rel->relhasrules == false ||
+		matviewRel->rd_rules->numLocks < 1)
+		elog(ERROR,
+			 "materialized view \"%s\" is missing rewrite information",
+			 RelationGetRelationName(matviewRel));
+
+	if (matviewRel->rd_rules->numLocks > 1)
+		elog(ERROR,
+			 "materialized view \"%s\" has too many rules",
+			 RelationGetRelationName(matviewRel));
+
+	rule = matviewRel->rd_rules->rules[0];
+	if (rule->event != CMD_SELECT || !(rule->isInstead))
+		elog(ERROR,
+			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
+			 RelationGetRelationName(matviewRel));
+
+	actions = rule->actions;
+	if (list_length(actions) != 1)
+		elog(ERROR,
+			 "the rule for materialized view \"%s\" is not a single action",
+			 RelationGetRelationName(matviewRel));
+
+	/*
+	 * The stored query was rewritten at the time of the MV definition, but
+	 * has not been scribbled on by the planner.
+	 */
+	return linitial_node(Query, actions);
+}
+
+
+/* ----------------------------------------------------
+ *		Incremental View Maintenance routines
+ * ---------------------------------------------------
+ */
+
+/*
+ * IVM_immediate_before
+ *
+ * IVM trigger function invoked before base table is modified. If this is
+ * invoked firstly in the same statement, we save the transaction id and the
+ * command id at that time.
+ */
+Datum
+IVM_immediate_before(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	char	   *matviewOid_text = trigdata->tg_trigger->tgargs[0];
+	char	   *ex_lock_text = trigdata->tg_trigger->tgargs[1];
+	Oid			matviewOid;
+	MV_TriggerHashEntry *entry;
+	bool	found;
+	bool	ex_lock;
+
+	matviewOid = DatumGetObjectId(DirectFunctionCall1(oidin, CStringGetDatum(matviewOid_text)));
+	ex_lock = DatumGetBool(DirectFunctionCall1(boolin, CStringGetDatum(ex_lock_text)));
+
+	/* If the view has more than one tables, we have to use an exclusive lock. */
+	if (ex_lock)
+	{
+		/*
+		 * Wait for concurrent transactions which update this materialized view at
+		 * READ COMMITED. This is needed to see changes committed in other
+		 * transactions. No wait and raise an error at REPEATABLE READ or
+		 * SERIALIZABLE to prevent update anomalies of matviews.
+		 * XXX: dead-lock is possible here.
+		 */
+		if (!IsolationUsesXactSnapshot())
+			LockRelationOid(matviewOid, ExclusiveLock);
+		else if (!ConditionalLockRelationOid(matviewOid, ExclusiveLock))
+		{
+			/* try to throw error by name; relation could be deleted... */
+			char	   *relname = get_rel_name(matviewOid);
+
+			if (!relname)
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						errmsg("could not obtain lock on materialized view during incremental maintenance")));
+
+			ereport(ERROR,
+					(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+					errmsg("could not obtain lock on materialized view \"%s\" during incremental maintenance",
+							relname)));
+		}
+	}
+	else
+		LockRelationOid(matviewOid, RowExclusiveLock);
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_trigger_info)
+		mv_InitHashTables();
+
+	entry = (MV_TriggerHashEntry *) hash_search(mv_trigger_info,
+											  (void *) &matviewOid,
+											  HASH_ENTER, &found);
+
+	/* On the first BEFORE to update the view, initialize trigger data */
+	if (!found)
+	{
+		Snapshot snapshot = GetActiveSnapshot();
+
+		entry->matview_id = matviewOid;
+		entry->before_trig_count = 0;
+		entry->after_trig_count = 0;
+		entry->xid = GetCurrentTransactionId();
+		entry->cid = snapshot->curcid;
+		entry->tables = NIL;
+		entry->has_old = false;
+		entry->has_new = false;
+	}
+
+	entry->before_trig_count++;
+
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * IVM_immediate_before
+ *
+ * IVM trigger function invoked after base table is modified.
+ * For each table, tuplestores of transition tables are collected.
+ * and after the last modification
+ */
+Datum
+IVM_immediate_maintenance(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	Relation	rel;
+	Oid			relid;
+	Oid			matviewOid;
+	Query	   *query;
+	Query	   *rewritten = NULL;
+	char	   *matviewOid_text = trigdata->tg_trigger->tgargs[0];
+	Relation	matviewRel;
+	int old_depth = matview_maintenance_depth;
+
+	Oid			relowner;
+	Tuplestorestate *old_tuplestore = NULL;
+	Tuplestorestate *new_tuplestore = NULL;
+	DestReceiver *dest_new = NULL, *dest_old = NULL;
+	Oid			save_userid;
+	int			save_sec_context;
+	int			save_nestlevel;
+
+	MV_TriggerHashEntry *entry;
+	MV_TriggerTable		*table;
+	bool	found;
+
+	ParseState		 *pstate;
+	QueryEnvironment *queryEnv = create_queryEnv();
+	MemoryContext	oldcxt;
+	ListCell   *lc;
+	int			i;
+
+
+	/* Create a ParseState for rewriting the view definition query */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	rel = trigdata->tg_relation;
+	relid = rel->rd_id;
+
+	matviewOid = DatumGetObjectId(DirectFunctionCall1(oidin, CStringGetDatum(matviewOid_text)));
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_trigger_info)
+		mv_InitHashTables();
+
+	/* get the entry for this materialized view */
+	entry = (MV_TriggerHashEntry *) hash_search(mv_trigger_info,
+											  (void *) &matviewOid,
+											  HASH_FIND, &found);
+	Assert (found && entry != NULL);
+	entry->after_trig_count++;
+
+	/* search the entry for the modified table and create new entry if not found */
+	found = false;
+	foreach(lc, entry->tables)
+	{
+		table = (MV_TriggerTable *) lfirst(lc);
+		if (table->table_id == relid)
+		{
+			found = true;
+			break;
+		}
+	}
+	if (!found)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		table = (MV_TriggerTable *) palloc0(sizeof(MV_TriggerTable));
+		table->table_id = relid;
+		table->old_tuplestores = NIL;
+		table->new_tuplestores = NIL;
+		table->old_rtes = NIL;
+		table->new_rtes = NIL;
+		table->rte_indexes = NIL;
+		entry->tables = lappend(entry->tables, table);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* Save the transition tables and make a request to not free immediately */
+	if (trigdata->tg_oldtable)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+		table->old_tuplestores = lappend(table->old_tuplestores, trigdata->tg_oldtable);
+		entry->has_old = true;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (trigdata->tg_newtable)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+		table->new_tuplestores = lappend(table->new_tuplestores, trigdata->tg_newtable);
+		entry->has_new = true;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (entry->has_new || entry->has_old)
+	{
+		CmdType cmd;
+
+		if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+			cmd = CMD_INSERT;
+		else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+			cmd = CMD_DELETE;
+		else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+			cmd = CMD_UPDATE;
+		else
+			elog(ERROR,"unsupported trigger type");
+
+		/* Prolong lifespan of transition tables to the end of the last AFTER trigger */
+		SetTransitionTablePreserved(relid, cmd);
+	}
+
+
+	/* If this is not the last AFTER trigger call, immediately exit. */
+	Assert (entry->before_trig_count >= entry->after_trig_count);
+	if (entry->before_trig_count != entry->after_trig_count)
+		return PointerGetDatum(NULL);
+
+	/*
+	 * If this is the last AFTER trigger call, continue and update the view.
+	 */
+
+	/*
+	 * Advance command counter to make the updated base table row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+
+	matviewRel = table_open(matviewOid, NoLock);
+
+	/* get view query*/
+	query = get_matview_query(matviewRel);
+
+	/* Make sure it is a materialized view. */
+	Assert(matviewRel->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Get and push the latast snapshot to see any changes which is committed
+	 * during waiting in other transactions at READ COMMITTED level.
+	 */
+	PushActiveSnapshot(GetTransactionSnapshot());
+
+	/*
+	 * Check for active uses of the relation in the current transaction, such
+	 * as open scans.
+	 *
+	 * NB: We count on this to protect us against problems with refreshing the
+	 * data using TABLE_INSERT_FROZEN.
+	 */
+	CheckTableNotInUse(matviewRel, "refresh a materialized view incrementally");
+
+	/*
+	 * Switch to the owner's userid, so that any functions are run as that
+	 * user.  Also arrange to make GUC variable changes local to this command.
+	 * We will switch modes when we are about to execute user code.
+	 */
+	relowner = matviewRel->rd_rel->relowner;
+	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+	save_nestlevel = NewGUCNestLevel();
+
+	/*
+	 * rewrite query for calculating deltas
+	 */
+
+	rewritten = copyObject(query);
+
+	/* Replace resnames in a target list with materialized view's attnames */
+	i = 0;
+	foreach (lc, rewritten->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char *resname = NameStr(attr->attname);
+
+		tle->resname = pstrdup(resname);
+		i++;
+	}
+
+	/* Set all tables in the query to pre-update state */
+	rewritten = rewrite_query_for_preupdate_state(rewritten, entry->tables,
+												  entry->xid, entry->cid,
+												  pstate);
+	/* Rewrite for DISTINCT clause */
+	rewritten = rewrite_query_for_distinct(rewritten, pstate);
+
+	/* Create tuplestores to store view deltas */
+	if (entry->has_old)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		old_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+		dest_old = CreateDestReceiver(DestTuplestore);
+		SetTuplestoreDestReceiverParams(dest_old,
+									old_tuplestore,
+									TopTransactionContext,
+									false,
+									NULL,
+									NULL);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (entry->has_new)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		new_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+		dest_new = CreateDestReceiver(DestTuplestore);
+		SetTuplestoreDestReceiverParams(dest_new,
+									new_tuplestore,
+									TopTransactionContext,
+									false,
+									NULL,
+									NULL);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* for all modified tables */
+	foreach(lc, entry->tables)
+	{
+		ListCell *lc2;
+
+		table = (MV_TriggerTable *) lfirst(lc);
+
+		/* loop for self-join */
+		foreach(lc2, table->rte_indexes)
+		{
+			int	rte_index = lfirst_int(lc2);
+			TupleDesc		tupdesc_old;
+			TupleDesc		tupdesc_new;
+			bool	use_count = false;
+			char   *count_colname = NULL;
+
+			count_colname = pstrdup("__ivm_count__");
+
+			if (query->distinctClause)
+				use_count = true;
+
+			/* calculate delta tables */
+			calc_delta(table, rte_index, rewritten, dest_old, dest_new,
+					   &tupdesc_old, &tupdesc_new, queryEnv);
+
+			/* Set the table in the query to post-update state */
+			rewritten = rewrite_query_for_postupdate_state(rewritten, table, rte_index);
+
+			PG_TRY();
+			{
+				/* apply the delta tables to the materialized view */
+				apply_delta(matviewOid, old_tuplestore, new_tuplestore,
+							tupdesc_old, tupdesc_new, query, use_count,
+							count_colname);
+			}
+			PG_CATCH();
+			{
+				matview_maintenance_depth = old_depth;
+				PG_RE_THROW();
+			}
+			PG_END_TRY();
+
+			/* clear view delta tuplestores */
+			if (old_tuplestore)
+				tuplestore_clear(old_tuplestore);
+			if (new_tuplestore)
+				tuplestore_clear(new_tuplestore);
+		}
+	}
+
+	/* Clean up hash entry and delete tuplestores */
+	clean_up_IVM_hash_entry(entry);
+	if (old_tuplestore)
+	{
+		dest_old->rDestroy(dest_old);
+		tuplestore_end(old_tuplestore);
+	}
+	if (new_tuplestore)
+	{
+		dest_new->rDestroy(dest_new);
+		tuplestore_end(new_tuplestore);
+	}
+
+	/* Pop the original snapshot. */
+	PopActiveSnapshot();
+
+	table_close(matviewRel, NoLock);
+
+	/* Roll back any GUC changes */
+	AtEOXact_GUC(false, save_nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(save_userid, save_sec_context);
+
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * rewrite_query_for_preupdate_state
+ *
+ * Rewrite the query so that base tables' RTEs will represent "pre-update"
+ * state of tables. This is necessary to calculate view delta after multiple
+ * tables are modified. xid and cid are the transaction id and command id
+ * before the first table was modified.
+ */
+static Query*
+rewrite_query_for_preupdate_state(Query *query, List *tables,
+								  TransactionId xid, CommandId cid,
+								  ParseState *pstate)
+{
+	ListCell *lc;
+	int num_rte = list_length(query->rtable);
+	int i;
+
+
+	/* register delta ENRs */
+	register_delta_ENRs(pstate, query, tables);
+
+	/* XXX: Is necessary? Is this right timing? */
+	AcquireRewriteLocks(query, true, false);
+
+	i = 1;
+	foreach(lc, query->rtable)
+	{
+		RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+
+		ListCell *lc2;
+		foreach(lc2, tables)
+		{
+			MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc2);
+			/*
+			 * if the modified table is found then replace the original RTE with
+			 * "pre-state" RTE and append its index to the list.
+			 */
+			if (r->relid == table->table_id)
+			{
+				lfirst(lc) = get_prestate_rte(r, table, xid, cid, pstate->p_queryEnv);
+				table->rte_indexes = lappend_int(table->rte_indexes, i);
+				break;
+			}
+		}
+
+		/* finish the loop if we processed all RTE included in the original query */
+		if (i++ >= num_rte)
+			break;
+	}
+
+	return query;
+}
+
+/*
+ * register_delta_ENRs
+ *
+ * For all modified tables, make ENRs for their transition tables
+ * and register them to the queryEnv. ENR's RTEs are also appended
+ * into the list in query tree.
+ */
+static void
+register_delta_ENRs(ParseState *pstate, Query *query, List *tables)
+{
+	QueryEnvironment *queryEnv = pstate->p_queryEnv;
+	ListCell *lc;
+	RangeTblEntry	*rte;
+
+	foreach(lc, tables)
+	{
+		MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc);
+		ListCell *lc2;
+		int count;
+
+		count = 0;
+		foreach(lc2, table->old_tuplestores)
+		{
+			Tuplestorestate *oldtable = (Tuplestorestate *) lfirst(lc2);
+			EphemeralNamedRelation enr =
+				palloc(sizeof(EphemeralNamedRelationData));
+			ParseNamespaceItem *nsitem;
+
+			enr->md.name = make_delta_enr_name("old", table->table_id, count);
+			enr->md.reliddesc = table->table_id;
+			enr->md.tupdesc = NULL;
+			enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+			enr->md.enrtuples = tuplestore_tuple_count(oldtable);
+			enr->reldata = oldtable;
+			register_ENR(queryEnv, enr);
+
+			nsitem = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+			rte = nsitem->p_rte;
+			/* if base table has RLS, set security condition to enr */
+			rte->securityQuals = get_securityQuals(table->table_id, list_length(query->rtable) + 1, query);
+
+			query->rtable = lappend(query->rtable, rte);
+			table->old_rtes = lappend(table->old_rtes, rte);
+
+			count++;
+		}
+
+		count = 0;
+		foreach(lc2, table->new_tuplestores)
+		{
+			Tuplestorestate *newtable = (Tuplestorestate *) lfirst(lc2);
+			EphemeralNamedRelation enr =
+				palloc(sizeof(EphemeralNamedRelationData));
+			ParseNamespaceItem *nsitem;
+
+			enr->md.name = make_delta_enr_name("new", table->table_id, count);
+			enr->md.reliddesc = table->table_id;
+			enr->md.tupdesc = NULL;
+			enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+			enr->md.enrtuples = tuplestore_tuple_count(newtable);
+			enr->reldata = newtable;
+			register_ENR(queryEnv, enr);
+
+			nsitem = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+			rte = nsitem->p_rte;
+			/* if base table has RLS, set security condition to enr*/
+			rte->securityQuals = get_securityQuals(table->table_id, list_length(query->rtable) + 1, query);
+
+			query->rtable = lappend(query->rtable, rte);
+			table->new_rtes = lappend(table->new_rtes, rte);
+
+			count++;
+		}
+	}
+}
+
+/*
+ * get_prestate_rte
+ *
+ * Rewrite RTE of the modified table to a subquery which represents
+ * "pre-state" table. The original RTE is saved in table->rte_original.
+ */
+static RangeTblEntry*
+get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
+				 TransactionId xid, CommandId cid,
+				 QueryEnvironment *queryEnv)
+{
+	StringInfoData str;
+	RawStmt *raw;
+	Query *sub;
+	Relation rel;
+	ParseState *pstate;
+	char *relname;
+	int i;
+
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	/*
+	 * We can use NoLock here since AcquireRewriteLocks should
+	 * have locked the rel already.
+	 */
+	rel = table_open(table->table_id, NoLock);
+	relname = quote_qualified_identifier(
+					get_namespace_name(RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+	table_close(rel, NoLock);
+
+	initStringInfo(&str);
+	appendStringInfo(&str,
+		"SELECT t.* FROM %s t"
+		" WHERE (age(t.xmin) - age(%u::text::xid) > 0) OR"
+		" (t.xmin = %u AND t.cmin::text::int < %u)",
+			relname, xid, xid, cid);
+
+	for (i = 0; i < list_length(table->old_tuplestores); i++)
+	{
+		appendStringInfo(&str, " UNION ALL ");
+		appendStringInfo(&str," SELECT * FROM %s",
+			make_delta_enr_name("old", table->table_id, i));
+	}
+
+	raw = (RawStmt*)linitial(raw_parser(str.data, RAW_PARSE_DEFAULT));
+	sub = transformStmt(pstate, raw->stmt);
+
+	/* If this query has setOperations, RTEs in rtables has a subquery which contains ENR */
+	if (sub->setOperations != NULL)
+	{
+		ListCell *lc;
+
+		/* add securityQuals for tuplestores */
+		foreach (lc, sub->rtable)
+		{
+			RangeTblEntry *rte;
+			RangeTblEntry *sub_rte;
+
+			rte = (RangeTblEntry *)lfirst(lc);
+			Assert(rte->subquery != NULL);
+
+			sub_rte = (RangeTblEntry *)linitial(rte->subquery->rtable);
+			if (sub_rte->rtekind == RTE_NAMEDTUPLESTORE)
+				/* rt_index is always 1, bacause subquery has enr_rte only */
+				sub_rte->securityQuals = get_securityQuals(sub_rte->relid, 1, sub);
+		}
+	}
+
+	/* save the original RTE */
+	table->original_rte = copyObject(rte);
+
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = sub;
+	rte->security_barrier = false;
+	/* Clear fields that should not be set in a subquery RTE */
+	rte->relid = InvalidOid;
+	rte->relkind = 0;
+	rte->rellockmode = 0;
+	rte->tablesample = NULL;
+	rte->inh = false;			/* must not be set for a subquery */
+
+	rte->requiredPerms = 0;		/* no permission check on subquery itself */
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->insertedCols = NULL;
+	rte->updatedCols = NULL;
+	rte->extraUpdatedCols = NULL;
+
+	return rte;
+}
+
+/*
+ * make_delta_enr_name
+ *
+ * Make a name for ENR of a transition table from the base table's oid.
+ * prefix will be "new" or "old" depending on its transition table kind..
+ */
+static char*
+make_delta_enr_name(const char *prefix, Oid relid, int count)
+{
+	char buf[NAMEDATALEN];
+	char *name;
+
+	snprintf(buf, NAMEDATALEN, "__ivm_%s_%u_%u", prefix, relid, count);
+	name = pstrdup(buf);
+
+	return name;
+}
+
+/*
+ * union_ENRs
+ *
+ * Make a single table delta by unionning all transition tables of the modified table
+ * whose RTE is specified by
+ */
+static RangeTblEntry*
+union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
+		   QueryEnvironment *queryEnv)
+{
+	StringInfoData str;
+	ParseState	*pstate;
+	RawStmt *raw;
+	Query *sub;
+	int	i;
+	RangeTblEntry *enr_rte;
+
+	/* Create a ParseState for rewriting the view definition query */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	initStringInfo(&str);
+
+	for (i = 0; i < list_length(enr_rtes); i++)
+	{
+		if (i > 0)
+			appendStringInfo(&str, " UNION ALL ");
+
+		appendStringInfo(&str,
+			" SELECT * FROM %s",
+			make_delta_enr_name(prefix, relid, i));
+	}
+
+	raw = (RawStmt*)linitial(raw_parser(str.data, RAW_PARSE_DEFAULT));
+	sub = transformStmt(pstate, raw->stmt);
+
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = sub;
+	rte->security_barrier = false;
+	/* Clear fields that should not be set in a subquery RTE */
+	rte->relid = InvalidOid;
+	rte->relkind = 0;
+	rte->rellockmode = 0;
+	rte->tablesample = NULL;
+	rte->inh = false;			/* must not be set for a subquery */
+
+	rte->requiredPerms = 0;		/* no permission check on subquery itself */
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->insertedCols = NULL;
+	rte->updatedCols = NULL;
+	rte->extraUpdatedCols = NULL;
+	/* if base table has RLS, set security condition to enr*/
+	enr_rte = (RangeTblEntry *)linitial(sub->rtable);
+	/* rt_index is always 1, bacause subquery has enr_rte only */
+	enr_rte->securityQuals = get_securityQuals(relid, 1, sub);
+
+	return rte;
+}
+
+/*
+ * rewrite_query_for_distinct
+ *
+ * Rewrite query for counting DISTINCT clause.
+ */
+static Query *
+rewrite_query_for_distinct(Query *query, ParseState *pstate)
+{
+	TargetEntry *tle_count;
+	FuncCall *fn;
+	Node *node;
+
+	/* Add count(*) for counting distinct tuples in views */
+	fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+	fn->agg_star = true;
+	if (!query->groupClause && !query->hasAggs)
+		query->groupClause = transformDistinctClause(NULL, &query->targetList, query->sortClause, false);
+
+	node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+	tle_count = makeTargetEntry((Expr *) node,
+								list_length(query->targetList) + 1,
+								pstrdup("__ivm_count__"),
+								false);
+	query->targetList = lappend(query->targetList, tle_count);
+	query->hasAggs = true;
+
+	return query;
+}
+
+/*
+ * calc_delta
+ *
+ * Calculate view deltas generated under the modification of a table specified
+ * by the RTE index.
+ */
+static void
+calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
+			DestReceiver *dest_old, DestReceiver *dest_new,
+			TupleDesc *tupdesc_old, TupleDesc *tupdesc_new,
+			QueryEnvironment *queryEnv)
+{
+	ListCell *lc = list_nth_cell(query->rtable, rte_index - 1);
+	RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+	/* Generate old delta */
+	if (list_length(table->old_rtes) > 0)
+	{
+		/* Replace the modified table with the old delta table and calculate the old view delta. */
+		lfirst(lc) = union_ENRs(rte, table->table_id, table->old_rtes, "old", queryEnv);
+		refresh_matview_datafill(dest_old, query, queryEnv, tupdesc_old, "");
+	}
+
+	/* Generate new delta */
+	if (list_length(table->new_rtes) > 0)
+	{
+		/* Replace the modified table with the new delta table and calculate the new view delta*/
+		lfirst(lc) = union_ENRs(rte, table->table_id, table->new_rtes, "new", queryEnv);
+		refresh_matview_datafill(dest_new, query, queryEnv, tupdesc_new, "");
+	}
+}
+
+/*
+ * rewrite_query_for_postupdate_state
+ *
+ * Rewrite the query so that the specified base table's RTEs will represent
+ * "post-update" state of tables. This is called after the view delta
+ * calculation due to changes on this table finishes.
+ */
+static Query*
+rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte_index)
+{
+	ListCell *lc = list_nth_cell(query->rtable, rte_index - 1);
+
+	/* Retore the original RTE */
+	lfirst(lc) = table->original_rte;
+
+	return query;
+}
+
+/*
+ * apply_delta
+ *
+ * Apply deltas to the materialized view. In outer join cases, this requires
+ * the view maintenance graph.
+ */
+static void
+apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
+			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
+			Query *query, bool use_count, char *count_colname)
+{
+	StringInfoData querybuf;
+	StringInfoData target_list_buf;
+	Relation	matviewRel;
+	char	   *matviewname;
+	ListCell	*lc;
+	int			i;
+	List	   *keys = NIL;
+
+
+	/*
+	 * get names of the materialized view and delta tables
+	 */
+
+	matviewRel = table_open(matviewOid, NoLock);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	/*
+	 * Build parts of the maintenance queries
+	 */
+
+	initStringInfo(&querybuf);
+	initStringInfo(&target_list_buf);
+
+	/* build string of target list */
+	for (i = 0; i < matviewRel->rd_att->natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char   *resname = NameStr(attr->attname);
+
+		if (i != 0)
+			appendStringInfo(&target_list_buf, ", ");
+		appendStringInfo(&target_list_buf, "%s", quote_qualified_identifier(NULL, resname));
+	}
+
+	i = 0;
+	foreach (lc, query->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char *resname = NameStr(attr->attname);
+
+		i++;
+
+		if (tle->resjunk)
+			continue;
+
+		keys = lappend(keys, resname);
+	}
+
+	/* Start maintaining the materialized view. */
+	OpenMatViewIncrementalMaintenance();
+
+	/* Open SPI context. */
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	/* For tuple deletion */
+	if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
+	{
+		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		int				rc;
+
+		/* convert tuplestores to ENR, and register for SPI */
+		enr->md.name = pstrdup(OLD_DELTA_ENRNAME);
+		enr->md.reliddesc = InvalidOid;
+		enr->md.tupdesc = tupdesc_old;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(old_tuplestores);
+		enr->reldata = old_tuplestores;
+
+		rc = SPI_register_relation(enr);
+		if (rc != SPI_OK_REL_REGISTER)
+			elog(ERROR, "SPI_register failed");
+
+		if (use_count)
+			/* apply old delta and get rows to be recalculated */
+			apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME,
+									   keys, count_colname);
+		else
+			apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys);
+
+	}
+	/* For tuple insertion */
+	if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
+	{
+		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		int rc;
+
+		/* convert tuplestores to ENR, and register for SPI */
+		enr->md.name = pstrdup(NEW_DELTA_ENRNAME);
+		enr->md.reliddesc = InvalidOid;
+		enr->md.tupdesc = tupdesc_new;;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(new_tuplestores);
+		enr->reldata = new_tuplestores;
+
+		rc = SPI_register_relation(enr);
+		if (rc != SPI_OK_REL_REGISTER)
+			elog(ERROR, "SPI_register failed");
+
+		/* apply new delta */
+		if (use_count)
+			apply_new_delta_with_count(matviewname, NEW_DELTA_ENRNAME,
+								keys, &target_list_buf, count_colname);
+		else
+			apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf);
+	}
+
+	/* We're done maintaining the materialized view. */
+	CloseMatViewIncrementalMaintenance();
+
+	table_close(matviewRel, NoLock);
+
+	/* Close SPI context. */
+	if (SPI_finish() != SPI_OK_FINISH)
+		elog(ERROR, "SPI_finish failed");
+}
+
+/*
+ * apply_old_delta_with_count
+ *
+ * Execute a query for applying a delta table given by deltname_old
+ * which contains tuples to be deleted from to a materialized view given by
+ * matviewname.  This is used when counting is required, that is, the view
+ * has aggregate or distinct.
+ */
+static void
+apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
+				List *keys, const char *count_colname)
+{
+	StringInfoData	querybuf;
+	char   *match_cond;
+
+	/* build WHERE condition for searching tuples to be deleted */
+	match_cond = get_matching_condition_string(keys);
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"WITH t AS ("			/* collecting tid of target tuples in the view */
+						"SELECT diff.%s, "			/* count column */
+								"(diff.%s OPERATOR(pg_catalog.=) mv.%s) AS for_dlt, "
+								"mv.ctid "
+						"FROM %s AS mv, %s AS diff "
+						"WHERE %s"					/* tuple matching condition */
+					"), updt AS ("			/* update a tuple if this is not to be deleted */
+						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.-) t.%s "
+						"FROM t WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND NOT for_dlt "
+					"), dlt AS ("			/* delete a tuple if this is to be deleted */
+						"DELETE FROM %s AS mv USING t "
+						"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt"
+					")",
+					count_colname,
+					count_colname, count_colname,
+					matviewname, deltaname_old,
+					match_cond,
+					matviewname, count_colname, count_colname, count_colname,
+					matviewname);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_old_delta
+ *
+ * Execute a query for applying a delta table given by deltname_old
+ * which contains tuples to be deleted from to a materialized view given by
+ * matviewname.  This is used when counting is not required.
+ */
+static void
+apply_old_delta(const char *matviewname, const char *deltaname_old,
+				List *keys)
+{
+	StringInfoData	querybuf;
+	StringInfoData	keysbuf;
+	char   *match_cond;
+	ListCell *lc;
+
+	/* build WHERE condition for searching tuples to be deleted */
+	match_cond = get_matching_condition_string(keys);
+
+	/* build string of keys list */
+	initStringInfo(&keysbuf);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char   *resname = NameStr(attr->attname);
+		appendStringInfo(&keysbuf, "%s", quote_qualified_identifier("mv", resname));
+		if (lnext(keys, lc))
+			appendStringInfo(&keysbuf, ", ");
+	}
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+	"DELETE FROM %s WHERE ctid IN ("
+		"SELECT tid FROM (SELECT row_number() over (partition by %s) AS \"__ivm_row_number__\","
+								  "mv.ctid AS tid,"
+								  "diff.\"__ivm_count__\""
+						 "FROM %s AS mv, %s AS diff "
+						 "WHERE %s) v "
+					"WHERE v.\"__ivm_row_number__\" OPERATOR(pg_catalog.<=) v.\"__ivm_count__\")",
+					matviewname,
+					keysbuf.data,
+					matviewname, deltaname_old,
+					match_cond);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_new_delta_with_count
+ *
+ * Execute a query for applying a delta table given by deltname_new
+ * which contains tuples to be inserted into a materialized view given by
+ * matviewname.  This is used when counting is required, that is, the view
+ * has aggregate or distinct. Also, when a table in EXISTS sub queries
+ * is modified.
+ */
+static void
+apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
+				List *keys, StringInfo target_list, const char* count_colname)
+{
+	StringInfoData	querybuf;
+	StringInfoData	returning_keys;
+	ListCell	*lc;
+	char	*match_cond = "";
+
+	/* build WHERE condition for searching tuples to be updated */
+	match_cond = get_matching_condition_string(keys);
+
+	/* build string of keys list */
+	initStringInfo(&returning_keys);
+	if (keys)
+	{
+		foreach (lc, keys)
+		{
+			Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+			char   *resname = NameStr(attr->attname);
+			appendStringInfo(&returning_keys, "%s", quote_qualified_identifier("mv", resname));
+			if (lnext(keys, lc))
+				appendStringInfo(&returning_keys, ", ");
+		}
+	}
+	else
+		appendStringInfo(&returning_keys, "NULL");
+
+	/* Search for matching tuples from the view and update if found or insert if not. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"WITH updt AS ("		/* update a tuple if this exists in the view */
+						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.+) diff.%s "
+						"FROM %s AS diff "
+						"WHERE %s "					/* tuple matching condition */
+						"RETURNING %s"				/* returning keys of updated tuples */
+					") INSERT INTO %s (%s)"	/* insert a new tuple if this doesn't existw */
+						"SELECT %s FROM %s AS diff "
+						"WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE %s);",
+					matviewname, count_colname, count_colname, count_colname,
+					deltaname_new,
+					match_cond,
+					returning_keys.data,
+					matviewname, target_list->data,
+					target_list->data, deltaname_new,
+					match_cond);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_new_delta
+ *
+ * Execute a query for applying a delta table given by deltname_new
+ * which contains tuples to be inserted into a materialized view given by
+ * matviewname.  This is used when counting is not required.
+ */
+static void
+apply_new_delta(const char *matviewname, const char *deltaname_new,
+				StringInfo target_list)
+{
+	StringInfoData	querybuf;
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"INSERT INTO %s (%s) SELECT %s FROM ("
+						"SELECT diff.*, generate_series(1, diff.\"__ivm_count__\") "
+						"FROM %s AS diff) AS v",
+					matviewname, target_list->data, target_list->data,
+					deltaname_new);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * get_matching_condition_string
+ *
+ * Build a predicate string for looking for a tuple with given keys.
+ */
+static char *
+get_matching_condition_string(List *keys)
+{
+	StringInfoData match_cond;
+	ListCell	*lc;
+
+	/* If there is no key columns, the condition is always true. */
+	if (keys == NIL)
+		return "true";
+
+	initStringInfo(&match_cond);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char   *resname = NameStr(attr->attname);
+		char   *mv_resname = quote_qualified_identifier("mv", resname);
+		char   *diff_resname = quote_qualified_identifier("diff", resname);
+		Oid		typid = attr->atttypid;
+
+		/* Considering NULL values, we can not use simple = operator. */
+		appendStringInfo(&match_cond, "(");
+		generate_equal(&match_cond, typid, mv_resname, diff_resname);
+		appendStringInfo(&match_cond, " OR (%s IS NULL AND %s IS NULL))",
+						 mv_resname, diff_resname);
+
+		if (lnext(keys, lc))
+			appendStringInfo(&match_cond, " AND ");
+	}
+
+	return match_cond.data;
+}
+
+/*
+ * generate_equals
+ *
+ * Generate an equality clause using given operands' default equality
+ * operator.
+ */
+static void
+generate_equal(StringInfo querybuf, Oid opttype,
+			   const char *leftop, const char *rightop)
+{
+	TypeCacheEntry *typentry;
+
+	typentry = lookup_type_cache(opttype, TYPECACHE_EQ_OPR);
+	if (!OidIsValid(typentry->eq_opr))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_FUNCTION),
+				 errmsg("could not identify an equality operator for type %s",
+						format_type_be(opttype))));
+
+	generate_operator_clause(querybuf,
+							 leftop, opttype,
+							 typentry->eq_opr,
+							 rightop, opttype);
+}
+
+/*
+ * mv_InitHashTables
+ */
+static void
+mv_InitHashTables(void)
+{
+	HASHCTL		ctl;
+
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(MV_TriggerHashEntry);
+	mv_trigger_info = hash_create("MV trigger info",
+								 MV_INIT_QUERYHASHSIZE,
+								 &ctl, HASH_ELEM | HASH_BLOBS);
+}
+
+/*
+ * AtAbort_IVM
+ *
+ * Clean up hash entries for all materialized views. This is called at
+ * transaction abort.
+ */
+void
+AtAbort_IVM()
+{
+	HASH_SEQ_STATUS seq;
+	MV_TriggerHashEntry *entry;
+
+	if (mv_trigger_info)
+	{
+		hash_seq_init(&seq, mv_trigger_info);
+		while ((entry = hash_seq_search(&seq)) != NULL)
+			clean_up_IVM_hash_entry(entry);
+	}
+}
+
+/*
+ * clean_up_IVM_hash_entry
+ *
+ * Clean up tuple stores and hash entries for a materialized view after its
+ * maintenance finished.
+ */
+static void
+clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry)
+{
+	bool found;
+	ListCell *lc;
+
+	foreach(lc, entry->tables)
+	{
+		MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc);
+
+		list_free(table->old_tuplestores);
+		list_free(table->new_tuplestores);
+	}
+	list_free(entry->tables);
+
+	hash_search(mv_trigger_info, (void *) &entry->matview_id, HASH_REMOVE, &found);
+}
+
+/*
+ * isIvmName
+ *
+ * Check if this is a IVM hidden column from the name.
+ */
+bool
+isIvmName(const char *s)
+{
+	if (s)
+		return (strncmp(s, "__ivm_", 6) == 0);
+	return false;
+}
+
+/*
+ * get_securityQuals
+ *
+ * Get row security policy on a relation.
+ * This is used by IVM for copying RLS from base table to enr.
+ */
+static List *
+get_securityQuals(Oid relId, int rt_index, Query *query)
+{
+	ParseState *pstate;
+	Relation rel;
+	ParseNamespaceItem *nsitem;
+	RangeTblEntry *rte;
+	List *securityQuals;
+	List *withCheckOptions;
+	bool  hasRowSecurity;
+	bool  hasSubLinks;
+
+	securityQuals = NIL;
+	pstate = make_parsestate(NULL);
+
+	rel = table_open(relId, NoLock);
+	nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, false);
+	rte = nsitem->p_rte;
+
+	get_row_security_policies(query, rte, rt_index,
+							  &securityQuals, &withCheckOptions,
+							  &hasRowSecurity, &hasSubLinks);
+
+	/*
+	 * Make sure the query is marked correctly if row level security
+	 * applies, or if the new quals had sublinks.
+	 */
+	if (hasRowSecurity)
+		query->hasRowSecurity = true;
+	if (hasSubLinks)
+		query->hasSubLinks = true;
+
+	table_close(rel, NoLock);
+
+	return securityQuals;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3e83f375b5..c64539b20d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -51,6 +51,7 @@
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
+#include "commands/matview.h"
 #include "commands/event_trigger.h"
 #include "commands/policy.h"
 #include "commands/sequence.h"
@@ -3415,6 +3416,14 @@ renameatt_internal(Oid myrelid,
 	targetrelation = relation_open(myrelid, AccessExclusiveLock);
 	renameatt_check(myrelid, RelationGetForm(targetrelation), recursing);
 
+	/*
+	 * Don't rename IVM columns.
+	 */
+	if (RelationIsIVM(targetrelation) && isIvmName(oldattname))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("IVM column can not be renamed")));
+
 	/*
 	 * if the 'recurse' flag is set then we are supposed to rename this
 	 * attribute in all classes that inherit from 'relname' (as well as in
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 80268ac059..158652734f 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2464,6 +2464,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(relisivm);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 572560b4a2..5a320c28a0 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2765,6 +2765,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(relisivm);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8ec4059cc0..300d488824 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3258,6 +3258,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index cc6dcb7220..37dff1ec27 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1441,6 +1441,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index cb9e177b5e..dcfd1f3fc0 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -36,6 +36,7 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 #include "utils/varlena.h"
+#include "commands/matview.h"
 
 
 /*
@@ -79,7 +80,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							int count, int offset,
 							int rtindex, int sublevels_up,
 							int location, bool include_dropped,
-							List **colnames, List **colvars);
+							List **colnames, List **colvars, bool is_ivm);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
 
@@ -1433,6 +1434,7 @@ addRangeTableEntry(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -1521,6 +1523,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -2676,7 +2679,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 						expandTupleDesc(tupdesc, rte->eref,
 										rtfunc->funccolcount, atts_done,
 										rtindex, sublevels_up, location,
-										include_dropped, colnames, colvars);
+										include_dropped, colnames, colvars, false);
 					}
 					else if (functypclass == TYPEFUNC_SCALAR)
 					{
@@ -2944,7 +2947,7 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
 	expandTupleDesc(rel->rd_att, eref, rel->rd_att->natts, 0,
 					rtindex, sublevels_up,
 					location, include_dropped,
-					colnames, colvars);
+					colnames, colvars, RelationIsIVM(rel));
 	relation_close(rel, AccessShareLock);
 }
 
@@ -2961,7 +2964,7 @@ static void
 expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 				int rtindex, int sublevels_up,
 				int location, bool include_dropped,
-				List **colnames, List **colvars)
+				List **colnames, List **colvars, bool is_ivm)
 {
 	ListCell   *aliascell;
 	int			varattno;
@@ -2974,6 +2977,9 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 	{
 		Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
 
+		if (is_ivm && isIvmName(NameStr(attr->attname)) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		if (attr->attisdropped)
 		{
 			if (include_dropped)
@@ -3127,6 +3133,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 		Var		   *varnode = (Var *) lfirst(var);
 		TargetEntry *te;
 
+		/* if transform * into columnlist with IMMV, remove IVM columns */
+		if (rte->relisivm && isIvmName(label) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		te = makeTargetEntry((Expr *) varnode,
 							 (AttrNumber) pstate->p_next_resno++,
 							 label,
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 185bf5fbff..d8a8b66196 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -776,7 +776,8 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 														attr->atttypmod))));
 	}
 
-	if (i != resultDesc->natts)
+	/* No check for materialized views since this could have special columns for IVM */
+	if ((!isSelect || requireColumnNameMatch) && i != resultDesc->natts)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 isSelect ?
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7024dbe10a..a37602d45f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11736,4 +11736,12 @@
   prorettype => 'bytea', proargtypes => 'pg_brin_minmax_multi_summary',
   prosrc => 'brin_minmax_multi_summary_send' },
 
+# IVM
+{ oid => '786', descr => 'ivm trigger (before)',
+  proname => 'IVM_immediate_before', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_before' },
+{ oid => '787', descr => 'ivm trigger (after)',
+  proname => 'IVM_immediate_maintenance', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_maintenance' },
+
 ]
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index 54a38491fb..bcea9782d3 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -16,6 +16,7 @@
 
 #include "catalog/objectaddress.h"
 #include "nodes/params.h"
+#include "nodes/pathnodes.h"
 #include "parser/parse_node.h"
 #include "tcop/dest.h"
 #include "utils/queryenvironment.h"
@@ -25,6 +26,11 @@ extern ObjectAddress ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *st
 									   ParamListInfo params, QueryEnvironment *queryEnv,
 									   QueryCompletion *qc);
 
+extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create);
+extern void CreateIndexOnIMMV(Query *query, Relation matviewRel);
+
+extern Query *rewriteQueryForIMMV(Query *query, List *colNames);
+
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
 
 extern DestReceiver *CreateIntoRelDestReceiver(IntoClause *intoClause);
diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h
index a067da39d2..ec479db513 100644
--- a/src/include/commands/matview.h
+++ b/src/include/commands/matview.h
@@ -15,6 +15,7 @@
 #define MATVIEW_H
 
 #include "catalog/objectaddress.h"
+#include "fmgr.h"
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
 #include "tcop/dest.h"
@@ -23,6 +24,8 @@
 
 extern void SetMatViewPopulatedState(Relation relation, bool newstate);
 
+extern void SetMatViewIVMState(Relation relation, bool newstate);
+
 extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										ParamListInfo params, QueryCompletion *qc);
 
@@ -30,4 +33,9 @@ extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
 
 extern bool MatViewIncrementalMaintenanceIsEnabled(void);
 
+extern Datum IVM_immediate_before(PG_FUNCTION_ARGS);
+extern Datum IVM_immediate_maintenance(PG_FUNCTION_ARGS);
+extern void AtAbort_IVM(void);
+extern bool isIvmName(const char *s);
+
 #endif							/* MATVIEW_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3e9bdc781f..b0e8a396cc 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1036,6 +1036,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		relisivm;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -2195,6 +2196,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		ivm;			/* incremental view maintenance is used by materialized view */
 } CreateStmt;
 
 /* ----------
-- 
2.17.1

v25-0006-Add-Incremental-View-Maintenance-support-to-psql.patchtext/x-diff; name=v25-0006-Add-Incremental-View-Maintenance-support-to-psql.patchDownload
From 43e922b758373197ab302a40a110f55cd9140bd1 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:21:54 +0900
Subject: [PATCH v25 06/15] Add Incremental View Maintenance support to psql

Add tab completion and meta-command output for IVM.
---
 src/bin/psql/describe.c     | 32 +++++++++++++++++++++++++++++++-
 src/bin/psql/tab-complete.c | 14 +++++++++-----
 2 files changed, 40 insertions(+), 6 deletions(-)

diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 346cd92793..601eca408b 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1480,6 +1480,7 @@ describeOneTableDetails(const char *schemaname,
 		char		relpersistence;
 		char		relreplident;
 		char	   *relam;
+		bool		isivm;
 	}			tableinfo;
 	bool		show_column_details = false;
 
@@ -1492,7 +1493,26 @@ describeOneTableDetails(const char *schemaname,
 	initPQExpBuffer(&tmpbuf);
 
 	/* Get general table info */
-	if (pset.sversion >= 120000)
+	if (pset.sversion >= 150000)
+	{
+		printfPQExpBuffer(&buf,
+						  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+						  "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, "
+						  "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
+						  "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
+						  "c.relpersistence, c.relreplident, am.amname, "
+						  "c.relisivm\n"
+						  "FROM pg_catalog.pg_class c\n "
+						  "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+						  "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
+						  "WHERE c.oid = '%s';",
+						  (verbose ?
+						   "pg_catalog.array_to_string(c.reloptions || "
+						   "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+						   : "''"),
+						  oid);
+	}
+	else if (pset.sversion >= 120000)
 	{
 		printfPQExpBuffer(&buf,
 						  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
@@ -1612,6 +1632,10 @@ describeOneTableDetails(const char *schemaname,
 			(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
 	else
 		tableinfo.relam = NULL;
+	if (pset.sversion >= 150000)
+		tableinfo.isivm = strcmp(PQgetvalue(res, 0, 15), "t") == 0;
+	else
+		tableinfo.isivm = false;
 	PQclear(res);
 	res = NULL;
 
@@ -3375,6 +3399,12 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
 			printTableAddFooter(&cont, buf.data);
 		}
+
+		/* Incremental view maintance info */
+		if (verbose && tableinfo.relkind == RELKIND_MATVIEW && tableinfo.isivm)
+		{
+			printTableAddFooter(&cont, _("Incremental view maintenance: yes"));
+		}
 	}
 
 	/* reloptions, if verbose */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d1e421bc0f..5832e37aa0 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1172,6 +1172,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"INCREMENTAL MATERIALIZED VIEW", NULL, NULL, &Query_for_list_of_matviews, THING_NO_DROP | THING_NO_ALTER},
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2986,7 +2987,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
-		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
+		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW", "INCREMENTAL MATERIALIZED VIEW");
 	/* Complete PARTITION BY with RANGE ( or LIST ( or ... */
 	else if (TailMatches("PARTITION", "BY"))
 		COMPLETE_WITH("RANGE (", "LIST (", "HASH (");
@@ -3302,13 +3303,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches("CREATE", "MATERIALIZED"))
+	else if (Matches("CREATE", "MATERIALIZED") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED"))
 		COMPLETE_WITH("VIEW");
-	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	/* Complete CREATE MATERIALIZED VIEW <name> with AS  */
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny) ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH("SELECT");
 
 /* CREATE EVENT TRIGGER */
-- 
2.17.1

v25-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patchtext/x-diff; name=v25-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patchDownload
From 23a47ebb05537dbaaf4ec0fffcafe202040b8e62 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Wed, 11 Nov 2020 17:01:25 +0900
Subject: [PATCH v25 05/15] Add Incremental View Maintenance support to pg_dump

Support CREATE INCREMENTAL MATERIALIZED VIEW syntax.
---
 src/bin/pg_dump/pg_dump.c        | 18 +++++++++++++++---
 src/bin/pg_dump/pg_dump.h        |  1 +
 src/bin/pg_dump/t/002_pg_dump.pl | 15 +++++++++++++++
 3 files changed, 31 insertions(+), 3 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e3ddf19959..99cdca23a6 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5979,6 +5979,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_relacl;
 	int			i_acldefault;
 	int			i_ispartition;
+	int			i_isivm;
 
 	/*
 	 * Find all the tables and table-like objects.
@@ -6081,10 +6082,17 @@ getTables(Archive *fout, int *numTables)
 
 	if (fout->remoteVersion >= 100000)
 		appendPQExpBufferStr(query,
-							 "c.relispartition AS ispartition ");
+							 "c.relispartition AS ispartition, ");
 	else
 		appendPQExpBufferStr(query,
-							 "false AS ispartition ");
+							 "false AS ispartition, ");
+
+	if (fout->remoteVersion >= 150000)
+		appendPQExpBufferStr(query,
+							 "c.relisivm AS isivm ");
+	else
+		appendPQExpBufferStr(query,
+							 "false AS isivm ");
 
 	/*
 	 * Left join to pg_depend to pick up dependency info linking sequences to
@@ -6193,6 +6201,7 @@ getTables(Archive *fout, int *numTables)
 	i_relacl = PQfnumber(res, "relacl");
 	i_acldefault = PQfnumber(res, "acldefault");
 	i_ispartition = PQfnumber(res, "ispartition");
+	i_isivm = PQfnumber(res, "isivm");
 
 	if (dopt->lockWaitTimeout)
 	{
@@ -6270,6 +6279,7 @@ getTables(Archive *fout, int *numTables)
 			tblinfo[i].amname = pg_strdup(PQgetvalue(res, i, i_amname));
 		tblinfo[i].is_identity_sequence = (strcmp(PQgetvalue(res, i, i_is_identity_sequence), "t") == 0);
 		tblinfo[i].ispartition = (strcmp(PQgetvalue(res, i, i_ispartition), "t") == 0);
+		tblinfo[i].isivm = (strcmp(PQgetvalue(res, i, i_isivm), "t") == 0);
 
 		/* other fields were zeroed above */
 
@@ -14997,9 +15007,11 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
-		appendPQExpBuffer(q, "CREATE %s%s %s",
+		appendPQExpBuffer(q, "CREATE %s%s%s %s",
 						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
 						  "UNLOGGED " : "",
+						  tbinfo->relkind == RELKIND_MATVIEW && tbinfo->isivm ?
+						  "INCREMENTAL " : "",
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 066a129ee5..7e80748d53 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -318,6 +318,7 @@ typedef struct _tableInfo
 	bool		dummy_view;		/* view's real definition must be postponed */
 	bool		postponed_def;	/* matview must be postponed into post-data */
 	bool		ispartition;	/* is table a partition? */
+	bool		isivm;			/* is incrementally maintainable materialized view? */
 
 	/*
 	 * These fields are computed only if we decide the table is interesting
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 39fa1952e7..5fc70c30c1 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2232,6 +2232,21 @@ my %tests = (
 		  { exclude_dump_test_schema => 1, no_toast_compression => 1, },
 	},
 
+	'CREATE MATERIALIZED VIEW matview_ivm' => {
+		create_order => 21,
+		create_sql   => 'CREATE INCREMENTAL MATERIALIZED VIEW dump_test.matview_ivm (col1) AS
+					   SELECT col1 FROM dump_test.test_table;',
+		regexp => qr/^
+			\QCREATE INCREMENTAL MATERIALIZED VIEW dump_test.matview_ivm AS\E
+			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QFROM dump_test.test_table\E
+			\n\s+\QWITH NO DATA;\E
+			/xm,
+		like =>
+		  { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, },
+	},
+
 	'CREATE POLICY p1 ON test_table' => {
 		create_order => 22,
 		create_sql   => 'CREATE POLICY p1 ON dump_test.test_table
-- 
2.17.1

v25-0004-Allow-to-prolong-life-span-of-transition-tables-.patchtext/x-diff; name=v25-0004-Allow-to-prolong-life-span-of-transition-tables-.patchDownload
From e2539b2da1f24c8b4fb40b7622e475ba5a23637e Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:09:45 +0900
Subject: [PATCH v25 04/15] Allow to prolong life span of transition tables
 until transaction end

Originally, tuplestores of AFTER trigger's transition tables were
freed for each query depth. For our IVM implementation, we would like
to prolong life of the tuplestores because we have to preserve them
for a whole query assuming that some base tables might be changed
in some trigger functions.
---
 src/backend/commands/trigger.c | 80 +++++++++++++++++++++++++++++++++-
 src/include/commands/trigger.h |  2 +
 2 files changed, 80 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1a9c1ac290..0fcb1aa0b5 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3651,6 +3651,10 @@ typedef struct AfterTriggerEventList
  * end of the list, so it is relatively easy to discard them.  The event
  * list chunks themselves are stored in event_cxt.
  *
+ * prolonged_tuplestored is a list of transition table tuplestores whose
+ * life are prolonged to the end of the outmost query instead of each nested
+ * query.
+ *
  * query_depth is the current depth of nested AfterTriggerBeginQuery calls
  * (-1 when the stack is empty).
  *
@@ -3716,6 +3720,7 @@ typedef struct AfterTriggersData
 	SetConstraintState state;	/* the active S C state */
 	AfterTriggerEventList events;	/* deferred-event list */
 	MemoryContext event_cxt;	/* memory context for events, if any */
+	List   *prolonged_tuplestores;	/* list of prolonged tuplestores */
 
 	/* per-query-level data: */
 	AfterTriggersQueryData *query_stack;	/* array of structs shown below */
@@ -3751,6 +3756,7 @@ struct AfterTriggersTableData
 	bool		closed;			/* true when no longer OK to add tuples */
 	bool		before_trig_done;	/* did we already queue BS triggers? */
 	bool		after_trig_done;	/* did we already queue AS triggers? */
+	bool		prolonged;			/* are transition tables prolonged? */
 	AfterTriggerEventList after_trig_events;	/* if so, saved list pointer */
 	Tuplestorestate *old_tuplestore;	/* "old" transition table, if any */
 	Tuplestorestate *new_tuplestore;	/* "new" transition table, if any */
@@ -4529,6 +4535,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
 }
 
 
+/*
+ * SetTransitionTablePreserved
+ *
+ * Prolong lifespan of transition tables corresponding specified relid and
+ * command type to the end of the outmost query instead of each nested query.
+ * This enables to use nested AFTER trigger's transition tables from outer
+ * query's triggers.  Currently, only immediate incremental view maintenance
+ * uses this.
+ */
+void
+SetTransitionTablePreserved(Oid relid, CmdType cmdType)
+{
+	AfterTriggersTableData *table;
+	AfterTriggersQueryData *qs;
+	bool		found = false;
+	ListCell   *lc;
+
+	/* Check state, like AfterTriggerSaveEvent. */
+	if (afterTriggers.query_depth < 0)
+		elog(ERROR, "SetTransitionTablePreserved() called outside of query");
+
+	qs = &afterTriggers.query_stack[afterTriggers.query_depth];
+
+	foreach(lc, qs->tables)
+	{
+		table = (AfterTriggersTableData *) lfirst(lc);
+		if (table->relid == relid && table->cmdType == cmdType &&
+			table->closed)
+		{
+			table->prolonged = true;
+			found = true;
+		}
+	}
+
+	if (!found)
+		elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType);
+}
+
+
 /*
  * GetAfterTriggersTableData
  *
@@ -4723,6 +4768,7 @@ AfterTriggerBeginXact(void)
 	 */
 	afterTriggers.firing_counter = (CommandId) 1;	/* mustn't be 0 */
 	afterTriggers.query_depth = -1;
+	afterTriggers.prolonged_tuplestores = NIL;
 
 	/*
 	 * Verify that there is no leftover state remaining.  If these assertions
@@ -4883,11 +4929,29 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
 		ts = table->old_tuplestore;
 		table->old_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+		{
+			if (table->prolonged && afterTriggers.query_depth > 0)
+			{
+				MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext);
+				afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts);
+				MemoryContextSwitchTo(oldcxt);
+			}
+			else
+				tuplestore_end(ts);
+		}
 		ts = table->new_tuplestore;
 		table->new_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+		{
+			if (table->prolonged && afterTriggers.query_depth > 0)
+			{
+				MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext);
+				afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts);
+				MemoryContextSwitchTo(oldcxt);
+			}
+			else
+				tuplestore_end(ts);
+		}
 		if (table->storeslot)
 			ExecDropSingleTupleTableSlot(table->storeslot);
 	}
@@ -4899,6 +4963,18 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
 	 */
 	qs->tables = NIL;
 	list_free_deep(tables);
+
+	/* Release prolonged tuplestores at the end of the outmost query */
+	if (afterTriggers.query_depth == 0)
+	{
+		foreach(lc, afterTriggers.prolonged_tuplestores)
+		{
+			ts = (Tuplestorestate *) lfirst(lc);
+			if (ts)
+				tuplestore_end(ts);
+		}
+		afterTriggers.prolonged_tuplestores = NIL;
+	}
 }
 
 
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index e1271420e5..697a6f6d55 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -255,6 +255,8 @@ extern void AfterTriggerEndSubXact(bool isCommit);
 extern void AfterTriggerSetState(ConstraintsSetStmt *stmt);
 extern bool AfterTriggerPendingOnRel(Oid relid);
 
+extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType);
+
 
 /*
  * in utils/adt/ri_triggers.c
-- 
2.17.1

v25-0003-Add-new-deptype-option-m-in-pg_depend-system-cat.patchtext/x-diff; name=v25-0003-Add-new-deptype-option-m-in-pg_depend-system-cat.patchDownload
From 6ef5ab2d30b13e9ac93486738a57ce02bbeda024 Mon Sep 17 00:00:00 2001
From: Takuma Hoshiai <takuma.hoshiai@gmail.com>
Date: Fri, 17 Jan 2020 16:04:14 +0900
Subject: [PATCH v25 03/15] Add new deptype option 'm' in pg_depend system
 catalog

The deptype option 'm' mean specific database obejects referenced Incrementally
Maintainable Materialized View(IMMV). If set NO DATA flag to IMVM, these
database objects must be dropped.
---
 src/backend/catalog/dependency.c | 2 ++
 src/include/catalog/dependency.h | 3 ++-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index ab9e42d7d1..c7d074d8e6 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -598,6 +598,7 @@ findDependentObjects(const ObjectAddress *object,
 			case DEPENDENCY_NORMAL:
 			case DEPENDENCY_AUTO:
 			case DEPENDENCY_AUTO_EXTENSION:
+			case DEPENDENCY_IMMV:
 				/* no problem */
 				break;
 
@@ -915,6 +916,7 @@ findDependentObjects(const ObjectAddress *object,
 				subflags = DEPFLAG_AUTO;
 				break;
 			case DEPENDENCY_INTERNAL:
+			case DEPENDENCY_IMMV:
 				subflags = DEPFLAG_INTERNAL;
 				break;
 			case DEPENDENCY_PARTITION_PRI:
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 344482ec87..b4578610ea 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -36,7 +36,8 @@ typedef enum DependencyType
 	DEPENDENCY_PARTITION_PRI = 'P',
 	DEPENDENCY_PARTITION_SEC = 'S',
 	DEPENDENCY_EXTENSION = 'e',
-	DEPENDENCY_AUTO_EXTENSION = 'x'
+	DEPENDENCY_AUTO_EXTENSION = 'x',
+	DEPENDENCY_IMMV = 'm'
 } DependencyType;
 
 /*
-- 
2.17.1

v25-0002-Add-relisivm-column-to-pg_class-system-catalog.patchtext/x-diff; name=v25-0002-Add-relisivm-column-to-pg_class-system-catalog.patchDownload
From 1c9b1701f576cbd7ccc3a2e30a176786430ffd0a Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:07:23 +0900
Subject: [PATCH v25 02/15] Add relisivm column to pg_class system catalog

If this boolean column is true, a relations is Incrementally Maintainable
Materialized View (IMMV). This is set when IMMV is created.
---
 src/backend/catalog/heap.c          |  1 +
 src/backend/catalog/index.c         |  1 +
 src/backend/utils/cache/lsyscache.c | 24 ++++++++++++++++++++++++
 src/backend/utils/cache/relcache.c  |  2 ++
 src/include/catalog/pg_class.h      |  3 +++
 src/include/utils/lsyscache.h       |  1 +
 src/include/utils/rel.h             |  2 ++
 7 files changed, 34 insertions(+)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 7e99de88b3..212a6293aa 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -935,6 +935,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
+	values[Anum_pg_class_relisivm - 1] = BoolGetDatum(rd_rel->relisivm);
 	if (relacl != (Datum) 0)
 		values[Anum_pg_class_relacl - 1] = relacl;
 	else
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 2308d40256..244c9af7b8 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -981,6 +981,7 @@ index_create(Relation heapRelation,
 	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
 	indexRelation->rd_rel->relam = accessMethodObjectId;
 	indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid);
+	indexRelation->rd_rel->relisivm = false;
 
 	/*
 	 * store index's pg_class entry
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index feef999863..1db83878c6 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2013,6 +2013,30 @@ get_rel_relispartition(Oid relid)
 		return false;
 }
 
+/*
+ * get_rel_relisivm
+ *
+ *		Returns the relisivm flag associated with a given relation.
+ */
+bool
+get_rel_relisivm(Oid relid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp);
+		bool		result;
+
+		result = reltup->relisivm;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return false;
+}
+
 /*
  * get_rel_tablespace
  *
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2e760e8a3b..0dd901d6ed 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1902,6 +1902,8 @@ formrdesc(const char *relationName, Oid relationReltype,
 
 	/* ... and they're always populated, too */
 	relation->rd_rel->relispopulated = true;
+	/* ... and they're always no ivm, too */
+	relation->rd_rel->relisivm = false;
 
 	relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
 	relation->rd_rel->relpages = 0;
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 304e8c18d5..fbaa1438d6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -119,6 +119,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
 	/* is relation a partition? */
 	bool		relispartition BKI_DEFAULT(f);
 
+	/* is relation a matview with ivm? */
+	bool		relisivm BKI_DEFAULT(f);
+
 	/* link to original rel during table rewrite; otherwise 0 */
 	Oid			relrewrite BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_class);
 
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index b8dd27d4a9..cc2f635122 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -137,6 +137,7 @@ extern Oid	get_rel_namespace(Oid relid);
 extern Oid	get_rel_type_id(Oid relid);
 extern char get_rel_relkind(Oid relid);
 extern bool get_rel_relispartition(Oid relid);
+extern bool get_rel_relisivm(Oid relid);
 extern Oid	get_rel_tablespace(Oid relid);
 extern char get_rel_persistence(Oid relid);
 extern Oid	get_transform_fromsql(Oid typid, Oid langid, List *trftypes);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 6da1b220cd..6356c24f84 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -650,6 +650,8 @@ RelationGetSmgr(Relation rel)
  */
 #define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated)
 
+#define RelationIsIVM(relation) ((relation)->rd_rel->relisivm)
+
 /*
  * RelationIsAccessibleInLogicalDecoding
  *		True if we need to log enough information to have access via
-- 
2.17.1

v25-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patchtext/x-diff; name=v25-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patchDownload
From 26060bb29f44e0d73cf3651097d80de685a01213 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:05:02 +0900
Subject: [PATCH v25 01/15] Add a syntax to create Incrementally Maintainable
 Materialized Views

Allow to create Incrementally Maintainable Materialized View (IMMV)
by using INCREMENTAL option in CREATE MATERIALIZED VIEW command
as follow:

     CREATE [INCREMANTAL] MATERIALIZED VIEW xxxxx AS SELECT ....;
---
 src/backend/nodes/copyfuncs.c  |  1 +
 src/backend/nodes/equalfuncs.c |  1 +
 src/backend/nodes/outfuncs.c   |  1 +
 src/backend/nodes/readfuncs.c  |  1 +
 src/backend/parser/gram.y      | 32 +++++++++++++++++++++-----------
 src/include/nodes/primnodes.h  |  1 +
 src/include/parser/kwlist.h    |  1 +
 7 files changed, 27 insertions(+), 11 deletions(-)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 90b5da51c9..80268ac059 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1426,6 +1426,7 @@ _copyIntoClause(const IntoClause *from)
 	COPY_STRING_FIELD(tableSpaceName);
 	COPY_NODE_FIELD(viewQuery);
 	COPY_SCALAR_FIELD(skipData);
+	COPY_SCALAR_FIELD(ivm);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 06345da3ba..572560b4a2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -155,6 +155,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
 	COMPARE_STRING_FIELD(tableSpaceName);
 	COMPARE_NODE_FIELD(viewQuery);
 	COMPARE_SCALAR_FIELD(skipData);
+	COMPARE_SCALAR_FIELD(ivm);
 
 	return true;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2b0236937a..8ec4059cc0 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1119,6 +1119,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
 	WRITE_STRING_FIELD(tableSpaceName);
 	WRITE_NODE_FIELD(viewQuery);
 	WRITE_BOOL_FIELD(skipData);
+	WRITE_BOOL_FIELD(ivm);
 }
 
 static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3f68f7c18d..cc6dcb7220 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -563,6 +563,7 @@ _readIntoClause(void)
 	READ_STRING_FIELD(tableSpaceName);
 	READ_NODE_FIELD(viewQuery);
 	READ_BOOL_FIELD(skipData);
+	READ_BOOL_FIELD(ivm);
 
 	READ_DONE();
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b5966712ce..21beb087c2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -459,6 +459,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target create_mv_target
+%type <boolean>	incremental
 
 %type <defelt>	createfunc_opt_item common_func_opt_item dostmt_opt_item
 %type <fun_param> func_arg func_arg_with_default table_func_column aggr_arg
@@ -692,7 +693,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
 	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
-	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+	INCLUDING INCREMENT INCREMENTAL INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
 	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
 	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -4359,30 +4360,32 @@ opt_with_data:
  *****************************************************************************/
 
 CreateMatViewStmt:
-		CREATE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
+		CREATE OptNoLog incremental MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $7;
-					ctas->into = $5;
+					ctas->query = $8;
+					ctas->into = $6;
 					ctas->objtype = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = false;
 					/* cram additional flags into the IntoClause */
-					$5->rel->relpersistence = $2;
-					$5->skipData = !($8);
+					$6->rel->relpersistence = $2;
+					$6->skipData = !($9);
+					$6->ivm = $3;
 					$$ = (Node *) ctas;
 				}
-		| CREATE OptNoLog MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
+		| CREATE OptNoLog incremental MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $10;
-					ctas->into = $8;
+					ctas->query = $11;
+					ctas->into = $9;
 					ctas->objtype = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = true;
 					/* cram additional flags into the IntoClause */
-					$8->rel->relpersistence = $2;
-					$8->skipData = !($11);
+					$9->rel->relpersistence = $2;
+					$9->skipData = !($12);
+					$9->ivm = $3;
 					$$ = (Node *) ctas;
 				}
 		;
@@ -4399,9 +4402,14 @@ create_mv_target:
 					$$->tableSpaceName = $5;
 					$$->viewQuery = NULL;		/* filled at analysis time */
 					$$->skipData = false;		/* might get changed later */
+					$$->ivm = false;
 				}
 		;
 
+incremental:	INCREMENTAL				{ $$ = true; }
+				| /*EMPTY*/				{ $$ = false; }
+		;
+
 OptNoLog:	UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
 		;
@@ -15752,6 +15760,7 @@ unreserved_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
@@ -16304,6 +16313,7 @@ bare_label_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index dab5c4ff5d..bc1bcbda13 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -117,6 +117,7 @@ typedef struct IntoClause
 	char	   *tableSpaceName; /* table space to use, or NULL */
 	Node	   *viewQuery;		/* materialized view's SELECT query */
 	bool		skipData;		/* true for WITH NO DATA */
+	bool		ivm;			/* true for WITH IVM */
 } IntoClause;
 
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index bcef7eed2f..a43af4a597 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -205,6 +205,7 @@ PG_KEYWORD("in", IN_P, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("incremental", INCREMENTAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD, BARE_LABEL)
-- 
2.17.1

#213Zhihong Yu
zyu@yugabyte.com
In reply to: Yugo NAGATA (#212)
Re: Implementing Incremental View Maintenance

On Thu, Feb 3, 2022 at 8:50 AM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Thu, 3 Feb 2022 08:48:00 -0800
Zhihong Yu <zyu@yugabyte.com> wrote:

On Thu, Feb 3, 2022 at 8:28 AM Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi,

On Thu, 13 Jan 2022 18:23:42 +0800
Julien Rouhaud <rjuju123@gmail.com> wrote:

Hi,

On Thu, Nov 25, 2021 at 04:37:10PM +0900, Yugo NAGATA wrote:

On Wed, 24 Nov 2021 04:31:25 +0000
"r.takahashi_2@fujitsu.com" <r.takahashi_2@fujitsu.com> wrote:

I checked the same procedure on v24 patch.
But following error occurs instead of the original error.

ERROR: relation "ivm_t_index" already exists

Thank you for pointing out it!

Hmmm, an index is created when IMMV is defined, so CREAE INDEX

called

after this would fail... Maybe, we should not create any index

automatically

if IMMV is created WITH NO DATA.

I'll fix it after some investigation.

Are you still investigating on that problem? Also, the patchset

doesn't

apply

anymore:

I attached the updated and rebased patch set.

I fixed to not create a unique index when an IMMV is created WITH NO

DATA.

Instead, the index is created by REFRESH WITH DATA only when the same

one

is not created yet.

Also, I fixed the documentation to describe that foreign tables and
partitioned
tables are not supported according with Takahashi-san's suggestion.

There isn't any answer to your following email summarizing the

feature

yet, so

I'm not sure what should be the status of this patch, as there's no

ideal

category for that. For now I'll change the patch to Waiting on

Author

on the

cf app, feel free to switch it back to Needs Review if you think it's

more

suitable, at least for the design discussion need.

I changed the status to Needs Review.

Hi,

Did you intend to attach updated patch ?

I don't seem to find any.

Oops, I attached. Thanks!

Hi,

For CreateIndexOnIMMV():

+           ereport(NOTICE,
+                   (errmsg("could not create an index on materialized view
\"%s\" automatically",
...
+           return;
+       }

Should the return type be changed to bool so that the caller knows whether
the index creation succeeds ?
If index creation is unsuccessful, should the call
to CreateIvmTriggersOnBaseTables() be skipped ?

For check_ivm_restriction_walker():

+           break;
+           expression_tree_walker(node, check_ivm_restriction_walker,
NULL);
+           break;

Something is missing between the break and expression_tree_walker().

Cheers

#214huyajun
hu_yajun@qq.com
In reply to: Yugo NAGATA (#212)
Re: Implementing Incremental View Maintenance

Hi, Nagata-san
I am very interested in IMMV and read your patch but have some comments in v25-0007-Add-Incremental-View-Maintenance-support.patch and want to discuss with you.

+		/* For IMMV, we need to rewrite matview query */
+		query = rewriteQueryForIMMV(query, into->colNames);
+		query_immv = copyObject(query);
/* Create triggers on incremental maintainable materialized view */
+				Assert(query_immv != NULL);
+				CreateIvmTriggersOnBaseTables(query_immv, matviewOid, true);
1. Do we need copy query?Is it okay  that CreateIvmTriggersOnBaseTables directly use (Query *) into->viewQuery instead of query_immv like CreateIndexOnIMMV?  It seems only planner may change query, but it shouldn't affect us finding the correct base table in CreateIvmTriggersOnBaseTables .
+void
+CreateIndexOnIMMV(Query *query, Relation matviewRel)
+{
+	Query *qry = (Query *) copyObject(query);
2. Also, is it okay to not copy query in CreateIndexOnIMMV? It seems we only read query in CreateIndexOnIMMV.

Regards,

#215Yugo NAGATA
nagata@sraoss.co.jp
In reply to: huyajun (#214)
Re: Implementing Incremental View Maintenance

On Wed, 16 Feb 2022 22:34:18 +0800
huyajun <hu_yajun@qq.com> wrote:

Hi, Nagata-san
I am very interested in IMMV and read your patch but have some comments in v25-0007-Add-Incremental-View-Maintenance-support.patch and want to discuss with you.

Thank you for your review!

+		/* For IMMV, we need to rewrite matview query */
+		query = rewriteQueryForIMMV(query, into->colNames);
+		query_immv = copyObject(query);
/* Create triggers on incremental maintainable materialized view */
+				Assert(query_immv != NULL);
+				CreateIvmTriggersOnBaseTables(query_immv, matviewOid, true);
1. Do we need copy query?Is it okay  that CreateIvmTriggersOnBaseTables directly use (Query *) into->viewQuery instead of query_immv like CreateIndexOnIMMV?  It seems only planner may change query, but it shouldn't affect us finding the correct base table in CreateIvmTriggersOnBaseTables .

The copy to query_immv was necessary for supporting sub-queries in the view
definition. However, we excluded the fueature from the current patch to reduce
the patch size, so it would be unnecessary. I'll fix it.

+void
+CreateIndexOnIMMV(Query *query, Relation matviewRel)
+{
+	Query *qry = (Query *) copyObject(query);
2. Also, is it okay to not copy query in CreateIndexOnIMMV? It seems we only read query in CreateIndexOnIMMV.

This was also necessary for supporting CTEs, but unnecessary in the current
patch, so I'll fix it, too.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#216Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#215)
10 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

I attached the updated patch-set (v26).

On Wed, 16 Feb 2022 22:34:18 +0800
huyajun <hu_yajun@qq.com> wrote:

Hi, Nagata-san
I am very interested in IMMV and read your patch but have some comments in v25-0007-Add-Incremental-View-Maintenance-support.patch and want to discuss with you.

Thank you for your review!

+		/* For IMMV, we need to rewrite matview query */
+		query = rewriteQueryForIMMV(query, into->colNames);
+		query_immv = copyObject(query);
/* Create triggers on incremental maintainable materialized view */
+				Assert(query_immv != NULL);
+				CreateIvmTriggersOnBaseTables(query_immv, matviewOid, true);
1. Do we need copy query?Is it okay  that CreateIvmTriggersOnBaseTables directly use (Query *) into->viewQuery instead of query_immv like CreateIndexOnIMMV?  It seems only planner may change query, but it shouldn't affect us finding the correct base table in CreateIvmTriggersOnBaseTables .

The copy to query_immv was necessary for supporting sub-queries in the view
definition. However, we excluded the fueature from the current patch to reduce
the patch size, so it would be unnecessary. I'll fix it.

+void
+CreateIndexOnIMMV(Query *query, Relation matviewRel)
+{
+	Query *qry = (Query *) copyObject(query);
2. Also, is it okay to not copy query in CreateIndexOnIMMV? It seems we only read query in CreateIndexOnIMMV.

This was also necessary for supporting CTEs, but unnecessary in the current
patch, so I'll fix it, too.

I removed unnecessary copies of Query in according with the suggestions
from huyajun, and fix wrong codes in a "switch" statement pointed out
by Zhihong Yu.

In addition, I made the following fixes:
- Fix psql tab-completion code according with master branch
- Fix auto-index-creation that didn't work well in REFRESH command
- Add documentation description about the automatic index creation

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

v26-0010-Add-documentations-about-Incremental-View-Mainte.patchtext/x-diff; name=v26-0010-Add-documentations-about-Incremental-View-Mainte.patchDownload
From 5a408499a79c277b4d5e913234468f2f5da603ad Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:25:34 +0900
Subject: [PATCH v26 10/10] Add documentations about Incremental View
 Maintenance

---
 doc/src/sgml/catalogs.sgml                    |  24 +-
 .../sgml/ref/create_materialized_view.sgml    | 131 +++++-
 .../sgml/ref/refresh_materialized_view.sgml   |   8 +-
 doc/src/sgml/rules.sgml                       | 443 ++++++++++++++++++
 4 files changed, 601 insertions(+), 5 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 7777d60514..990798e13d 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2188,6 +2188,15 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>relisivm</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if materialized view enables incremental view maintenance
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>relrewrite</structfield> <type>oid</type>
@@ -3485,6 +3494,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para>
      </listitem>
     </varlistentry>
+
+    <varlistentry>
+     <term><symbol>DEPENDENCY_IMMV</symbol> (<literal>m</literal>)</term>
+     <listitem>
+      <para>
+       The dependent object was created as part of creation of the Materialized
+       View with Incremental View Maintenance reference, and is really just a 
+       part of its internal implementation. The dependent object must not be
+       dropped unless the materialized view is also dropped.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
 
    Other dependency flavors might be needed in future.
@@ -3868,7 +3889,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <structfield>extrelocatable</structfield> <type>bool</type>
       </para>
       <para>
-       True if extension can be relocated to another schema
+      True for materialized views which are enabled for incremental
+      view maintenance (IVM).
       </para></entry>
      </row>
 
diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml
index 0d2fea2b97..52a4d206b1 100644
--- a/doc/src/sgml/ref/create_materialized_view.sgml
+++ b/doc/src/sgml/ref/create_materialized_view.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
+CREATE [ INCREMENTAL ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
     [ (<replaceable>column_name</replaceable> [, ...] ) ]
     [ USING <replaceable class="parameter">method</replaceable> ]
     [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
@@ -60,6 +60,132 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
   <title>Parameters</title>
 
   <variablelist>
+   <varlistentry>
+    <term><literal>INCREMENTAL</literal></term>
+    <listitem>
+     <para>
+      If specified, some triggers are automatically created so that the rows
+      of the materialized view are immediately updated when base tables of the
+      materialized view are updated. In general, this allows faster update of
+      the materialized view at a price of slower update of the base tables
+      because the triggers will be invoked. We call this form of materialized
+      view as "Incrementally Maintainable Materialized View" (IMMV).
+     </para>
+     <para>
+      When <acronym>IMMV</acronym> is defined without using <command>WITH NO DATA</command>,
+      a unique index is created on the view automatically if possible.  If the view
+      definition query has a GROUP BY clause, a unique index is created on the columns
+      of GROUP BY expressions.  Also, if the view has DISTINCT clause, a unique index
+      is created on all columns in the target list.  Otherwise, if the view contains all
+      primary key attritubes of its base tables in the target list, a unique index is
+      created on these attritubes.  In other cases, no index is created.
+     </para>
+     <para>
+      There are restrictions of query definitions allowed to use this
+      option. The following are supported in query definitions for IMMV:
+      <itemizedlist>
+
+       <listitem>
+        <para>
+         Inner joins (including self-joins).
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Some built-in aggregate functions (count, sum, avg, min, max) without a HAVING
+         clause. 
+        </para>
+        </listitem>
+      </itemizedlist>
+
+      Unsupported queries with this option include the following:
+
+      <itemizedlist>
+       <listitem>
+        <para>
+         Outer joins.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Sub-queries.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Aggregate functions other than built-in count, sum, avg, min and max.
+        </para>
+       </listitem>
+       <listitem>
+        <para>
+         Aggregate functions with a HAVING clause.
+        </para>
+       </listitem>
+       <listitem>
+        <para>
+         DISTINCT ON, WINDOW, VALUES, LIMIT and OFFSET clause.
+        </para>
+       </listitem>
+      </itemizedlist>
+
+      Other restrictions include:
+      <itemizedlist>
+
+       <listitem>
+        <para>
+         IMMVs must be based on simple base tables. It's not supported to
+         create them on top of views or materialized views.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         When the TRUNCATE command is executed on a base table,
+         no changes are made to the materialized view.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         It is not supported to include system columns in an IMMV.
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+ERROR:  system column is not supported with IVM
+         </programlisting>
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Non-immutable functions are not supported.
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+ERROR:  functions in IMMV must be marked IMMUTABLE
+         </programlisting>
+        </para>
+        </listitem>
+
+       <listitem>
+        <para>
+         IMMVs do not support expressions that contains aggregates
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Logical replication does not support IMMVs.
+        </para>
+       </listitem>
+
+      </itemizedlist>
+
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>IF NOT EXISTS</literal></term>
     <listitem>
@@ -155,7 +281,8 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
       This clause specifies whether or not the materialized view should be
       populated at creation time.  If not, the materialized view will be
       flagged as unscannable and cannot be queried until <command>REFRESH
-      MATERIALIZED VIEW</command> is used.
+      MATERIALIZED VIEW</command> is used.  Also, if the view is IMMV,
+      triggers for maintaining the view are not created.
      </para>
     </listitem>
    </varlistentry>
diff --git a/doc/src/sgml/ref/refresh_materialized_view.sgml b/doc/src/sgml/ref/refresh_materialized_view.sgml
index 675d6090f3..c29cfc19b6 100644
--- a/doc/src/sgml/ref/refresh_materialized_view.sgml
+++ b/doc/src/sgml/ref/refresh_materialized_view.sgml
@@ -35,9 +35,13 @@ REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] <replaceable class="parameter">name</
    owner of the materialized view.  The old contents are discarded.  If
    <literal>WITH DATA</literal> is specified (or defaults) the backing query
    is executed to provide the new data, and the materialized view is left in a
-   scannable state.  If <literal>WITH NO DATA</literal> is specified no new
+   scannable state.  If the view is an incrementally maintainable materialized
+   view (IMMV) and was unpopulated, triggers for maintaining the view are
+   created. Also, a unique index is created for IMMV if it is possible and the
+   view doesn't have that yet.
+   If <literal>WITH NO DATA</literal> is specified no new
    data is generated and the materialized view is left in an unscannable
-   state.
+   state.  If the view is IMMV, the triggers are dropped.
   </para>
   <para>
    <literal>CONCURRENTLY</literal> and <literal>WITH NO DATA</literal> may not
diff --git a/doc/src/sgml/rules.sgml b/doc/src/sgml/rules.sgml
index 4aa4e00e01..13a64eab7f 100644
--- a/doc/src/sgml/rules.sgml
+++ b/doc/src/sgml/rules.sgml
@@ -1090,6 +1090,449 @@ SELECT word FROM words ORDER BY word &lt;-&gt; 'caterpiler' LIMIT 10;
 
 </sect1>
 
+<sect1 id="rules-ivm">
+<title>Incremental View Maintenance</title>
+
+<indexterm zone="rules-ivm">
+ <primary>incremental view maintenance</primary>
+</indexterm>
+
+<indexterm zone="rules-ivm">
+ <primary>materialized view</primary>
+ <secondary>incremental view maintenance</secondary>
+</indexterm>
+
+<indexterm zone="rules-ivm">
+ <primary>view</primary>
+ <secondary>incremental view maintenance</secondary>
+</indexterm>
+
+<sect2 id="rules-ivm-overview">
+<title>Overview</title>
+
+<para>
+    Incremental View Maintenance (<acronym>IVM</acronym>) is a way to make
+    materialized views up-to-date in which only incremental changes are computed
+    and applied on views rather than recomputing the contents from scratch as
+    <command>REFRESH MATERIALIZED VIEW</command> does.  <acronym>IVM</acronym>
+    can update materialized views more efficiently than recomputation when only
+    small parts of the view are changed.
+</para>
+
+<para>
+    There are two approaches with regard to timing of view maintenance:
+    immediate and deferred.  In immediate maintenance, views are updated in the
+    same transaction that its base table is modified.  In deferred maintenance,
+    views are updated after the transaction is committed, for example, when the
+    view is accessed, as a response to user command like <command>REFRESH
+    MATERIALIZED VIEW</command>, or periodically in background, and so on.
+    <productname>PostgreSQL</productname> currently implements only a kind of
+    immediate maintenance, in which materialized views are updated immediately
+    in AFTER triggers when a base table is modified.
+</para>
+
+<para>
+    To create materialized views supporting <acronym>IVM</acronym>, use the
+    <command>CREATE INCREMENTAL MATERIALIZED VIEW</command>, for example:
+<programlisting>
+CREATE <emphasis>INCREMENTAL</emphasis> MATERIALIZED VIEW mymatview AS SELECT * FROM mytab;
+</programlisting>
+    When a materialized view is created with the <literal>INCREMENTAL</literal>
+    keyword, some triggers are automatically created so that the view's contents are
+    immediately updated when its base tables are modified. We call this form
+    of materialized view an Incrementally Maintainable Materialized View
+    (<acronym>IMMV</acronym>).
+<programlisting>
+postgres=# CREATE INCREMENTAL MATERIALIZED VIEW m AS SELECT * FROM t0;
+NOTICE:  could not create an index on materialized view "m" automatically
+HINT:  Create an index on the materialized view for effcient incremental maintenance.
+SELECT 3
+postgres=# SELECT * FROM m;
+ i
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+postgres=# INSERT INTO t0 VALUES (4);
+INSERT 0 1
+postgres=# SELECT * FROM m; -- automatically updated
+ i
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+</programlisting>
+</para>
+
+<para>
+    Some <acronym>IMMV</acronym>s have hidden columns which are added
+    automatically when a materialized view is created. Their name starts
+    with <literal>__ivm_</literal> and they contain information required
+    for maintaining the <acronym>IMMV</acronym>. Such columns are not visible
+    when the <acronym>IMMV</acronym> is accessed by <literal>SELECT *</literal>
+    but are visible if the column name is explicitly specified in the target
+    list. We can also see the hidden columns in <literal>\d</literal>
+    meta-commands of <command>psql</command> commands.
+</para>
+
+<para>
+    In general, <acronym>IMMV</acronym>s allow faster updates of materialized
+    views at the price of slower updates to their base tables. Updates of
+    <acronym>IMMV</acronym> is slower because triggers will be invoked and the
+    view is updated in triggers per modification statement.
+</para>
+
+<para>
+    For example, suppose a normal materialized view defined as below:
+
+<programlisting>
+test=# CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm AS
+        SELECT a.aid, b.bid, a.abalance, b.bbalance
+        FROM pgbench_accounts a JOIN pgbench_branches b USING(bid);
+SELECT 10000000
+
+</programlisting>
+
+    Updating a tuple in a base table of this materialized view is rapid but the
+   <command>REFRESH MATERIALIZED VIEW</command> command on this view takes a long time:
+
+<programlisting>
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 0.990 ms
+
+test=# REFRESH MATERIALIZED VIEW mv_normal ;
+REFRESH MATERIALIZED VIEW
+Time: 33533.952 ms (00:33.534)
+</programlisting>
+</para>
+
+<para>
+    On the other hand, after creating <acronym>IMMV</acronym> with the same view
+    definition as below:
+
+<programlisting>
+test=# CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm AS
+        SELECT a.aid, b.bid, a.abalance, b.bbalance
+        FROM pgbench_accounts a JOIN pgbench_branches b USING(bid);
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+NOTICE:  created index "mv_ivm_index" on materialized view "mv_ivm"
+</programlisting>
+
+    updating a tuple in a base table takes more than the normal view,
+    but its content is updated automatically and this is faster than the
+    <command>REFRESH MATERIALIZED VIEW</command> command.
+
+<programlisting>
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 13.068 ms
+</programlisting>
+
+</para>
+
+<para>
+    Appropriate indexes on <acronym>IMMV</acronym>s are necessary for
+    efficient <acronym>IVM</acronym> because it looks for tuples to be
+    updated in <acronym>IMMV</acronym>.  If there are no indexes, it
+    will take a long time.
+</para>
+
+<para>
+    Therefore, when <acronym>IMMV</acronym> is defined, a unique index is created on the view
+    automatically if possible.  If the view definition query has a GROUP BY clause, a unique
+    index is created on the columns of GROUP BY expressions.  Also, if the view has DISTINCT
+    clause, a unique index is created on all columns in the target list. Otherwise, if the
+    view contains all primary key attritubes of its base tables in the target list, a unique
+    index is created on these attritubes.  In other cases, no index is created.
+</para>
+
+<para>
+    In the previous example, a unique index "mv_ivm_index" is created on aid and bid
+    columns of materialized view "mv_ivm", and this enables the rapid update of the view.
+    Dropping this index make updating the view take a loger time.
+<programlisting>
+test=# DROP INDEX mv_ivm_index;
+DROP INDEX
+Time: 67.081 ms
+
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 16386.245 ms (00:16.386)
+</programlisting>
+
+</para>
+
+<para>
+    <acronym>IVM</acronym> is effective when we want to keep a materialized
+    view up-to-date and small fraction of a base table is modified
+    infrequently.  Due to the overhead of immediate maintenance, <acronym>IVM</acronym>
+    is not effective when a base table is modified frequently.  Also, when a
+    large part of a base table is modified or large data is inserted into a
+    base table, <acronym>IVM</acronym> is not effective and the cost of
+    maintenance can be larger than the <command>REFRESH MATERIALIZED VIEW</command>
+    command. In such situation, we can use <command>REFRESH MATERIALIZED VIEW</command>
+    and specify <literal>WITH NO DATA</literal> to disable immediate
+    maintenance before modifying a base table. After a base table modification,
+    execute the <command>REFRESH MATERIALIZED VIEW</command> (with <literal>WITH DATA</literal>)
+    command to refresh the view data and enable immediate maintenance.
+</para>
+
+</sect2>
+
+<sect2>
+<title>Supported View Definitions and Restrictions</title>
+
+<para>
+    Currently, we can create <acronym>IMMV</acronym>s using inner joins, and some
+    aggregates. However, several restrictions apply to the definition of IMMV.
+</para>
+
+<sect3>
+<title>Joins</title>
+<para>
+    Inner joins including self-join are supported. Outer joins are not supported.
+</para>
+</sect3>
+
+<sect3>
+<title>Aggregates</title>
+<para>
+    Supported aggregate functions are <function>count</function>, <function>sum</function>,
+    <function>avg</function>, <function>min</function>, and <function>max</function>.
+    Currently, only built-in aggregate functions are supported and user defined
+    aggregates cannot be used.  When a base table is modified, the new aggregated
+    values are incrementally calculated using the old aggregated values and values
+    of related hidden columns stored in <acronym>IMMV</acronym>.
+</para>
+
+<para>
+     Note that for <function>min</function> or <function>max</function>, the new values
+     could be re-calculated from base tables with regard to the affected groups when a
+     tuple containing the current minimal or maximal values are deleted from a base table.
+     Therefore, it can takes a long time to update an <acronym>IMMV</acronym> containing
+     these functions.
+</para>
+
+<para>
+    Also note that using <function>sum</function> or <function>avg</function> on
+    <type>real</type> (<type>float4</type>) type or <type>double precision</type>
+    (<type>float8</type>) type in <acronym>IMMV</acronym> is unsafe. This is
+    because aggregated values in <acronym>IMMV</acronym> can become different from
+    results calculated from base tables due to the limited precision of these types.
+    To avoid this problem, use the <type>numeric</type> type instead.
+</para>
+
+    <sect4>
+    <title>Restrictions on Aggregates</title>
+    <para>
+        There are the following restrictions:
+    <itemizedlist>
+        <listitem>
+        <para>
+            If we have a <literal>GROUP BY</literal> clause, expressions specified in
+               <literal>GROUP BY</literal> must appear in the target list.  This is
+               how tuples to be updated in the <acronym>IMMV</acronym> are identified.
+               These attributes are used as scan keys for searching tuples in the
+               <acronym>IMMV</acronym>, so indexes on them are required for efficient
+               <acronym>IVM</acronym>.
+        </para>
+        </listitem>
+
+        <listitem>
+        <para>
+            <literal>HAVING</literal> clause cannot be used.
+        </para>
+        </listitem>
+    </itemizedlist>
+    </para>
+    </sect4>
+</sect3>
+
+<sect3>
+<title>Other General Restrictions</title>
+<para>
+    There are other restrictions which generally apply to <acronym>IMMV</acronym>:
+    <itemizedlist>
+        <listitem>
+          <para>
+           Sub-queries cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+           CTEs cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+           Window functions cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s must be based on simple base tables.  It's not
+               supported to create them on top of views, materialized views, foreign tables, inhe.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            LIMIT and OFFSET clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s cannot contain system columns.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s cannot contain non-immutable functions.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            UNION/INTERSECT/EXCEPT clauses cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            DISTINCT ON clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            TABLESAMPLE parameter cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            inheritance parent tables cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            VALUES clause cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <literal>GROUPING SETS</literal> and <literal>FILTER</literal> clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            FOR UPDATE/SHARE cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            targetlist cannot contain columns whose name start with <literal>__ivm_</literal>.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            targetlist cannot contain expressions which contain an aggregate in it.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+              Logical replication is not supported, that is, even when a base table
+               at a publisher node is modified, <acronym>IMMV</acronym>s at subscriber
+               nodes are not updated.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            When the <literal>TRUNCATE</literal> command is executed on a base table,
+               nothing is changed on the <acronym>IMMV</acronym>.
+          </para>
+        </listitem>
+
+    </itemizedlist>
+</para>
+</sect3>
+
+</sect2>
+
+<sect2>
+<title><literal>DISTINCT</literal></title>
+
+<para>
+    <productname>PostgreSQL</productname> supports <acronym>IMMV</acronym> with
+    <literal>DISTINCT</literal>.  For example, suppose a <acronym>IMMV</acronym>
+    defined with <literal>DISTINCT</literal> on a base table containing duplicate
+    tuples.  When tuples are deleted from the base table, a tuple in the view is
+    deleted if and only if the multiplicity of the tuple becomes zero.  Moreover,
+    when tuples are inserted into the base table, a tuple is inserted into the
+    view only if the same tuple doesn't already exist in it.
+</para>
+
+<para>
+    Physically, an <acronym>IMMV</acronym> defined with <literal>DISTINCT</literal>
+    contains tuples after eliminating duplicates, and the multiplicity of each tuple
+    is stored in a hidden column named <literal>__ivm_count__</literal>.
+</para>
+</sect2>
+
+<sect2>
+<title>Concurrent Transactions</title>
+<para>
+    Suppose an <acronym>IMMV</acronym> is defined on two base tables and each
+    table was modified in different a concurrent transaction simultaneously.
+    In the transaction which was committed first, <acronym>IMMV</acronym> can
+    be updated considering only the change which happened in this transaction.
+    On the other hand, in order to update the view correctly in the transaction
+    which was committed later, we need to know the changes occurred in
+    both transactions.  For this reason, <literal>ExclusiveLock</literal>
+    is held on an <acronym>IMMV</acronym> immediately after a base table is
+    modified in <literal>READ COMMITTED</literal> mode to make sure that
+    the <acronym>IMMV</acronym> is updated in the latter transaction after
+    the former transaction is committed.  In <literal>REPEATABLE READ</literal>
+    or <literal>SERIALIZABLE</literal> mode, an error is raised immediately
+    if lock acquisition fails because any changes which occurred in
+    other transactions are not be visible in these modes and
+    <acronym>IMMV</acronym> cannot be updated correctly in such situations.
+    However, as an exception if the view has only one base table, 
+    the lock held on thew view is <literal>RowExclusiveLock</literal>.
+</para>
+</sect2>
+
+<sect2>
+<title>Row Level Security</title>
+<para>
+    If some base tables have row level security policy, rows that are not visible
+    to the materialized view's owner are excluded from the result.  In addition, such
+    rows are excluded as well when views are incrementally maintained.  However, if a
+    new policy is defined or policies are changed after the materialized view was created,
+    the new policy will not be applied to the view contents.  To apply the new policy,
+    you need to refresh materialized views.
+</para>
+</sect2>
+
+</sect1>
+
 <sect1 id="rules-update">
 <title>Rules on <command>INSERT</command>, <command>UPDATE</command>, and <command>DELETE</command></title>
 
-- 
2.17.1

v26-0009-Add-regression-tests-for-Incremental-View-Mainte.patchtext/x-diff; name=v26-0009-Add-regression-tests-for-Incremental-View-Mainte.patchDownload
From bdde89bba90a0d09e0cb082e92c8a7982522a8b5 Mon Sep 17 00:00:00 2001
From: Takuma Hoshiai <takuma.hoshiai@gmail.com>
Date: Wed, 10 Mar 2021 11:11:13 +0900
Subject: [PATCH v26 09/10] Add regression tests for Incremental View
 Maintenance

---
 .../regress/expected/incremental_matview.out  | 812 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/incremental_matview.sql  | 400 +++++++++
 3 files changed, 1213 insertions(+), 1 deletion(-)
 create mode 100644 src/test/regress/expected/incremental_matview.out
 create mode 100644 src/test/regress/sql/incremental_matview.sql

diff --git a/src/test/regress/expected/incremental_matview.out b/src/test/regress/expected/incremental_matview.out
new file mode 100644
index 0000000000..7b10fcddbe
--- /dev/null
+++ b/src/test/regress/expected/incremental_matview.out
@@ -0,0 +1,812 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) WITH NO DATA;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ERROR:  materialized view "mv_ivm_1" has not been populated
+HINT:  Use the REFRESH MATERIALIZED VIEW command.
+REFRESH MATERIALIZED VIEW mv_ivm_1;
+NOTICE:  could not create an index on materialized view "mv_ivm_1" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- immediate maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- rename of IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_rename AS SELECT DISTINCT * FROM mv_base_a;
+NOTICE:  created index "mv_ivm_rename_index" on materialized view "mv_ivm_rename"
+ALTER MATERIALIZED VIEW mv_ivm_rename RENAME COLUMN __ivm_count__ TO xxx;
+ERROR:  IVM column can not be renamed
+DROP MATERIALIZED VIEW mv_ivm_rename;
+-- unique index on IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_unique AS SELECT DISTINCT * FROM mv_base_a;
+NOTICE:  created index "mv_ivm_unique_index" on materialized view "mv_ivm_unique"
+CREATE UNIQUE INDEX ON mv_ivm_unique(__ivm_count__);
+ERROR:  unique index creation on IVM columns is not supported
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__));
+ERROR:  unique index creation on IVM columns is not supported
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__ + 1));
+ERROR:  unique index creation on IVM columns is not supported
+DROP MATERIALIZED VIEW mv_ivm_unique;
+-- some query syntax
+BEGIN;
+CREATE FUNCTION ivm_func() RETURNS int LANGUAGE 'sql'
+       AS 'SELECT 1' IMMUTABLE;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_func AS SELECT * FROM ivm_func();
+NOTICE:  could not create an index on materialized view "mv_ivm_func" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_no_tbl AS SELECT 1;
+NOTICE:  could not create an index on materialized view "mv_ivm_no_tbl" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+ROLLBACK;
+-- result of materialized view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_duplicate" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+NOTICE:  created index "mv_ivm_distinct_index" on materialized view "mv_ivm_distinct"
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 20
+ 30
+ 40
+ 50
+(6 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+ROLLBACK;
+-- support SUM(), COUNT() and AVG() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i), AVG(j) FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_agg_index" on materialized view "mv_ivm_agg"
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 | 120 |     2 | 60.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg          
+---+-----+-------+----------------------
+ 1 |  10 |     1 |  10.0000000000000000
+ 2 | 220 |     2 | 110.0000000000000000
+ 3 |  30 |     1 |  30.0000000000000000
+ 4 |  40 |     1 |  40.0000000000000000
+ 5 |  50 |     1 |  50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support COUNT(*) aggregate function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(*) FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_agg_index" on materialized view "mv_ivm_agg"
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 |  20 |     1
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 | 120 |     2
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+ROLLBACK;
+-- support aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j), COUNT(j), AVG(j) FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_group" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count |         avg         
+-----+-------+---------------------
+ 150 |     5 | 30.0000000000000000
+(1 row)
+
+INSERT INTO mv_base_a VALUES(6,60);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count |         avg         
+-----+-------+---------------------
+ 210 |     6 | 35.0000000000000000
+(1 row)
+
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count | avg 
+-----+-------+-----
+     |     0 |    
+(1 row)
+
+ROLLBACK;
+-- resolved issue: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+NOTICE:  created index "mv_ivm_avg_bug_index" on materialized view "mv_ivm_avg_bug"
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     3 |  3.3333333333333333
+ 2 |  80 |     3 | 26.6666666666666667
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support MIN(), MAX() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT i, MIN(j), MAX(j)  FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_min_max_index" on materialized view "mv_ivm_min_max"
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  10 |  10
+ 2 |  20 |  20
+ 3 |  30 |  30
+ 4 |  40 |  40
+ 5 |  50 |  50
+(5 rows)
+
+INSERT INTO mv_base_a VALUES
+  (1,11), (1,12),
+  (2,21), (2,22),
+  (3,31), (3,32),
+  (4,41), (4,42),
+  (5,51), (5,52);
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  10 |  12
+ 2 |  20 |  22
+ 3 |  30 |  32
+ 4 |  40 |  42
+ 5 |  50 |  52
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  11 |  12
+ 2 |  20 |  22
+ 3 |  30 |  31
+ 4 |  40 |  42
+ 5 |  50 |  52
+(5 rows)
+
+ROLLBACK;
+-- support MIN(), MAX() aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT MIN(j), MAX(j)  FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_min_max" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+  10 |  50
+(1 row)
+
+INSERT INTO mv_base_a VALUES
+  (0,0), (6,60), (7,70);
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+   0 |  70
+(1 row)
+
+DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+  10 |  60
+(1 row)
+
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+     |    
+(1 row)
+
+ROLLBACK;
+-- support self join view and multiple change on the same table
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1, 10), (2, 20), (3, 30);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_self(v1, v2) AS
+ SELECT t1.v, t2.v FROM base_t AS t1 JOIN base_t AS t2 ON t1.i = t2.i;
+NOTICE:  could not create an index on materialized view "mv_self" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_self ORDER BY v1;
+ v1 | v2 
+----+----
+ 10 | 10
+ 20 | 20
+ 30 | 30
+(3 rows)
+
+INSERT INTO base_t VALUES (4,40);
+DELETE FROM base_t WHERE i = 1;
+UPDATE base_t SET v = v*10 WHERE i=2;
+SELECT * FROM mv_self ORDER BY v1;
+ v1  | v2  
+-----+-----
+  30 |  30
+  40 |  40
+ 200 | 200
+(3 rows)
+
+WITH
+ ins_t1 AS (INSERT INTO base_t VALUES (5,50) RETURNING 1),
+ ins_t2 AS (INSERT INTO base_t VALUES (6,60) RETURNING 1),
+ upd_t AS (UPDATE base_t SET v = v + 100  RETURNING 1),
+ dlt_t AS (DELETE FROM base_t WHERE i IN (4,5)  RETURNING 1)
+SELECT NULL;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT * FROM mv_self ORDER BY v1;
+ v1  | v2  
+-----+-----
+  50 |  50
+  60 |  60
+ 130 | 130
+ 300 | 300
+(4 rows)
+
+ROLLBACK;
+-- support simultaneous table changes
+BEGIN;
+CREATE TABLE base_r (i int, v int);
+CREATE TABLE base_s (i int, v int);
+INSERT INTO base_r VALUES (1, 10), (2, 20), (3, 30);
+INSERT INTO base_s VALUES (1, 100), (2, 200), (3, 300);
+CREATE INCREMENTAL MATERIALIZED VIEW mv(v1, v2) AS
+ SELECT r.v, s.v FROM base_r AS r JOIN base_s AS s USING(i);
+NOTICE:  could not create an index on materialized view "mv" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY v1;
+ v1 | v2  
+----+-----
+ 10 | 100
+ 20 | 200
+ 30 | 300
+(3 rows)
+
+WITH
+ ins_r AS (INSERT INTO base_r VALUES (1,11) RETURNING 1),
+ ins_r2 AS (INSERT INTO base_r VALUES (3,33) RETURNING 1),
+ ins_s AS (INSERT INTO base_s VALUES (2,222) RETURNING 1),
+ upd_r AS (UPDATE base_r SET v = v + 1000 WHERE i = 2 RETURNING 1),
+ dlt_s AS (DELETE FROM base_s WHERE i = 3 RETURNING 1)
+SELECT NULL;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT * FROM mv ORDER BY v1;
+  v1  | v2  
+------+-----
+   10 | 100
+   11 | 100
+ 1020 | 200
+ 1020 | 222
+(4 rows)
+
+ROLLBACK;
+-- support foreign reference constraints
+BEGIN;
+CREATE TABLE ri1 (i int PRIMARY KEY);
+CREATE TABLE ri2 (i int PRIMARY KEY REFERENCES ri1(i) ON UPDATE CASCADE ON DELETE CASCADE, v int);
+INSERT INTO ri1 VALUES (1),(2),(3);
+INSERT INTO ri2 VALUES (1),(2),(3);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ri(i1, i2) AS
+ SELECT ri1.i, ri2.i FROM ri1 JOIN ri2 USING(i);
+NOTICE:  created index "mv_ri_index" on materialized view "mv_ri"
+SELECT * FROM mv_ri ORDER BY i1;
+ i1 | i2 
+----+----
+  1 |  1
+  2 |  2
+  3 |  3
+(3 rows)
+
+UPDATE ri1 SET i=10 where i=1;
+DELETE FROM ri1 WHERE i=2;
+SELECT * FROM mv_ri ORDER BY i2;
+ i1 | i2 
+----+----
+  3 |  3
+ 10 | 10
+(2 rows)
+
+ROLLBACK;
+-- views including NULL
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1,10),(2, NULL);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+NOTICE:  could not create an index on materialized view "mv" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY i;
+ i | v  
+---+----
+ 1 | 10
+ 2 |   
+(2 rows)
+
+UPDATE base_t SET v = 20 WHERE i = 2;
+SELECT * FROM mv ORDER BY i;
+ i | v  
+---+----
+ 1 | 10
+ 2 | 20
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+NOTICE:  could not create an index on materialized view "mv" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY i;
+ i 
+---
+(0 rows)
+
+INSERT INTO base_t VALUES (1),(NULL);
+SELECT * FROM mv ORDER BY i;
+ i 
+---
+ 1
+  
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (1, 10), (1, 20);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, sum(v) FROM base_t GROUP BY i;
+NOTICE:  created index "mv_index" on materialized view "mv"
+SELECT * FROM mv ORDER BY i;
+ i | sum 
+---+-----
+ 1 |  30
+   |   3
+(2 rows)
+
+UPDATE base_t SET v = v * 10;
+SELECT * FROM mv ORDER BY i;
+ i | sum 
+---+-----
+ 1 | 300
+   |  30
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (NULL, 3), (NULL, 4), (NULL, 5);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, min(v), max(v) FROM base_t GROUP BY i;
+NOTICE:  created index "mv_index" on materialized view "mv"
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   1 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 1;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 3;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 5;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   4
+(1 row)
+
+ROLLBACK;
+-- IMMV containing user defined type
+BEGIN;
+CREATE TYPE mytype;
+CREATE FUNCTION mytype_in(cstring)
+ RETURNS mytype AS 'int4in'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+NOTICE:  return type mytype is only a shell
+CREATE FUNCTION mytype_out(mytype)
+ RETURNS cstring AS 'int4out'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+NOTICE:  argument type mytype is only a shell
+CREATE TYPE mytype (
+ LIKE = int4,
+ INPUT = mytype_in,
+ OUTPUT = mytype_out
+);
+CREATE FUNCTION mytype_eq(mytype, mytype)
+ RETURNS bool AS 'int4eq'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_lt(mytype, mytype)
+ RETURNS bool AS 'int4lt'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_cmp(mytype, mytype)
+ RETURNS integer AS 'btint4cmp'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE OPERATOR = (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_eq);
+CREATE OPERATOR < (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_lt);
+CREATE OPERATOR CLASS mytype_ops
+ DEFAULT FOR TYPE mytype USING btree AS
+ OPERATOR        1       <,
+ OPERATOR        3       = ,
+ FUNCTION		1		mytype_cmp(mytype,mytype);
+CREATE TABLE t_mytype (x mytype);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_mytype AS
+ SELECT * FROM t_mytype;
+NOTICE:  could not create an index on materialized view "mv_mytype" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+INSERT INTO t_mytype VALUES ('1'::mytype);
+SELECT * FROM mv_mytype;
+ x 
+---
+ 1
+(1 row)
+
+ROLLBACK;
+-- outer join is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i;
+ERROR:  OUTER JOIN is not supported on incrementally maintainable materialized view
+-- CTE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS
+    WITH b AS ( SELECT * FROM mv_base_b) SELECT a.i,a.j FROM mv_base_a a, b WHERE a.i = b.i;
+ERROR:  CTE is not supported on incrementally maintainable materialized view
+-- contain system column
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm01 AS SELECT i,j,xmin FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT i,j,xmin::text AS x_min FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm06 AS SELECT i,j,xidsend(xmin) AS x_min FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+-- contain subquery
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm03 AS SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT * FROM mv_base_b) b WHERE a.i = b.i;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm05 AS SELECT i,j, (SELECT k FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+-- contain ORDER BY
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm07 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) ORDER BY i,j,k;
+ERROR:  ORDER BY clause is not supported on incrementally maintainable materialized view
+-- contain HAVING
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm08 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) GROUP BY i,j,k HAVING SUM(i) > 5;
+ERROR:   HAVING clause is not supported on incrementally maintainable materialized view
+-- contain view or materialized view
+CREATE VIEW b_view AS SELECT i,k FROM mv_base_b;
+CREATE MATERIALIZED VIEW b_mview AS SELECT i,k FROM mv_base_b;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm07 AS SELECT a.i,a.j FROM mv_base_a a,b_view b WHERE a.i = b.i;
+ERROR:  VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm08 AS SELECT a.i,a.j FROM mv_base_a a,b_mview b WHERE a.i = b.i;
+ERROR:  VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm09 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm10 AS SELECT a.i,a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) OR a.i > 5;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+-- contain mutable functions
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+ERROR:  mutable function is not supported on incrementally maintainable materialized view
+HINT:  functions must be marked IMMUTABLE
+-- LIMIT/OFFSET is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm13 AS SELECT i,j FROM mv_base_a LIMIT 10 OFFSET 5;
+ERROR:  LIMIT/OFFSET clause is not supported on incrementally maintainable materialized view
+-- DISTINCT ON is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm14 AS SELECT DISTINCT ON(i) i, j FROM mv_base_a;
+ERROR:  DISTINCT ON is not supported on incrementally maintainable materialized view
+-- TABLESAMPLE clause is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm15 AS SELECT i, j FROM mv_base_a TABLESAMPLE SYSTEM(50);
+ERROR:  TABLESAMPLE clause is not supported on incrementally maintainable materialized view
+-- window functions are not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm16 AS SELECT *, cume_dist() OVER (ORDER BY i) AS rank FROM mv_base_a;
+ERROR:  window functions are not supported on incrementally maintainable materialized view
+-- aggregate function with some options is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm17 AS SELECT COUNT(*) FILTER(WHERE i < 3) FROM mv_base_a;
+ERROR:  aggregate function with FILTER clause is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm18 AS SELECT COUNT(DISTINCT i)  FROM mv_base_a;
+ERROR:  aggregate function with DISTINCT arguments is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm19 AS SELECT array_agg(j ORDER BY i DESC) FROM mv_base_a;
+ERROR:  aggregate function with ORDER clause is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm20 AS SELECT i,SUM(j) FROM mv_base_a GROUP BY GROUPING SETS((i),());
+ERROR:  GROUPING SETS, ROLLUP, or CUBE clauses is not supported on incrementally maintainable materialized view
+-- inheritance parent is not supported
+BEGIN;
+CREATE TABLE parent (i int, v int);
+CREATE TABLE child_a(options text) INHERITS(parent);
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm21 AS SELECT * FROM parent;
+ERROR:  inheritance parent is not supported on incrementally maintainable materialized view
+ROLLBACK;
+-- UNION statement is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm22 AS SELECT i,j FROM mv_base_a UNION ALL SELECT i,k FROM mv_base_b;;
+ERROR:  UNION/INTERSECT/EXCEPT statements are not supported on incrementally maintainable materialized view
+-- empty target list is not allowed with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm25 AS SELECT FROM mv_base_a;
+ERROR:  empty target list is not supported on incrementally maintainable materialized view
+-- FOR UPDATE/SHARE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm26 AS SELECT i,j FROM mv_base_a FOR UPDATE;
+ERROR:  FOR UPDATE/SHARE clause is not supported on incrementally maintainable materialized view
+-- tartget list cannot contain ivm column that start with '__ivm'
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm28 AS SELECT i AS "__ivm_count__" FROM mv_base_a;
+ERROR:  column name __ivm_count__ is not supported on incrementally maintainable materialized view
+-- expressions specified in GROUP BY must appear in the target list.
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm29 AS SELECT COUNT(i) FROM mv_base_a GROUP BY i;
+ERROR:  GROUP BY expression not appearing in select list is not supported on incrementally maintainable materialized view
+-- experssions containing an aggregate is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm30 AS SELECT sum(i)*0.5 FROM mv_base_a;
+ERROR:  expression containing an aggregate in it is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm31 AS SELECT sum(i)/sum(j) FROM mv_base_a;
+ERROR:  expression containing an aggregate in it is not supported on incrementally maintainable materialized view
+-- VALUES is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_only_values1 AS values(1);
+ERROR:  VALUES is not supported on incrementally maintainable materialized view
+-- base table which has row level security
+DROP USER IF EXISTS ivm_admin;
+NOTICE:  role "ivm_admin" does not exist, skipping
+DROP USER IF EXISTS ivm_user;
+NOTICE:  role "ivm_user" does not exist, skipping
+CREATE USER ivm_admin;
+CREATE USER ivm_user;
+SET SESSION AUTHORIZATION ivm_admin;
+CREATE TABLE rls_tbl(id int, data text, owner name);
+INSERT INTO rls_tbl VALUES
+  (1,'foo','ivm_user'),
+  (2,'bar','postgres');
+CREATE TABLE num_tbl(id int, num text);
+INSERT INTO num_tbl VALUES
+  (1,'one'),
+  (2,'two'),
+  (3,'three'),
+  (4,'four');
+CREATE POLICY rls_tbl_policy ON rls_tbl FOR SELECT TO PUBLIC USING(owner = current_user);
+CREATE POLICY rls_tbl_policy2 ON rls_tbl FOR INSERT TO PUBLIC WITH CHECK(current_user LIKE 'ivm_%');
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+GRANT ALL on rls_tbl TO PUBLIC;
+GRANT ALL on num_tbl TO PUBLIC;
+SET SESSION AUTHORIZATION ivm_user;
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls AS SELECT * FROM rls_tbl;
+NOTICE:  could not create an index on materialized view "ivm_rls" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+ id | data |  owner   
+----+------+----------
+  1 | foo  | ivm_user
+(1 row)
+
+INSERT INTO rls_tbl VALUES
+  (3,'baz','ivm_user'),
+  (4,'qux','postgres');
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+ id | data |  owner   
+----+------+----------
+  1 | foo  | ivm_user
+  3 | baz  | ivm_user
+(2 rows)
+
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls2 AS SELECT * FROM rls_tbl JOIN num_tbl USING(id);
+NOTICE:  could not create an index on materialized view "ivm_rls2" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+RESET SESSION AUTHORIZATION;
+WITH
+ x AS (UPDATE rls_tbl SET data = data || '_2' where id in (3,4)),
+ y AS (UPDATE num_tbl SET num = num || '_2' where id in (3,4))
+SELECT;
+--
+(1 row)
+
+SELECT * FROM ivm_rls2 ORDER BY 1,2,3;
+ id | data  |  owner   |   num   
+----+-------+----------+---------
+  1 | foo   | ivm_user | one
+  3 | baz_2 | ivm_user | three_2
+(2 rows)
+
+DROP TABLE rls_tbl CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to materialized view ivm_rls
+drop cascades to materialized view ivm_rls2
+DROP TABLE num_tbl CASCADE;
+DROP USER ivm_user;
+DROP USER ivm_admin;
+DROP TABLE mv_base_b CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to materialized view mv_ivm_1
+drop cascades to view b_view
+drop cascades to materialized view b_mview
+DROP TABLE mv_base_a CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 6d8f524ae9..79988e068c 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -86,7 +86,7 @@ test: brin_bloom brin_multi
 # psql depends on create_am
 # amutils depends on geometry, create_index_spgist, hash_index, brin
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort create_role
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort create_role incremental_matview
 
 # collate.*.utf8 tests cannot be run in parallel with each other
 test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8
diff --git a/src/test/regress/sql/incremental_matview.sql b/src/test/regress/sql/incremental_matview.sql
new file mode 100644
index 0000000000..311e9b96fb
--- /dev/null
+++ b/src/test/regress/sql/incremental_matview.sql
@@ -0,0 +1,400 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) WITH NO DATA;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+REFRESH MATERIALIZED VIEW mv_ivm_1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+-- immediate maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+
+-- rename of IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_rename AS SELECT DISTINCT * FROM mv_base_a;
+ALTER MATERIALIZED VIEW mv_ivm_rename RENAME COLUMN __ivm_count__ TO xxx;
+DROP MATERIALIZED VIEW mv_ivm_rename;
+
+-- unique index on IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_unique AS SELECT DISTINCT * FROM mv_base_a;
+CREATE UNIQUE INDEX ON mv_ivm_unique(__ivm_count__);
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__));
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__ + 1));
+DROP MATERIALIZED VIEW mv_ivm_unique;
+
+-- some query syntax
+BEGIN;
+CREATE FUNCTION ivm_func() RETURNS int LANGUAGE 'sql'
+       AS 'SELECT 1' IMMUTABLE;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_func AS SELECT * FROM ivm_func();
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_no_tbl AS SELECT 1;
+ROLLBACK;
+
+-- result of materialized view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ROLLBACK;
+
+-- support SUM(), COUNT() and AVG() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i), AVG(j) FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ROLLBACK;
+
+-- support COUNT(*) aggregate function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(*) FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j), COUNT(j), AVG(j) FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+INSERT INTO mv_base_a VALUES(6,60);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ROLLBACK;
+
+-- resolved issue: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support MIN(), MAX() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT i, MIN(j), MAX(j)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES
+  (1,11), (1,12),
+  (2,21), (2,22),
+  (3,31), (3,32),
+  (4,41), (4,42),
+  (5,51), (5,52);
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support MIN(), MAX() aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT MIN(j), MAX(j)  FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+INSERT INTO mv_base_a VALUES
+  (0,0), (6,60), (7,70);
+SELECT * FROM mv_ivm_min_max;
+DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
+SELECT * FROM mv_ivm_min_max;
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+ROLLBACK;
+
+-- support self join view and multiple change on the same table
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1, 10), (2, 20), (3, 30);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_self(v1, v2) AS
+ SELECT t1.v, t2.v FROM base_t AS t1 JOIN base_t AS t2 ON t1.i = t2.i;
+SELECT * FROM mv_self ORDER BY v1;
+INSERT INTO base_t VALUES (4,40);
+DELETE FROM base_t WHERE i = 1;
+UPDATE base_t SET v = v*10 WHERE i=2;
+SELECT * FROM mv_self ORDER BY v1;
+WITH
+ ins_t1 AS (INSERT INTO base_t VALUES (5,50) RETURNING 1),
+ ins_t2 AS (INSERT INTO base_t VALUES (6,60) RETURNING 1),
+ upd_t AS (UPDATE base_t SET v = v + 100  RETURNING 1),
+ dlt_t AS (DELETE FROM base_t WHERE i IN (4,5)  RETURNING 1)
+SELECT NULL;
+SELECT * FROM mv_self ORDER BY v1;
+ROLLBACK;
+
+-- support simultaneous table changes
+BEGIN;
+CREATE TABLE base_r (i int, v int);
+CREATE TABLE base_s (i int, v int);
+INSERT INTO base_r VALUES (1, 10), (2, 20), (3, 30);
+INSERT INTO base_s VALUES (1, 100), (2, 200), (3, 300);
+CREATE INCREMENTAL MATERIALIZED VIEW mv(v1, v2) AS
+ SELECT r.v, s.v FROM base_r AS r JOIN base_s AS s USING(i);
+SELECT * FROM mv ORDER BY v1;
+WITH
+ ins_r AS (INSERT INTO base_r VALUES (1,11) RETURNING 1),
+ ins_r2 AS (INSERT INTO base_r VALUES (3,33) RETURNING 1),
+ ins_s AS (INSERT INTO base_s VALUES (2,222) RETURNING 1),
+ upd_r AS (UPDATE base_r SET v = v + 1000 WHERE i = 2 RETURNING 1),
+ dlt_s AS (DELETE FROM base_s WHERE i = 3 RETURNING 1)
+SELECT NULL;
+SELECT * FROM mv ORDER BY v1;
+ROLLBACK;
+
+-- support foreign reference constraints
+BEGIN;
+CREATE TABLE ri1 (i int PRIMARY KEY);
+CREATE TABLE ri2 (i int PRIMARY KEY REFERENCES ri1(i) ON UPDATE CASCADE ON DELETE CASCADE, v int);
+INSERT INTO ri1 VALUES (1),(2),(3);
+INSERT INTO ri2 VALUES (1),(2),(3);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ri(i1, i2) AS
+ SELECT ri1.i, ri2.i FROM ri1 JOIN ri2 USING(i);
+SELECT * FROM mv_ri ORDER BY i1;
+UPDATE ri1 SET i=10 where i=1;
+DELETE FROM ri1 WHERE i=2;
+SELECT * FROM mv_ri ORDER BY i2;
+ROLLBACK;
+
+-- views including NULL
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1,10),(2, NULL);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+SELECT * FROM mv ORDER BY i;
+UPDATE base_t SET v = 20 WHERE i = 2;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+SELECT * FROM mv ORDER BY i;
+INSERT INTO base_t VALUES (1),(NULL);
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (1, 10), (1, 20);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, sum(v) FROM base_t GROUP BY i;
+SELECT * FROM mv ORDER BY i;
+UPDATE base_t SET v = v * 10;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (NULL, 3), (NULL, 4), (NULL, 5);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, min(v), max(v) FROM base_t GROUP BY i;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 1;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 3;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 5;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+-- IMMV containing user defined type
+BEGIN;
+
+CREATE TYPE mytype;
+CREATE FUNCTION mytype_in(cstring)
+ RETURNS mytype AS 'int4in'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_out(mytype)
+ RETURNS cstring AS 'int4out'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE TYPE mytype (
+ LIKE = int4,
+ INPUT = mytype_in,
+ OUTPUT = mytype_out
+);
+
+CREATE FUNCTION mytype_eq(mytype, mytype)
+ RETURNS bool AS 'int4eq'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_lt(mytype, mytype)
+ RETURNS bool AS 'int4lt'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_cmp(mytype, mytype)
+ RETURNS integer AS 'btint4cmp'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+
+CREATE OPERATOR = (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_eq);
+CREATE OPERATOR < (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_lt);
+
+CREATE OPERATOR CLASS mytype_ops
+ DEFAULT FOR TYPE mytype USING btree AS
+ OPERATOR        1       <,
+ OPERATOR        3       = ,
+ FUNCTION		1		mytype_cmp(mytype,mytype);
+
+CREATE TABLE t_mytype (x mytype);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_mytype AS
+ SELECT * FROM t_mytype;
+INSERT INTO t_mytype VALUES ('1'::mytype);
+SELECT * FROM mv_mytype;
+
+ROLLBACK;
+
+-- outer join is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i;
+-- CTE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS
+    WITH b AS ( SELECT * FROM mv_base_b) SELECT a.i,a.j FROM mv_base_a a, b WHERE a.i = b.i;
+-- contain system column
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm01 AS SELECT i,j,xmin FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT i,j,xmin::text AS x_min FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm06 AS SELECT i,j,xidsend(xmin) AS x_min FROM mv_base_a;
+-- contain subquery
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm03 AS SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT * FROM mv_base_b) b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm05 AS SELECT i,j, (SELECT k FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+-- contain ORDER BY
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm07 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) ORDER BY i,j,k;
+-- contain HAVING
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm08 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) GROUP BY i,j,k HAVING SUM(i) > 5;
+
+-- contain view or materialized view
+CREATE VIEW b_view AS SELECT i,k FROM mv_base_b;
+CREATE MATERIALIZED VIEW b_mview AS SELECT i,k FROM mv_base_b;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm07 AS SELECT a.i,a.j FROM mv_base_a a,b_view b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm08 AS SELECT a.i,a.j FROM mv_base_a a,b_mview b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm09 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm10 AS SELECT a.i,a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) OR a.i > 5;
+
+-- contain mutable functions
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+
+-- LIMIT/OFFSET is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm13 AS SELECT i,j FROM mv_base_a LIMIT 10 OFFSET 5;
+
+-- DISTINCT ON is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm14 AS SELECT DISTINCT ON(i) i, j FROM mv_base_a;
+
+-- TABLESAMPLE clause is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm15 AS SELECT i, j FROM mv_base_a TABLESAMPLE SYSTEM(50);
+
+-- window functions are not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm16 AS SELECT *, cume_dist() OVER (ORDER BY i) AS rank FROM mv_base_a;
+
+-- aggregate function with some options is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm17 AS SELECT COUNT(*) FILTER(WHERE i < 3) FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm18 AS SELECT COUNT(DISTINCT i)  FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm19 AS SELECT array_agg(j ORDER BY i DESC) FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm20 AS SELECT i,SUM(j) FROM mv_base_a GROUP BY GROUPING SETS((i),());
+
+-- inheritance parent is not supported
+BEGIN;
+CREATE TABLE parent (i int, v int);
+CREATE TABLE child_a(options text) INHERITS(parent);
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm21 AS SELECT * FROM parent;
+ROLLBACK;
+
+-- UNION statement is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm22 AS SELECT i,j FROM mv_base_a UNION ALL SELECT i,k FROM mv_base_b;;
+
+-- empty target list is not allowed with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm25 AS SELECT FROM mv_base_a;
+
+-- FOR UPDATE/SHARE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm26 AS SELECT i,j FROM mv_base_a FOR UPDATE;
+
+-- tartget list cannot contain ivm column that start with '__ivm'
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm28 AS SELECT i AS "__ivm_count__" FROM mv_base_a;
+
+-- expressions specified in GROUP BY must appear in the target list.
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm29 AS SELECT COUNT(i) FROM mv_base_a GROUP BY i;
+
+-- experssions containing an aggregate is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm30 AS SELECT sum(i)*0.5 FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm31 AS SELECT sum(i)/sum(j) FROM mv_base_a;
+
+-- VALUES is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_only_values1 AS values(1);
+
+-- base table which has row level security
+DROP USER IF EXISTS ivm_admin;
+DROP USER IF EXISTS ivm_user;
+CREATE USER ivm_admin;
+CREATE USER ivm_user;
+SET SESSION AUTHORIZATION ivm_admin;
+
+CREATE TABLE rls_tbl(id int, data text, owner name);
+INSERT INTO rls_tbl VALUES
+  (1,'foo','ivm_user'),
+  (2,'bar','postgres');
+CREATE TABLE num_tbl(id int, num text);
+INSERT INTO num_tbl VALUES
+  (1,'one'),
+  (2,'two'),
+  (3,'three'),
+  (4,'four');
+CREATE POLICY rls_tbl_policy ON rls_tbl FOR SELECT TO PUBLIC USING(owner = current_user);
+CREATE POLICY rls_tbl_policy2 ON rls_tbl FOR INSERT TO PUBLIC WITH CHECK(current_user LIKE 'ivm_%');
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+GRANT ALL on rls_tbl TO PUBLIC;
+GRANT ALL on num_tbl TO PUBLIC;
+
+SET SESSION AUTHORIZATION ivm_user;
+
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls AS SELECT * FROM rls_tbl;
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+INSERT INTO rls_tbl VALUES
+  (3,'baz','ivm_user'),
+  (4,'qux','postgres');
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls2 AS SELECT * FROM rls_tbl JOIN num_tbl USING(id);
+
+RESET SESSION AUTHORIZATION;
+
+WITH
+ x AS (UPDATE rls_tbl SET data = data || '_2' where id in (3,4)),
+ y AS (UPDATE num_tbl SET num = num || '_2' where id in (3,4))
+SELECT;
+SELECT * FROM ivm_rls2 ORDER BY 1,2,3;
+
+DROP TABLE rls_tbl CASCADE;
+DROP TABLE num_tbl CASCADE;
+DROP USER ivm_user;
+DROP USER ivm_admin;
+
+DROP TABLE mv_base_b CASCADE;
+DROP TABLE mv_base_a CASCADE;
-- 
2.17.1

v26-0008-Add-aggregates-support-in-IVM.patchtext/x-diff; name=v26-0008-Add-aggregates-support-in-IVM.patchDownload
From c1df9bfb2918f445cad26d0dade96264414e781d Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Mon, 2 Aug 2021 14:59:27 +0900
Subject: [PATCH v26 08/10] Add aggregates support in IVM
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Currently, count, sum, avg, min and max are supported.

As a restriction, expressions specified in GROUP BY must appear in
the target list because tuples to be updated in IMMV are identified
by using this group key. However, in the case of aggregates without
GROUP BY, there is only one tuple in the view, so keys are not uses
to identify tuples.

When creating a IMMV, in addition to __ivm_count column, some hidden
columns for each aggregate are added to the target list. For example,
names of these hidden columns are ivm_count_avg and ivm_sum_avg for
the average function, and so on.

In the case of views without aggregate functions, only the number of
tuple multiplicities in __ivm_count__ column are updated at incremental
maintenance. On the other hand, in the case of view with aggregates,
the aggregated values and related hidden columns are also updated. The
way of update depends the kind of aggregate function. Specifically,
sum and count are updated by simply adding or subtracting delta value
calculated from delta tables. avg is updated by using values of sum
and count stored in views as hidden columns and deltas calculated
from delta tables.

In min or max cases, it becomes more complicated. For an example of min,
when tuples are inserted, the smaller value between the current min value
in the view and the value calculated from the new delta table is used.
When tuples are deleted, if the current min value in the view equals to
the min in the old delta table, we need re-computation the latest min
value from base tables. Otherwise, the current value in the view remains.

As to sum, avg, min, and max (any aggregate functions except count),
NULL in input values is ignored, and this returns a null value when no
rows are selected. To support this specification, the number of not-NULL
input values is counted and stored in views as a hidden column. In the
case of count(), count(x) returns zero when no rows are selected, and
count(*) doesn't ignore NULL input. These specification are also supported.
---
 src/backend/commands/createas.c |  296 ++++++++-
 src/backend/commands/matview.c  | 1016 ++++++++++++++++++++++++++++++-
 src/include/commands/createas.h |    1 +
 3 files changed, 1278 insertions(+), 35 deletions(-)

diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 1fbcede7aa..83a1b3de9e 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/regproc.h"
 #include "utils/rel.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
@@ -80,6 +81,11 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_intorel;
 
+typedef struct
+{
+	bool	has_agg;
+} check_ivm_restriction_context;
+
 /* utility functions for CTAS definition creation */
 static ObjectAddress create_ctas_internal(List *attrList, IntoClause *into);
 static ObjectAddress create_ctas_nodata(List *tlist, IntoClause *into);
@@ -94,9 +100,9 @@ static void CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid mat
 									 Relids *relids, bool ex_lock);
 static void CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock);
 static void check_ivm_restriction(Node *node);
-static bool check_ivm_restriction_walker(Node *node, void *context);
-static Bitmapset *get_primary_key_attnos_from_query(Query *qry, List **constraintList, bool is_create);
+static bool check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context);
 static Bitmapset *get_primary_key_attnos_from_query(Query *query, List **constraintList, bool is_create);
+static bool check_aggregate_supports_ivm(Oid aggfnoid);
 
 /*
  * create_ctas_internal
@@ -429,6 +435,7 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
  * rewriteQueryForIMMV -- rewrite view definition query for IMMV
  *
  * count(*) is added for counting distinct tuples in views.
+ * Also, additional hidden columns are added for aggregate values.
  */
 Query *
 rewriteQueryForIMMV(Query *query, List *colNames)
@@ -443,14 +450,46 @@ rewriteQueryForIMMV(Query *query, List *colNames)
 	rewritten = copyObject(query);
 	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
 
-	/*
-	 * Convert DISTINCT to GROUP BY and add count(*) for counting distinct
-	 * tuples in views.
-	 */
-	if (rewritten->distinctClause)
+	/* group keys must be in targetlist */
+	if (rewritten->groupClause)
 	{
+		ListCell *lc;
+		foreach(lc, rewritten->groupClause)
+		{
+			SortGroupClause *scl = (SortGroupClause *) lfirst(lc);
+			TargetEntry *tle = get_sortgroupclause_tle(scl, rewritten->targetList);
+
+			if (tle->resjunk)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("GROUP BY expression not appearing in select list is not supported on incrementally maintainable materialized view")));
+		}
+	}
+	/* Convert DISTINCT to GROUP BY.  count(*) will be added afterward. */
+	else if (!rewritten->hasAggs && rewritten->distinctClause)
 		rewritten->groupClause = transformDistinctClause(NULL, &rewritten->targetList, rewritten->sortClause, false);
 
+	/* Add additional columns for aggregate values */
+	if (rewritten->hasAggs)
+	{
+		ListCell *lc;
+		List *aggs = NIL;
+		AttrNumber next_resno = list_length(rewritten->targetList) + 1;
+
+		foreach(lc, rewritten->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+			char *resname = (colNames == NIL ? tle->resname : strVal(list_nth(colNames, tle->resno - 1)));
+
+			if (IsA(tle->expr, Aggref))
+				makeIvmAggColumn(pstate, (Aggref *)tle->expr, resname, &next_resno, &aggs);
+		}
+		rewritten->targetList = list_concat(rewritten->targetList, aggs);
+	}
+
+	/* Add count(*) for counting distinct tuples in views */
+	if (rewritten->distinctClause || rewritten->hasAggs)
+	{
 		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
 		fn->agg_star = true;
 
@@ -467,6 +506,91 @@ rewriteQueryForIMMV(Query *query, List *colNames)
 	return rewritten;
 }
 
+/*
+ * makeIvmAggColumn -- make additional aggregate columns for IVM
+ *
+ * For an aggregate column specified by aggref, additional aggregate columns
+ * are added, which are used to calculate the new aggregate value in IMMV.
+ * An additional aggregate columns has a name based on resname
+ * (ex. ivm_count_resname), and resno specified by next_resno. The created
+ * columns are returned to aggs, and the resno for the next column is also
+ * returned to next_resno.
+ *
+ * Currently, an additional count() is created for aggref other than count.
+ * In addition, sum() is created for avg aggregate column.
+ */
+void
+makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber *next_resno, List **aggs)
+{
+	TargetEntry *tle_count;
+	Node *node;
+	FuncCall *fn;
+	Const	*dmy_arg = makeConst(INT4OID,
+								 -1,
+								 InvalidOid,
+								 sizeof(int32),
+								 Int32GetDatum(1),
+								 false,
+								 true); /* pass by value */
+	const char *aggname = get_func_name(aggref->aggfnoid);
+
+	/*
+	 * For aggregate functions except count, add count() func with the same arg parameters.
+	 * This count result is used for determining if the aggregate value should be NULL or not.
+	 * Also, add sum() func for avg because we need to calculate an average value as sum/count.
+	 *
+	 * XXX: If there are same expressions explicitly in the target list, we can use this instead
+	 * of adding new duplicated one.
+	 */
+	if (strcmp(aggname, "count") != 0)
+	{
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+
+		/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+		node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+		((Aggref *)node)->args = aggref->args;
+
+		tle_count = makeTargetEntry((Expr *) node,
+									*next_resno,
+									pstrdup(makeObjectName("__ivm_count",resname, "_")),
+									false);
+		*aggs = lappend(*aggs, tle_count);
+		(*next_resno)++;
+	}
+	if (strcmp(aggname, "avg") == 0)
+	{
+		List *dmy_args = NIL;
+		ListCell *lc;
+		foreach(lc, aggref->aggargtypes)
+		{
+			Oid		typeid = lfirst_oid(lc);
+			Type	type = typeidType(typeid);
+
+			Const *con = makeConst(typeid,
+								   -1,
+								   typeTypeCollation(type),
+								   typeLen(type),
+								   (Datum) 0,
+								   true,
+								   typeByVal(type));
+			dmy_args = lappend(dmy_args, con);
+			ReleaseSysCache(type);
+		}
+		fn = makeFuncCall(list_make1(makeString("sum")), NIL, COERCE_EXPLICIT_CALL, -1);
+
+		/* Make a Func with dummy args, and then override this by the original agg's args. */
+		node = ParseFuncOrColumn(pstate, fn->funcname, dmy_args, NULL, fn, false, -1);
+		((Aggref *)node)->args = aggref->args;
+
+		tle_count = makeTargetEntry((Expr *) node,
+									*next_resno,
+									pstrdup(makeObjectName("__ivm_sum",resname, "_")),
+									false);
+		*aggs = lappend(*aggs, tle_count);
+		(*next_resno)++;
+	}
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -920,11 +1044,13 @@ CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock
 static void
 check_ivm_restriction(Node *node)
 {
-	check_ivm_restriction_walker(node, NULL);
+	check_ivm_restriction_context context = {false};
+
+	check_ivm_restriction_walker(node, &context);
 }
 
 static bool
-check_ivm_restriction_walker(Node *node, void *context)
+check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context)
 {
 	if (node == NULL)
 		return false;
@@ -1005,6 +1131,8 @@ check_ivm_restriction_walker(Node *node, void *context)
 					}
 				}
 
+				context->has_agg |= qry->hasAggs;
+
 				/* restrictions for rtable */
 				foreach(lc, qry->rtable)
 				{
@@ -1053,7 +1181,7 @@ check_ivm_restriction_walker(Node *node, void *context)
 
 				}
 
-				query_tree_walker(qry, check_ivm_restriction_walker, NULL, QTW_IGNORE_RANGE_TABLE);
+				query_tree_walker(qry, check_ivm_restriction_walker, (void *) context, QTW_IGNORE_RANGE_TABLE);
 
 				break;
 			}
@@ -1064,8 +1192,12 @@ check_ivm_restriction_walker(Node *node, void *context)
 						ereport(ERROR,
 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								 errmsg("column name %s is not supported on incrementally maintainable materialized view", tle->resname)));
+				if (context->has_agg && !IsA(tle->expr, Aggref) && contain_aggs_of_level((Node *) tle->expr, 0))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("expression containing an aggregate in it is not supported on incrementally maintainable materialized view")));
 
-				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
 				break;
 			}
 		case T_JoinExpr:
@@ -1077,9 +1209,36 @@ check_ivm_restriction_walker(Node *node, void *context)
 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								 errmsg("OUTER JOIN is not supported on incrementally maintainable materialized view")));
 
-				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
+				break;
+			}
+		case T_Aggref:
+			{
+				/* Check if this supports IVM */
+				Aggref *aggref = (Aggref *) node;
+				const char *aggname = format_procedure(aggref->aggfnoid);
+
+				if (aggref->aggfilter != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with FILTER clause is not supported on incrementally maintainable materialized view")));
+
+				if (aggref->aggdistinct != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with DISTINCT arguments is not supported on incrementally maintainable materialized view")));
+
+				if (aggref->aggorder != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with ORDER clause is not supported on incrementally maintainable materialized view")));
+
+				if (!check_aggregate_supports_ivm(aggref->aggfnoid))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function %s is not supported on incrementally maintainable materialized view", aggname)));
+				break;
 			}
-			break;
 		default:
 			expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
 			break;
@@ -1087,6 +1246,91 @@ check_ivm_restriction_walker(Node *node, void *context)
 	return false;
 }
 
+/*
+ * check_aggregate_supports_ivm
+ *
+ * Check if the given aggregate function is supporting IVM
+ */
+static bool
+check_aggregate_supports_ivm(Oid aggfnoid)
+{
+	switch (aggfnoid)
+	{
+		/* count */
+		case F_COUNT_ANY:
+		case F_COUNT_:
+
+		/* sum */
+		case F_SUM_INT8:
+		case F_SUM_INT4:
+		case F_SUM_INT2:
+		case F_SUM_FLOAT4:
+		case F_SUM_FLOAT8:
+		case F_SUM_MONEY:
+		case F_SUM_INTERVAL:
+		case F_SUM_NUMERIC:
+
+		/* avg */
+		case F_AVG_INT8:
+		case F_AVG_INT4:
+		case F_AVG_INT2:
+		case F_AVG_NUMERIC:
+		case F_AVG_FLOAT4:
+		case F_AVG_FLOAT8:
+		case F_AVG_INTERVAL:
+
+		/* min */
+		case F_MIN_ANYARRAY:
+		case F_MIN_INT8:
+		case F_MIN_INT4:
+		case F_MIN_INT2:
+		case F_MIN_OID:
+		case F_MIN_FLOAT4:
+		case F_MIN_FLOAT8:
+		case F_MIN_DATE:
+		case F_MIN_TIME:
+		case F_MIN_TIMETZ:
+		case F_MIN_MONEY:
+		case F_MIN_TIMESTAMP:
+		case F_MIN_TIMESTAMPTZ:
+		case F_MIN_INTERVAL:
+		case F_MIN_TEXT:
+		case F_MIN_NUMERIC:
+		case F_MIN_BPCHAR:
+		case F_MIN_TID:
+		case F_MIN_ANYENUM:
+		case F_MIN_INET:
+		case F_MIN_PG_LSN:
+
+		/* max */
+		case F_MAX_ANYARRAY:
+		case F_MAX_INT8:
+		case F_MAX_INT4:
+		case F_MAX_INT2:
+		case F_MAX_OID:
+		case F_MAX_FLOAT4:
+		case F_MAX_FLOAT8:
+		case F_MAX_DATE:
+		case F_MAX_TIME:
+		case F_MAX_TIMETZ:
+		case F_MAX_MONEY:
+		case F_MAX_TIMESTAMP:
+		case F_MAX_TIMESTAMPTZ:
+		case F_MAX_INTERVAL:
+		case F_MAX_TEXT:
+		case F_MAX_NUMERIC:
+		case F_MAX_BPCHAR:
+		case F_MAX_TID:
+		case F_MAX_ANYENUM:
+		case F_MAX_INET:
+		case F_MAX_PG_LSN:
+			return true;
+
+		default:
+			return false;
+	}
+}
+
 /*
  * CreateIndexOnIMMV
  *
@@ -1144,7 +1388,29 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create)
 	index->concurrent = false;
 	index->if_not_exists = false;
 
-	if (query->distinctClause)
+	if (query->groupClause)
+	{
+		/* create unique constraint on GROUP BY expression columns */
+		foreach(lc, query->groupClause)
+		{
+			SortGroupClause *scl = (SortGroupClause *) lfirst(lc);
+			TargetEntry *tle = get_sortgroupclause_tle(scl, query->targetList);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+			IndexElem  *iparam;
+
+			iparam = makeNode(IndexElem);
+			iparam->name = pstrdup(NameStr(attr->attname));
+			iparam->expr = NULL;
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->opclassopts = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+		}
+	}
+	else if (query->distinctClause)
 	{
 		/* create unique constraint on all columns */
 		foreach(lc, query->targetList)
@@ -1202,7 +1468,7 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create)
 					(errmsg("could not create an index on materialized view \"%s\" automatically",
 							RelationGetRelationName(matviewRel)),
 					 errdetail("This target list does not have all the primary key columns, "
-							   "or this view does not contain DISTINCT clause."),
+							   "or this view does not contain GROUP BY or DISTINCT clause."),
 					 errhint("Create an index on the materialized view for efficient incremental maintenance.")));
 			return;
 		}
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 29cf18360e..3721774a9b 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -81,6 +81,32 @@ typedef struct
 
 #define MV_INIT_QUERYHASHSIZE	16
 
+/* MV query type codes */
+#define MV_PLAN_RECALC			1
+#define MV_PLAN_SET_VALUE		2
+
+/*
+ * MI_QueryKey
+ *
+ * The key identifying a prepared SPI plan in our query hashtable
+ */
+typedef struct MV_QueryKey
+{
+	Oid			matview_id;	/* OID of materialized view */
+	int32		query_type;	/* query type ID, see MV_PLAN_XXX above */
+} MV_QueryKey;
+
+/*
+ * MV_QueryHashEntry
+ *
+ * Hash entry for cached plans used to maintain materialized views.
+ */
+typedef struct MV_QueryHashEntry
+{
+	MV_QueryKey key;
+	SPIPlanPtr	plan;
+} MV_QueryHashEntry;
+
 /*
  * MV_TriggerHashEntry
  *
@@ -117,8 +143,16 @@ typedef struct MV_TriggerTable
 	RangeTblEntry *original_rte;	/* the original RTE saved before rewriting query */
 } MV_TriggerTable;
 
+static HTAB *mv_query_cache = NULL;
 static HTAB *mv_trigger_info = NULL;
 
+/* kind of IVM operation for the view */
+typedef enum
+{
+	IVM_ADD,
+	IVM_SUB
+} IvmOp;
+
 /* ENR name for materialized view delta */
 #define NEW_DELTA_ENRNAME "new_delta"
 #define OLD_DELTA_ENRNAME "old_delta"
@@ -152,7 +186,7 @@ static RangeTblEntry *get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *tabl
 				 QueryEnvironment *queryEnv);
 static RangeTblEntry *union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
 		   QueryEnvironment *queryEnv);
-static Query *rewrite_query_for_distinct(Query *query, ParseState *pstate);
+static Query *rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate);
 
 static void calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
 			DestReceiver *dest_old, DestReceiver *dest_new,
@@ -163,19 +197,48 @@ static Query *rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *
 static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
 			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
 			Query *query, bool use_count, char *count_colname);
+static void append_set_clause_for_count(const char *resname, StringInfo buf_old,
+							StringInfo buf_new,StringInfo aggs_list);
+static void append_set_clause_for_sum(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list);
+static void append_set_clause_for_avg(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list,
+						  const char *aggtype);
+static void append_set_clause_for_minmax(const char *resname, StringInfo buf_old,
+							 StringInfo buf_new, StringInfo aggs_list,
+							 bool is_min);
+static char *get_operation_string(IvmOp op, const char *col, const char *arg1, const char *arg2,
+					 const char* count_col, const char *castType);
+static char *get_null_condition_string(IvmOp op, const char *arg1, const char *arg2,
+						  const char* count_col);
 static void apply_old_delta(const char *matviewname, const char *deltaname_old,
 				List *keys);
 static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
-				List *keys, const char *count_colname);
+				List *keys, StringInfo aggs_list, StringInfo aggs_set,
+				List *minmax_list, List *is_min_list,
+				const char *count_colname,
+				SPITupleTable **tuptable_recalc, uint64 *num_recalc);
 static void apply_new_delta(const char *matviewname, const char *deltaname_new,
 				StringInfo target_list);
 static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
-				List *keys, StringInfo target_list, const char* count_colname);
+				List *keys, StringInfo target_list, StringInfo aggs_set,
+				const char* count_colname);
 static char *get_matching_condition_string(List *keys);
+static char *get_returning_string(List *minmax_list, List *is_min_list, List *keys);
+static char *get_minmax_recalc_condition_string(List *minmax_list, List *is_min_list);
+static char *get_select_for_recalc_string(List *keys);
+static void recalc_and_set_values(SPITupleTable *tuptable_recalc, int64 num_tuples,
+					  List *namelist, List *keys, Relation matviewRel);
+static SPIPlanPtr get_plan_for_recalc(Oid matviewOid, List *namelist, List *keys, Oid *keyTypes);
+static SPIPlanPtr get_plan_for_set_values(Oid matviewOid, char *matviewname, List *namelist,
+						Oid *valTypes);
 static void generate_equal(StringInfo querybuf, Oid opttype,
 			   const char *leftop, const char *rightop);
 
 static void mv_InitHashTables(void);
+static SPIPlanPtr mv_FetchPreparedPlan(MV_QueryKey *key);
+static void mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan);
+static void mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type);
 static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry);
 
 static List *get_securityQuals(Oid relId, int rt_index, Query *query);
@@ -1447,8 +1510,8 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
 	rewritten = rewrite_query_for_preupdate_state(rewritten, entry->tables,
 												  entry->xid, entry->cid,
 												  pstate);
-	/* Rewrite for DISTINCT clause */
-	rewritten = rewrite_query_for_distinct(rewritten, pstate);
+	/* Rewrite for DISTINCT clause and aggregates functions */
+	rewritten = rewrite_query_for_distinct_and_aggregates(rewritten, pstate);
 
 	/* Create tuplestores to store view deltas */
 	if (entry->has_old)
@@ -1499,7 +1562,7 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
 
 			count_colname = pstrdup("__ivm_count__");
 
-			if (query->distinctClause)
+			if (query->hasAggs || query->distinctClause)
 				use_count = true;
 
 			/* calculate delta tables */
@@ -1861,17 +1924,34 @@ union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
 }
 
 /*
- * rewrite_query_for_distinct
+ * rewrite_query_for_distinct_and_aggregates
  *
- * Rewrite query for counting DISTINCT clause.
+ * Rewrite query for counting DISTINCT clause and aggregate functions.
  */
 static Query *
-rewrite_query_for_distinct(Query *query, ParseState *pstate)
+rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate)
 {
 	TargetEntry *tle_count;
 	FuncCall *fn;
 	Node *node;
 
+	/* For aggregate views */
+	if (query->hasAggs)
+	{
+		ListCell *lc;
+		List *aggs = NIL;
+		AttrNumber next_resno = list_length(query->targetList) + 1;
+
+		foreach(lc, query->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+			if (IsA(tle->expr, Aggref))
+				makeIvmAggColumn(pstate, (Aggref *)tle->expr, tle->resname, &next_resno, &aggs);
+		}
+		query->targetList = list_concat(query->targetList, aggs);
+	}
+
 	/* Add count(*) for counting distinct tuples in views */
 	fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
 	fn->agg_star = true;
@@ -1940,6 +2020,8 @@ rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte
 	return query;
 }
 
+#define IVM_colname(type, col) makeObjectName("__ivm_" type, col, "_")
+
 /*
  * apply_delta
  *
@@ -1953,11 +2035,16 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 {
 	StringInfoData querybuf;
 	StringInfoData target_list_buf;
+	StringInfo	aggs_list_buf = NULL;
+	StringInfo	aggs_set_old = NULL;
+	StringInfo	aggs_set_new = NULL;
 	Relation	matviewRel;
 	char	   *matviewname;
 	ListCell	*lc;
 	int			i;
 	List	   *keys = NIL;
+	List	   *minmax_list = NIL;
+	List	   *is_min_list = NIL;
 
 
 	/*
@@ -1975,6 +2062,15 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 	initStringInfo(&querybuf);
 	initStringInfo(&target_list_buf);
 
+	if (query->hasAggs)
+	{
+		if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
+			aggs_set_old = makeStringInfo();
+		if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
+			aggs_set_new = makeStringInfo();
+		aggs_list_buf = makeStringInfo();
+	}
+
 	/* build string of target list */
 	for (i = 0; i < matviewRel->rd_att->natts; i++)
 	{
@@ -1998,7 +2094,65 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		if (tle->resjunk)
 			continue;
 
-		keys = lappend(keys, resname);
+		/*
+		 * For views without aggregates, all attributes are used as keys to identify a
+		 * tuple in a view.
+		 */
+		if (!query->hasAggs)
+			keys = lappend(keys, attr);
+
+		/* For views with aggregates, we need to build SET clause for updating aggregate
+		 * values. */
+		if (query->hasAggs && IsA(tle->expr, Aggref))
+		{
+			Aggref *aggref = (Aggref *) tle->expr;
+			const char *aggname = get_func_name(aggref->aggfnoid);
+
+			/*
+			 * We can use function names here because it is already checked if these
+			 * can be used in IMMV by its OID at the definition time.
+			 */
+
+			/* count */
+			if (!strcmp(aggname, "count"))
+				append_set_clause_for_count(resname, aggs_set_old, aggs_set_new, aggs_list_buf);
+
+			/* sum */
+			else if (!strcmp(aggname, "sum"))
+				append_set_clause_for_sum(resname, aggs_set_old, aggs_set_new, aggs_list_buf);
+
+			/* avg */
+			else if (!strcmp(aggname, "avg"))
+				append_set_clause_for_avg(resname, aggs_set_old, aggs_set_new, aggs_list_buf,
+										  format_type_be(aggref->aggtype));
+
+			/* min/max */
+			else if (!strcmp(aggname, "min") || !strcmp(aggname, "max"))
+			{
+				bool	is_min = (!strcmp(aggname, "min"));
+
+				append_set_clause_for_minmax(resname, aggs_set_old, aggs_set_new, aggs_list_buf, is_min);
+
+				/* make a resname list of min and max aggregates */
+				minmax_list = lappend(minmax_list, resname);
+				is_min_list = lappend_int(is_min_list, is_min);
+			}
+			else
+				elog(ERROR, "unsupported aggregate function: %s", aggname);
+		}
+	}
+
+	/* If we have GROUP BY clause, we use its entries as keys. */
+	if (query->hasAggs && query->groupClause)
+	{
+		foreach (lc, query->groupClause)
+		{
+			SortGroupClause *sgcl = (SortGroupClause *) lfirst(lc);
+			TargetEntry		*tle = get_sortgroupclause_tle(sgcl, query->targetList);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+
+			keys = lappend(keys, attr);
+		}
 	}
 
 	/* Start maintaining the materialized view. */
@@ -2012,6 +2166,8 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 	if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
 	{
 		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		SPITupleTable  *tuptable_recalc = NULL;
+		uint64			num_recalc;
 		int				rc;
 
 		/* convert tuplestores to ENR, and register for SPI */
@@ -2029,10 +2185,19 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		if (use_count)
 			/* apply old delta and get rows to be recalculated */
 			apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME,
-									   keys, count_colname);
+									   keys, aggs_list_buf, aggs_set_old,
+									   minmax_list, is_min_list,
+									   count_colname, &tuptable_recalc, &num_recalc);
 		else
 			apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys);
 
+		/*
+		 * If we have min or max, we might have to recalculate aggregate values from base tables
+		 * on some tuples. TIDs and keys such tuples are returned as a result of the above query.
+		 */
+		if (minmax_list && tuptable_recalc)
+			recalc_and_set_values(tuptable_recalc, num_recalc, minmax_list, keys, matviewRel);
+
 	}
 	/* For tuple insertion */
 	if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
@@ -2055,7 +2220,7 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		/* apply new delta */
 		if (use_count)
 			apply_new_delta_with_count(matviewname, NEW_DELTA_ENRNAME,
-								keys, &target_list_buf, count_colname);
+								keys, aggs_set_new, &target_list_buf, count_colname);
 		else
 			apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf);
 	}
@@ -2070,49 +2235,410 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		elog(ERROR, "SPI_finish failed");
 }
 
+/*
+ * append_set_clause_for_count
+ *
+ * Append SET clause string for count aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_count(const char *resname, StringInfo buf_old,
+							StringInfo buf_new,StringInfo aggs_list)
+{
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* resname = mv.resname - t.resname */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, resname, "mv", "t", NULL, NULL));
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* resname = mv.resname + diff.resname */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, resname, "mv", "diff", NULL, NULL));
+	}
+
+	appendStringInfo(aggs_list, ", %s",
+		quote_qualified_identifier("diff", resname)
+	);
+}
+
+/*
+ * append_set_clause_for_sum
+ *
+ * Append SET clause string for sum aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_sum(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list)
+{
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* sum = mv.sum - t.sum */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, resname, "mv", "t", count_col, NULL)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* sum = mv.sum + diff.sum */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, resname, "mv", "diff", count_col, NULL)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * append_set_clause_for_avg
+ *
+ * Append SET clause string for avg aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_avg(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list,
+						  const char *aggtype)
+{
+	char *sum_col = IVM_colname("sum", resname);
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* avg = (mv.sum - t.sum)::aggtype / (mv.count - t.count) */
+		appendStringInfo(buf_old,
+			", %s = %s OPERATOR(pg_catalog./) %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, aggtype),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+		/* sum = mv.sum - t.sum */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, sum_col),
+			get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, NULL)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* avg = (mv.sum + diff.sum)::aggtype / (mv.count + diff.count) */
+		appendStringInfo(buf_new,
+			", %s = %s OPERATOR(pg_catalog./) %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, aggtype),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+		/* sum = mv.sum + diff.sum */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, sum_col),
+			get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, NULL)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("sum", resname)),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * append_set_clause_for_minmax
+ *
+ * Append SET clause string for min or max aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ * is_min is true if this is min, false if not.
+ */
+static void
+append_set_clause_for_minmax(const char *resname, StringInfo buf_old,
+							 StringInfo buf_new, StringInfo aggs_list,
+							 bool is_min)
+{
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/*
+		 * If the new value doesn't became NULL then use the value remaining
+		 * in the view although this will be recomputated afterwords.
+		 */
+		appendStringInfo(buf_old,
+			", %s = CASE WHEN %s THEN NULL ELSE %s END",
+			quote_qualified_identifier(NULL, resname),
+			get_null_condition_string(IVM_SUB, "mv", "t", count_col),
+			quote_qualified_identifier("mv", resname)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/*
+		 * min = LEAST(mv.min, diff.min)
+		 * max = GREATEST(mv.max, diff.max)
+		 */
+		appendStringInfo(buf_new,
+			", %s = CASE WHEN %s THEN NULL ELSE %s(%s,%s) END",
+			quote_qualified_identifier(NULL, resname),
+			get_null_condition_string(IVM_ADD, "mv", "diff", count_col),
+
+			is_min ? "LEAST" : "GREATEST",
+			quote_qualified_identifier("mv", resname),
+			quote_qualified_identifier("diff", resname)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * get_operation_string
+ *
+ * Build a string to calculate the new aggregate values.
+ */
+static char *
+get_operation_string(IvmOp op, const char *col, const char *arg1, const char *arg2,
+					 const char* count_col, const char *castType)
+{
+	StringInfoData buf;
+	StringInfoData castString;
+	char   *col1 = quote_qualified_identifier(arg1, col);
+	char   *col2 = quote_qualified_identifier(arg2, col);
+	char	op_char = (op == IVM_SUB ? '-' : '+');
+
+	initStringInfo(&buf);
+	initStringInfo(&castString);
+
+	if (castType)
+		appendStringInfo(&castString, "::%s", castType);
+
+	if (!count_col)
+	{
+		/*
+		 * If the attributes don't have count columns then calc the result
+		 * by using the operator simply.
+		 */
+		appendStringInfo(&buf, "(%s OPERATOR(pg_catalog.%c) %s)%s",
+			col1, op_char, col2, castString.data);
+	}
+	else
+	{
+		/*
+		 * If the attributes have count columns then consider the condition
+		 * where the result becomes NULL.
+		 */
+		char *null_cond = get_null_condition_string(op, arg1, arg2, count_col);
+
+		appendStringInfo(&buf,
+			"(CASE WHEN %s THEN NULL "
+				"WHEN %s IS NULL THEN %s "
+				"WHEN %s IS NULL THEN %s "
+				"ELSE (%s OPERATOR(pg_catalog.%c) %s)%s END)",
+			null_cond,
+			col1, col2,
+			col2, col1,
+			col1, op_char, col2, castString.data
+		);
+	}
+
+	return buf.data;
+}
+
+/*
+ * get_null_condition_string
+ *
+ * Build a predicate string for CASE clause to check if an aggregate value
+ * will became NULL after the given operation is applied.
+ */
+static char *
+get_null_condition_string(IvmOp op, const char *arg1, const char *arg2,
+						  const char* count_col)
+{
+	StringInfoData null_cond;
+	initStringInfo(&null_cond);
+
+	switch (op)
+	{
+		case IVM_ADD:
+			appendStringInfo(&null_cond,
+				"%s OPERATOR(pg_catalog.=) 0 AND %s OPERATOR(pg_catalog.=) 0",
+				quote_qualified_identifier(arg1, count_col),
+				quote_qualified_identifier(arg2, count_col)
+			);
+			break;
+		case IVM_SUB:
+			appendStringInfo(&null_cond,
+				"%s OPERATOR(pg_catalog.=) %s",
+				quote_qualified_identifier(arg1, count_col),
+				quote_qualified_identifier(arg2, count_col)
+			);
+			break;
+		default:
+			elog(ERROR,"unknown operation");
+	}
+
+	return null_cond.data;
+}
+
+
 /*
  * apply_old_delta_with_count
  *
  * Execute a query for applying a delta table given by deltname_old
  * which contains tuples to be deleted from to a materialized view given by
  * matviewname.  This is used when counting is required, that is, the view
- * has aggregate or distinct.
+ * has aggregate or distinct. Also, when a table in EXISTS sub queries
+ * is modified.
+ *
+ * If the view desn't have aggregates or has GROUP BY, this requires a keys
+ * list to identify a tuple in the view. If the view has aggregates, this
+ * requires strings representing resnames of aggregates and SET clause for
+ * updating aggregate values.
+ *
+ * If the view has min or max aggregate, this requires a list of resnames of
+ * min/max aggregates and a list of boolean which represents which entries in
+ * minmax_list is min. These are necessary to check if we need to recalculate
+ * min or max aggregate values. In this case, this query returns TID and keys
+ * of tuples which need to be recalculated.  This result and the number of rows
+ * are stored in tuptables and num_recalc repectedly.
+ *
  */
 static void
 apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
-				List *keys, const char *count_colname)
+				List *keys, StringInfo aggs_list, StringInfo aggs_set,
+				List *minmax_list, List *is_min_list,
+				const char *count_colname,
+				SPITupleTable **tuptable_recalc, uint64 *num_recalc)
 {
 	StringInfoData	querybuf;
 	char   *match_cond;
+	char   *updt_returning = "";
+	char   *select_for_recalc = "SELECT";
+	bool	agg_without_groupby = (list_length(keys) == 0);
+
+	Assert(tuptable_recalc != NULL);
+	Assert(num_recalc != NULL);
 
 	/* build WHERE condition for searching tuples to be deleted */
 	match_cond = get_matching_condition_string(keys);
 
+	/*
+	 * We need a special RETURNING clause and SELECT statement for min/max to
+	 * check which tuple needs re-calculation from base tables.
+	 */
+	if (minmax_list)
+	{
+		updt_returning = get_returning_string(minmax_list, is_min_list, keys);
+		select_for_recalc = get_select_for_recalc_string(keys);
+	}
+
 	/* Search for matching tuples from the view and update or delete if found. */
 	initStringInfo(&querybuf);
 	appendStringInfo(&querybuf,
 					"WITH t AS ("			/* collecting tid of target tuples in the view */
 						"SELECT diff.%s, "			/* count column */
-								"(diff.%s OPERATOR(pg_catalog.=) mv.%s) AS for_dlt, "
+								"(diff.%s OPERATOR(pg_catalog.=) mv.%s AND %s) AS for_dlt, "
 								"mv.ctid "
+								"%s "				/* aggregate columns */
 						"FROM %s AS mv, %s AS diff "
 						"WHERE %s"					/* tuple matching condition */
 					"), updt AS ("			/* update a tuple if this is not to be deleted */
 						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.-) t.%s "
+											"%s"	/* SET clauses for aggregates */
 						"FROM t WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND NOT for_dlt "
+						"%s"						/* RETURNING clause for recalc infomation */
 					"), dlt AS ("			/* delete a tuple if this is to be deleted */
 						"DELETE FROM %s AS mv USING t "
 						"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt"
-					")",
+					") %s",							/* SELECT returning which tuples need to be recalculated */
 					count_colname,
-					count_colname, count_colname,
+					count_colname, count_colname, (agg_without_groupby ? "false" : "true"),
+					(aggs_list != NULL ? aggs_list->data : ""),
 					matviewname, deltaname_old,
 					match_cond,
 					matviewname, count_colname, count_colname, count_colname,
-					matviewname);
+					(aggs_set != NULL ? aggs_set->data : ""),
+					updt_returning,
+					matviewname,
+					select_for_recalc);
 
-	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_SELECT)
 		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+
+
+	/* Return tuples to be recalculated. */
+	if (minmax_list)
+	{
+		*tuptable_recalc = SPI_tuptable;
+		*num_recalc = SPI_processed;
+	}
+	else
+	{
+		*tuptable_recalc = NULL;
+		*num_recalc = 0;
+	}
 }
 
 /*
@@ -2172,10 +2698,15 @@ apply_old_delta(const char *matviewname, const char *deltaname_old,
  * matviewname.  This is used when counting is required, that is, the view
  * has aggregate or distinct. Also, when a table in EXISTS sub queries
  * is modified.
+ *
+ * If the view desn't have aggregates or has GROUP BY, this requires a keys
+ * list to identify a tuple in the view. If the view has aggregates, this
+ * requires strings representing SET clause for updating aggregate values.
  */
 static void
 apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
-				List *keys, StringInfo target_list, const char* count_colname)
+				List *keys, StringInfo aggs_set, StringInfo target_list,
+				const char* count_colname)
 {
 	StringInfoData	querybuf;
 	StringInfoData	returning_keys;
@@ -2206,6 +2737,7 @@ apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
 	appendStringInfo(&querybuf,
 					"WITH updt AS ("		/* update a tuple if this exists in the view */
 						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.+) diff.%s "
+											"%s "	/* SET clauses for aggregates */
 						"FROM %s AS diff "
 						"WHERE %s "					/* tuple matching condition */
 						"RETURNING %s"				/* returning keys of updated tuples */
@@ -2213,6 +2745,7 @@ apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
 						"SELECT %s FROM %s AS diff "
 						"WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE %s);",
 					matviewname, count_colname, count_colname, count_colname,
+					(aggs_set != NULL ? aggs_set->data : ""),
 					deltaname_new,
 					match_cond,
 					returning_keys.data,
@@ -2287,6 +2820,349 @@ get_matching_condition_string(List *keys)
 	return match_cond.data;
 }
 
+/*
+ * get_returning_string
+ *
+ * Build a string for RETURNING clause of UPDATE used in apply_old_delta_with_count.
+ * This clause returns ctid and a boolean value that indicates if we need to
+ * recalculate min or max value, for each updated row.
+ */
+static char *
+get_returning_string(List *minmax_list, List *is_min_list, List *keys)
+{
+	StringInfoData returning;
+	char		*recalc_cond;
+	ListCell	*lc;
+
+	Assert(minmax_list != NIL && is_min_list != NIL);
+	recalc_cond = get_minmax_recalc_condition_string(minmax_list, is_min_list);
+
+	initStringInfo(&returning);
+
+	appendStringInfo(&returning, "RETURNING mv.ctid AS tid, (%s) AS recalc", recalc_cond);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char *resname = NameStr(attr->attname);
+		appendStringInfo(&returning, ", %s", quote_qualified_identifier("mv", resname));
+	}
+
+	return returning.data;
+}
+
+/*
+ * get_minmax_recalc_condition_string
+ *
+ * Build a predicate string for checking if any min/max aggregate
+ * value needs to be recalculated.
+ */
+static char *
+get_minmax_recalc_condition_string(List *minmax_list, List *is_min_list)
+{
+	StringInfoData recalc_cond;
+	ListCell	*lc1, *lc2;
+
+	initStringInfo(&recalc_cond);
+
+	Assert (list_length(minmax_list) == list_length(is_min_list));
+
+	forboth (lc1, minmax_list, lc2, is_min_list)
+	{
+		char   *resname = (char *) lfirst(lc1);
+		bool	is_min = (bool) lfirst_int(lc2);
+		char   *op_str = (is_min ? ">=" : "<=");
+
+		appendStringInfo(&recalc_cond, "%s OPERATOR(pg_catalog.%s) %s",
+			quote_qualified_identifier("mv", resname),
+			op_str,
+			quote_qualified_identifier("t", resname)
+		);
+
+		if (lnext(minmax_list, lc1))
+			appendStringInfo(&recalc_cond, " OR ");
+	}
+
+	return recalc_cond.data;
+}
+
+/*
+ * get_select_for_recalc_string
+ *
+ * Build a query to return tid and keys of tuples which need
+ * recalculation. This is used as the result of the query
+ * built by apply_old_delta.
+ */
+static char *
+get_select_for_recalc_string(List *keys)
+{
+	StringInfoData qry;
+	ListCell	*lc;
+
+	initStringInfo(&qry);
+
+	appendStringInfo(&qry, "SELECT tid");
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		appendStringInfo(&qry, ", %s", NameStr(attr->attname));
+	}
+
+	appendStringInfo(&qry, " FROM updt WHERE recalc");
+
+	return qry.data;
+}
+
+/*
+ * recalc_and_set_values
+ *
+ * Recalculate tuples in a materialized from base tables and update these.
+ * The tuples which needs recalculation are specified by keys, and resnames
+ * of columns to be updated are specified by namelist. TIDs and key values
+ * are given by tuples in tuptable_recalc. Its first attribute must be TID
+ * and key values must be following this.
+ */
+static void
+recalc_and_set_values(SPITupleTable *tuptable_recalc, int64 num_tuples,
+					  List *namelist, List *keys, Relation matviewRel)
+{
+	TupleDesc   tupdesc_recalc = tuptable_recalc->tupdesc;
+	Oid		   *keyTypes = NULL, *types = NULL;
+	char	   *keyNulls = NULL, *nulls = NULL;
+	Datum	   *keyVals = NULL, *vals = NULL;
+	int			num_vals = list_length(namelist);
+	int			num_keys = list_length(keys);
+	uint64      i;
+	Oid			matviewOid;
+	char	   *matviewname;
+
+	matviewOid = RelationGetRelid(matviewRel);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	/* If we have keys, initialize arrays for them. */
+	if (keys)
+	{
+		keyTypes = palloc(sizeof(Oid) * num_keys);
+		keyNulls = palloc(sizeof(char) * num_keys);
+		keyVals = palloc(sizeof(Datum) * num_keys);
+		/* a tuple contains keys to be recalculated and ctid to be updated*/
+		Assert(tupdesc_recalc->natts == num_keys + 1);
+
+		/* Types of key attributes  */
+		for (i = 0; i < num_keys; i++)
+			keyTypes[i] = TupleDescAttr(tupdesc_recalc, i + 1)->atttypid;
+	}
+
+	/* allocate memory for all attribute names and tid */
+	types = palloc(sizeof(Oid) * (num_vals + 1));
+	nulls = palloc(sizeof(char) * (num_vals + 1));
+	vals = palloc(sizeof(Datum) * (num_vals + 1));
+
+	/* For each tuple which needs recalculation */
+	for (i = 0; i < num_tuples; i++)
+	{
+		int j;
+		bool isnull;
+		SPIPlanPtr plan;
+		SPITupleTable *tuptable_newvals;
+		TupleDesc   tupdesc_newvals;
+
+		/* Set group key values as parameters if needed. */
+		if (keys)
+		{
+			for (j = 0; j < num_keys; j++)
+			{
+				keyVals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, j + 2, &isnull);
+				if (isnull)
+					keyNulls[j] = 'n';
+				else
+					keyNulls[j] = ' ';
+			}
+		}
+
+		/*
+		 * Get recalculated values from base tables. The result must be
+		 * only one tuple thich contains the new values for specified keys.
+		 */
+		plan = get_plan_for_recalc(matviewOid, namelist, keys, keyTypes);
+		if (SPI_execute_plan(plan, keyVals, keyNulls, false, 0) != SPI_OK_SELECT)
+			elog(ERROR, "SPI_execute_plan");
+		if (SPI_processed != 1)
+			elog(ERROR, "SPI_execute_plan returned zero or more than one rows");
+
+		tuptable_newvals = SPI_tuptable;
+		tupdesc_newvals = tuptable_newvals->tupdesc;
+
+		Assert(tupdesc_newvals->natts == num_vals);
+
+		/* Set the new values as parameters */
+		for (j = 0; j < tupdesc_newvals->natts; j++)
+		{
+			if (i == 0)
+				types[j] = TupleDescAttr(tupdesc_newvals, j)->atttypid;
+
+			vals[j] = SPI_getbinval(tuptable_newvals->vals[0], tupdesc_newvals, j + 1, &isnull);
+			if (isnull)
+				nulls[j] = 'n';
+			else
+				nulls[j] = ' ';
+		}
+		/* Set TID of the view tuple to be updated as a parameter */
+		types[j] = TIDOID;
+		vals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, 1, &isnull);
+		nulls[j] = ' ';
+
+		/* Update the view tuple to the new values */
+		plan = get_plan_for_set_values(matviewOid, matviewname, namelist, types);
+		if (SPI_execute_plan(plan, vals, nulls, false, 0) != SPI_OK_UPDATE)
+			elog(ERROR, "SPI_execute_plan");
+	}
+}
+
+
+/*
+ * get_plan_for_recalc
+ *
+ * Create or fetch a plan for recalculating value in the view's target list
+ * from base tables using the definition query of materialized view specified
+ * by matviewOid. namelist is a list of resnames of values to be recalculated.
+ *
+ * keys is a list of keys to identify tuples to be recalculated if this is not
+ * empty. KeyTypes is an array of types of keys.
+ */
+static SPIPlanPtr
+get_plan_for_recalc(Oid matviewOid, List *namelist, List *keys, Oid *keyTypes)
+{
+	MV_QueryKey hash_key;
+	SPIPlanPtr	plan;
+
+	/* Fetch or prepare a saved plan for the recalculation */
+	mv_BuildQueryKey(&hash_key, matviewOid, MV_PLAN_RECALC);
+	if ((plan = mv_FetchPreparedPlan(&hash_key)) == NULL)
+	{
+		ListCell	   *lc;
+		StringInfoData	str;
+		char   *viewdef;
+
+		/* get view definition of matview */
+		viewdef = text_to_cstring((text *) DatumGetPointer(
+					DirectFunctionCall1(pg_get_viewdef, ObjectIdGetDatum(matviewOid))));
+		/* get rid of trailing semi-colon */
+		viewdef[strlen(viewdef)-1] = '\0';
+
+		/*
+		 * Build a query string for recalculating values. This is like
+		 *
+		 *  SELECT x1, x2, x3, ... FROM ( ... view definition query ...) mv
+		 *   WHERE (key1, key2, ...) = ($1, $2, ...);
+		 */
+
+		initStringInfo(&str);
+		appendStringInfo(&str, "SELECT ");
+		foreach (lc, namelist)
+		{
+			appendStringInfo(&str, "%s", (char *) lfirst(lc));
+			if (lnext(namelist, lc))
+				appendStringInfoString(&str, ", ");
+		}
+		appendStringInfo(&str, " FROM (%s) mv", viewdef);
+
+		if (keys)
+		{
+			int		i = 1;
+			char	paramname[16];
+
+			appendStringInfo(&str, " WHERE (");
+			foreach (lc, keys)
+			{
+				Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+				char   *resname = NameStr(attr->attname);
+				Oid		typid = attr->atttypid;
+
+				sprintf(paramname, "$%d", i);
+				appendStringInfo(&str, "(");
+				generate_equal(&str, typid, resname, paramname);
+				appendStringInfo(&str, " OR (%s IS NULL AND %s IS NULL))",
+								 resname, paramname);
+
+				if (lnext(keys, lc))
+					appendStringInfoString(&str, " AND ");
+				i++;
+			}
+			appendStringInfo(&str, ")");
+		}
+		else
+			keyTypes = NULL;
+
+		plan = SPI_prepare(str.data, list_length(keys), keyTypes);
+		if (plan == NULL)
+			elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), str.data);
+
+		SPI_keepplan(plan);
+		mv_HashPreparedPlan(&hash_key, plan);
+	}
+
+	return plan;
+}
+
+/*
+ * get_plan_for_set_values
+ *
+ * Create or fetch a plan for applying new values calculated by
+ * get_plan_for_recalc to a materialized view specified by matviewOid.
+ * matviewname is the name of the view.  namelist is a list of resnames
+ * of attributes to be updated, and valTypes is an array of types of the
+ * values.
+ */
+static SPIPlanPtr
+get_plan_for_set_values(Oid matviewOid, char *matviewname, List *namelist,
+						Oid *valTypes)
+{
+	MV_QueryKey	key;
+	SPIPlanPtr	plan;
+
+	/* Fetch or prepare a saved plan for the real check */
+	mv_BuildQueryKey(&key, matviewOid, MV_PLAN_SET_VALUE);
+	if ((plan = mv_FetchPreparedPlan(&key)) == NULL)
+	{
+		ListCell	  *lc;
+		StringInfoData str;
+		int		i;
+
+		/*
+		 * Build a query string for applying min/max values. This is like
+		 *
+		 *  UPDATE matviewname AS mv
+		 *   SET (x1, x2, x3, x4) = ($1, $2, $3, $4)
+		 *   WHERE ctid = $5;
+		 */
+
+		initStringInfo(&str);
+		appendStringInfo(&str, "UPDATE %s AS mv SET (", matviewname);
+		foreach (lc, namelist)
+		{
+			appendStringInfo(&str, "%s", (char *) lfirst(lc));
+			if (lnext(namelist, lc))
+				appendStringInfoString(&str, ", ");
+		}
+		appendStringInfo(&str, ") = ROW(");
+
+		for (i = 1; i <= list_length(namelist); i++)
+			appendStringInfo(&str, "%s$%d", (i==1 ? "" : ", "), i);
+
+		appendStringInfo(&str, ") WHERE ctid OPERATOR(pg_catalog.=) $%d", i);
+
+		plan = SPI_prepare(str.data, list_length(namelist) + 1, valTypes);
+		if (plan == NULL)
+			elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), str.data);
+
+		SPI_keepplan(plan);
+		mv_HashPreparedPlan(&key, plan);
+	}
+
+	return plan;
+}
+
 /*
  * generate_equals
  *
@@ -2320,6 +3196,13 @@ mv_InitHashTables(void)
 {
 	HASHCTL		ctl;
 
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(MV_QueryKey);
+	ctl.entrysize = sizeof(MV_QueryHashEntry);
+	mv_query_cache = hash_create("MV query cache",
+								 MV_INIT_QUERYHASHSIZE,
+								 &ctl, HASH_ELEM | HASH_BLOBS);
+
 	memset(&ctl, 0, sizeof(ctl));
 	ctl.keysize = sizeof(Oid);
 	ctl.entrysize = sizeof(MV_TriggerHashEntry);
@@ -2328,6 +3211,99 @@ mv_InitHashTables(void)
 								 &ctl, HASH_ELEM | HASH_BLOBS);
 }
 
+/*
+ * mv_FetchPreparedPlan
+ */
+static SPIPlanPtr
+mv_FetchPreparedPlan(MV_QueryKey *key)
+{
+	MV_QueryHashEntry *entry;
+	SPIPlanPtr	plan;
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_query_cache)
+		mv_InitHashTables();
+
+	/*
+	 * Lookup for the key
+	 */
+	entry = (MV_QueryHashEntry *) hash_search(mv_query_cache,
+											  (void *) key,
+											  HASH_FIND, NULL);
+	if (entry == NULL)
+		return NULL;
+
+	/*
+	 * Check whether the plan is still valid.  If it isn't, we don't want to
+	 * simply rely on plancache.c to regenerate it; rather we should start
+	 * from scratch and rebuild the query text too.  This is to cover cases
+	 * such as table/column renames.  We depend on the plancache machinery to
+	 * detect possible invalidations, though.
+	 *
+	 * CAUTION: this check is only trustworthy if the caller has already
+	 * locked both materialized views and base tables.
+	 */
+	plan = entry->plan;
+	if (plan && SPI_plan_is_valid(plan))
+		return plan;
+
+	/*
+	 * Otherwise we might as well flush the cached plan now, to free a little
+	 * memory space before we make a new one.
+	 */
+	entry->plan = NULL;
+	if (plan)
+		SPI_freeplan(plan);
+
+	return NULL;
+}
+
+/*
+ * mv_HashPreparedPlan
+ *
+ * Add another plan to our private SPI query plan hashtable.
+ */
+static void
+mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan)
+{
+	MV_QueryHashEntry *entry;
+	bool		found;
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_query_cache)
+		mv_InitHashTables();
+
+	/*
+	 * Add the new plan.  We might be overwriting an entry previously found
+	 * invalid by mv_FetchPreparedPlan.
+	 */
+	entry = (MV_QueryHashEntry *) hash_search(mv_query_cache,
+											  (void *) key,
+											  HASH_ENTER, &found);
+	Assert(!found || entry->plan == NULL);
+	entry->plan = plan;
+}
+
+/*
+ * mv_BuildQueryKey
+ *
+ * Construct a hashtable key for a prepared SPI plan for IVM.
+ */
+static void
+mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type)
+{
+	/*
+	 * We assume struct MV_QueryKey contains no padding bytes, else we'd need
+	 * to use memset to clear them.
+	 */
+	key->matview_id = matview_id;
+	key->query_type = query_type;
+}
+
 /*
  * AtAbort_IVM
  *
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index c369b3ba5e..abcc31023c 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -30,6 +30,7 @@ extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_cr
 extern void CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create);
 
 extern Query *rewriteQueryForIMMV(Query *query, List *colNames);
+extern void makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber *next_resno, List **aggs);
 
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
 
-- 
2.17.1

v26-0007-Add-Incremental-View-Maintenance-support.patchtext/x-diff; name=v26-0007-Add-Incremental-View-Maintenance-support.patchDownload
From 37420f6eab1a5b0644a4c4d4bef63b621b14ce59 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Tue, 22 Dec 2020 18:40:18 +0900
Subject: [PATCH v26 07/10] Add Incremental View Maintenance support

In this implementation, AFTER triggers are used to collect
tuplestores containing transition table contents. When multiple tables
are changed, multiple AFTER triggers are invoked, then the final AFTER
trigger performs actual update of the matview. In addition, BEFORE
triggers are also used to handle global information for view
maintenance. To calculate view deltas, we need both pre-state and
post-state of base tables. Post-update states are available in AFTER
trigger, and pre-update states can be calculated by filtering inserted
tuples using cmin/xmin system columns, and append deleted tuples which
are contained in an old transition table.

This patch also allows self-join, simultaneous updates of more than
one base table, and multiple updates of the same base table.

Incrementally Maintainable Materialized Views (IMMV) can contain
duplicated tuples. Also, DISTINCT clause is supported. When IMMV is
created with DISTINCT, multiplicity of tuples is counted and stored
in  "__ivm_count__" column, which is a hidden column of IMMV.
The value in __ivm_count__ is updated when IMMV is maintained
incrementally. A tuple in IMMV can be removed if and only if the
count becomes zero.
---
 src/backend/access/transam/xact.c   |    5 +
 src/backend/commands/createas.c     |  746 +++++++++++++
 src/backend/commands/indexcmds.c    |   40 +
 src/backend/commands/matview.c      | 1555 ++++++++++++++++++++++++++-
 src/backend/commands/tablecmds.c    |    9 +
 src/backend/nodes/copyfuncs.c       |    1 +
 src/backend/nodes/equalfuncs.c      |    1 +
 src/backend/nodes/outfuncs.c        |    1 +
 src/backend/nodes/readfuncs.c       |    1 +
 src/backend/parser/parse_relation.c |   18 +-
 src/backend/rewrite/rewriteDefine.c |    3 +-
 src/include/catalog/pg_proc.dat     |    8 +
 src/include/commands/createas.h     |    6 +
 src/include/commands/matview.h      |    8 +
 src/include/nodes/parsenodes.h      |    2 +
 15 files changed, 2364 insertions(+), 40 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 8964ddf3eb..5d9ab6b1f9 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -36,6 +36,7 @@
 #include "catalog/pg_enum.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/matview.h"
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "common/pg_prng.h"
@@ -2777,6 +2778,7 @@ AbortTransaction(void)
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
 	AtAbort_Twophase();
+	AtAbort_IVM();
 
 	/*
 	 * Advertise the fact that we aborted in pg_xact (assuming that we got as
@@ -5017,6 +5019,9 @@ AbortSubTransaction(void)
 	AbortBufferIO();
 	UnlockBuffers();
 
+	/* Clean up hash entries for incremental view maintenance */
+	AtAbort_IVM();
+
 	/* Reset WAL record construction state */
 	XLogResetInsertion();
 
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 9abbb6b555..1fbcede7aa 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -32,24 +32,41 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
 #include "catalog/toasting.h"
 #include "commands/createas.h"
+#include "commands/defrem.h"
 #include "commands/matview.h"
 #include "commands/prepare.h"
 #include "commands/tablecmds.h"
+#include "commands/tablespace.h"
+#include "commands/trigger.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "optimizer/clauses.h"
+#include "optimizer/optimizer.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_func.h"
+#include "parser/parse_type.h"
 #include "rewrite/rewriteHandler.h"
+#include "rewrite/rewriteManip.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
+#include "utils/syscache.h"
 
 typedef struct
 {
@@ -73,6 +90,13 @@ static bool intorel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void intorel_shutdown(DestReceiver *self);
 static void intorel_destroy(DestReceiver *self);
 
+static void CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid,
+									 Relids *relids, bool ex_lock);
+static void CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock);
+static void check_ivm_restriction(Node *node);
+static bool check_ivm_restriction_walker(Node *node, void *context);
+static Bitmapset *get_primary_key_attnos_from_query(Query *qry, List **constraintList, bool is_create);
+static Bitmapset *get_primary_key_attnos_from_query(Query *query, List **constraintList, bool is_create);
 
 /*
  * create_ctas_internal
@@ -108,6 +132,8 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	create->oncommit = into->onCommit;
 	create->tablespacename = into->tableSpaceName;
 	create->if_not_exists = false;
+	/* Using Materialized view only */
+	create->ivm = into->ivm;
 	create->accessMethod = into->accessMethod;
 
 	/*
@@ -282,6 +308,21 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 		save_nestlevel = NewGUCNestLevel();
 	}
 
+	if (is_matview && into->ivm)
+	{
+		/* check if the query is supported in IMMV definition */
+		if (contain_mutable_functions((Node *) query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("mutable function is not supported on incrementally maintainable materialized view"),
+					 errhint("functions must be marked IMMUTABLE")));
+
+		check_ivm_restriction((Node *) query);
+
+		/* For IMMV, we need to rewrite matview query */
+		query = rewriteQueryForIMMV(query, into->colNames);
+	}
+
 	if (into->skipData)
 	{
 		/*
@@ -358,11 +399,74 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 
 		/* Restore userid and security context */
 		SetUserIdAndSecContext(save_userid, save_sec_context);
+
+		if (into->ivm)
+		{
+			Oid matviewOid = address.objectId;
+			Relation matviewRel = table_open(matviewOid, NoLock);
+
+			/*
+			 * Mark relisivm field, if it's a matview and into->ivm is true.
+			 */
+			SetMatViewIVMState(matviewRel, true);
+
+			if (!into->skipData)
+			{
+				/* Create an index on incremental maintainable materialized view, if possible */
+				CreateIndexOnIMMV((Query *) into->viewQuery, matviewRel, true);
+
+				/* Create triggers on incremental maintainable materialized view */
+				CreateIvmTriggersOnBaseTables((Query *) into->viewQuery, matviewOid, true);
+			}
+			table_close(matviewRel, NoLock);
+		}
 	}
 
 	return address;
 }
 
+/*
+ * rewriteQueryForIMMV -- rewrite view definition query for IMMV
+ *
+ * count(*) is added for counting distinct tuples in views.
+ */
+Query *
+rewriteQueryForIMMV(Query *query, List *colNames)
+{
+	Query *rewritten;
+
+	TargetEntry *tle;
+	Node *node;
+	ParseState *pstate = make_parsestate(NULL);
+	FuncCall *fn;
+
+	rewritten = copyObject(query);
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	/*
+	 * Convert DISTINCT to GROUP BY and add count(*) for counting distinct
+	 * tuples in views.
+	 */
+	if (rewritten->distinctClause)
+	{
+		rewritten->groupClause = transformDistinctClause(NULL, &rewritten->targetList, rewritten->sortClause, false);
+
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+		fn->agg_star = true;
+
+		node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+		tle = makeTargetEntry((Expr *) node,
+								list_length(rewritten->targetList) + 1,
+								pstrdup("__ivm_count__"),
+								false);
+		rewritten->targetList = lappend(rewritten->targetList, tle);
+		rewritten->hasAggs = true;
+	}
+
+	return rewritten;
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -623,3 +727,645 @@ intorel_destroy(DestReceiver *self)
 {
 	pfree(self);
 }
+
+/*
+ * CreateIvmTriggersOnBaseTables -- create IVM triggers on all base tables
+ */
+void
+CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create)
+{
+	Relids	relids = NULL;
+	bool	ex_lock = false;
+	Index	first_rtindex = is_create ? 1 : PRS2_NEW_VARNO + 1;
+	RangeTblEntry *rte;
+
+	/* Immediately return if we don't have any base tables. */
+	if (list_length(qry->rtable) < first_rtindex)
+		return;
+
+	/*
+	 * If the view has more than one base tables, we need an exclusive lock
+	 * on the view so that the view would be maintained serially to avoid
+	 * the inconsistency that occurs when two base tables are modified in
+	 * concurrent transactions. However, if the view has only one table,
+	 * we can use a weaker lock.
+	 *
+	 * The type of lock should be determined here, because if we check the
+	 * view definition at maintenance time, we need to acquire a weaker lock,
+	 * and upgrading the lock level after this increases probability of
+	 * deadlock.
+	 */
+
+	rte = list_nth(qry->rtable, first_rtindex - 1);
+	if (list_length(qry->rtable) > first_rtindex ||
+		rte->rtekind != RTE_RELATION)
+		ex_lock = true;
+
+	CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *)qry, matviewOid, &relids, ex_lock);
+
+	bms_free(relids);
+}
+
+static void
+CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid,
+									 Relids *relids, bool ex_lock)
+{
+	if (node == NULL)
+		return;
+
+	/* This can recurse, so check for excessive recursion */
+	check_stack_depth();
+
+	switch (nodeTag(node))
+	{
+		case T_Query:
+			{
+				Query *query = (Query *) node;
+
+				CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *)query->jointree, matviewOid, relids, ex_lock);
+			}
+			break;
+
+		case T_RangeTblRef:
+			{
+				int			rti = ((RangeTblRef *) node)->rtindex;
+				RangeTblEntry *rte = rt_fetch(rti, qry->rtable);
+
+				if (rte->rtekind == RTE_RELATION && !bms_is_member(rte->relid, *relids))
+				{
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_BEFORE, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_BEFORE, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_BEFORE, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_AFTER, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_AFTER, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_AFTER, ex_lock);
+
+					*relids = bms_add_member(*relids, rte->relid);
+				}
+			}
+			break;
+
+		case T_FromExpr:
+			{
+				FromExpr   *f = (FromExpr *) node;
+				ListCell   *l;
+
+				foreach(l, f->fromlist)
+					CreateIvmTriggersOnBaseTablesRecurse(qry, lfirst(l), matviewOid, relids, ex_lock);
+			}
+			break;
+
+		case T_JoinExpr:
+			{
+				JoinExpr   *j = (JoinExpr *) node;
+
+				CreateIvmTriggersOnBaseTablesRecurse(qry, j->larg, matviewOid, relids, ex_lock);
+				CreateIvmTriggersOnBaseTablesRecurse(qry, j->rarg, matviewOid, relids, ex_lock);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
+	}
+}
+
+/*
+ * CreateIvmTrigger -- create IVM trigger on a base table
+ */
+static void
+CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock)
+{
+	ObjectAddress	refaddr;
+	ObjectAddress	address;
+	CreateTrigStmt *ivm_trigger;
+	List *transitionRels = NIL;
+
+	Assert(timing == TRIGGER_TYPE_BEFORE || timing == TRIGGER_TYPE_AFTER);
+
+	refaddr.classId = RelationRelationId;
+	refaddr.objectId = viewOid;
+	refaddr.objectSubId = 0;
+
+	ivm_trigger = makeNode(CreateTrigStmt);
+	ivm_trigger->relation = NULL;
+	ivm_trigger->row = false;
+
+	ivm_trigger->timing = timing;
+	ivm_trigger->events = type;
+
+	switch (type)
+	{
+		case TRIGGER_TYPE_INSERT:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_ins_before" : "IVM_trigger_ins_after");
+			break;
+		case TRIGGER_TYPE_DELETE:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_del_before" : "IVM_trigger_del_after");
+			break;
+		case TRIGGER_TYPE_UPDATE:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_upd_before" : "IVM_trigger_upd_after");
+			break;
+		default:
+			elog(ERROR, "unsupported trigger type");
+	}
+
+	if (timing == TRIGGER_TYPE_AFTER)
+	{
+		if (type == TRIGGER_TYPE_INSERT || type == TRIGGER_TYPE_UPDATE)
+		{
+			TriggerTransition *n = makeNode(TriggerTransition);
+			n->name = "__ivm_newtable";
+			n->isNew = true;
+			n->isTable = true;
+
+			transitionRels = lappend(transitionRels, n);
+		}
+		if (type == TRIGGER_TYPE_DELETE || type == TRIGGER_TYPE_UPDATE)
+		{
+			TriggerTransition *n = makeNode(TriggerTransition);
+			n->name = "__ivm_oldtable";
+			n->isNew = false;
+			n->isTable = true;
+
+			transitionRels = lappend(transitionRels, n);
+		}
+	}
+
+	ivm_trigger->funcname =
+		(timing == TRIGGER_TYPE_BEFORE ? SystemFuncName("IVM_immediate_before") : SystemFuncName("IVM_immediate_maintenance"));
+
+	ivm_trigger->columns = NIL;
+	ivm_trigger->transitionRels = transitionRels;
+	ivm_trigger->whenClause = NULL;
+	ivm_trigger->isconstraint = false;
+	ivm_trigger->deferrable = false;
+	ivm_trigger->initdeferred = false;
+	ivm_trigger->constrrel = NULL;
+	ivm_trigger->args = list_make2(
+		makeString(DatumGetPointer(DirectFunctionCall1(oidout, ObjectIdGetDatum(viewOid)))),
+		makeString(DatumGetPointer(DirectFunctionCall1(boolout, BoolGetDatum(ex_lock))))
+		);
+
+	address = CreateTrigger(ivm_trigger, NULL, relOid, InvalidOid, InvalidOid,
+						 InvalidOid, InvalidOid, InvalidOid, NULL, true, false);
+
+	recordDependencyOn(&address, &refaddr, DEPENDENCY_IMMV);
+
+	/* Make changes-so-far visible */
+	CommandCounterIncrement();
+}
+
+/*
+ * check_ivm_restriction --- look for specify nodes in the query tree
+ */
+static void
+check_ivm_restriction(Node *node)
+{
+	check_ivm_restriction_walker(node, NULL);
+}
+
+static bool
+check_ivm_restriction_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	/*
+	 * We currently don't support Sub-Query.
+	 */
+	if (IsA(node, SubPlan) || IsA(node, SubLink))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("subquery is not supported on incrementally maintainable materialized view")));
+
+	/* This can recurse, so check for excessive recursion */
+	check_stack_depth();
+
+	switch (nodeTag(node))
+	{
+		case T_Query:
+			{
+				Query *qry = (Query *)node;
+				ListCell   *lc;
+				List       *vars;
+
+				/* if contained CTE, return error */
+				if (qry->cteList != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("CTE is not supported on incrementally maintainable materialized view")));
+				if (qry->havingQual != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg(" HAVING clause is not supported on incrementally maintainable materialized view")));
+				if (qry->sortClause != NIL)	/* There is a possibility that we don't need to return an error */
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("ORDER BY clause is not supported on incrementally maintainable materialized view")));
+				if (qry->limitOffset != NULL || qry->limitCount != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("LIMIT/OFFSET clause is not supported on incrementally maintainable materialized view")));
+				if (qry->hasDistinctOn)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DISTINCT ON is not supported on incrementally maintainable materialized view")));
+				if (qry->hasWindowFuncs)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("window functions are not supported on incrementally maintainable materialized view")));
+				if (qry->groupingSets != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("GROUPING SETS, ROLLUP, or CUBE clauses is not supported on incrementally maintainable materialized view")));
+				if (qry->setOperations != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("UNION/INTERSECT/EXCEPT statements are not supported on incrementally maintainable materialized view")));
+				if (list_length(qry->targetList) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("empty target list is not supported on incrementally maintainable materialized view")));
+				if (qry->rowMarks != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("FOR UPDATE/SHARE clause is not supported on incrementally maintainable materialized view")));
+
+				/* system column restrictions */
+				vars = pull_vars_of_level((Node *) qry, 0);
+				foreach(lc, vars)
+				{
+					if (IsA(lfirst(lc), Var))
+					{
+						Var *var = (Var *) lfirst(lc);
+						/* if system column, return error */
+						if (var->varattno < 0)
+							ereport(ERROR,
+									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									 errmsg("system column is not supported on incrementally maintainable materialized view")));
+					}
+				}
+
+				/* restrictions for rtable */
+				foreach(lc, qry->rtable)
+				{
+					RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+					if (rte->subquery)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("subquery is not supported on incrementally maintainable materialized view")));
+
+					if (rte->tablesample != NULL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("TABLESAMPLE clause is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("partitioned table is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_RELATION && has_superclass(rte->relid))
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("partitions is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_RELATION && find_inheritance_children(rte->relid, NoLock) != NIL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("inheritance parent is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_FOREIGN_TABLE)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("foreign table is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_VIEW ||
+						rte->relkind == RELKIND_MATVIEW)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view")));
+
+					if (rte->rtekind == RTE_VALUES)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("VALUES is not supported on incrementally maintainable materialized view")));
+
+				}
+
+				query_tree_walker(qry, check_ivm_restriction_walker, NULL, QTW_IGNORE_RANGE_TABLE);
+
+				break;
+			}
+		case T_TargetEntry:
+			{
+				TargetEntry *tle = (TargetEntry *)node;
+				if (isIvmName(tle->resname))
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("column name %s is not supported on incrementally maintainable materialized view", tle->resname)));
+
+				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				break;
+			}
+		case T_JoinExpr:
+			{
+				JoinExpr *joinexpr = (JoinExpr *)node;
+
+				if (joinexpr->jointype > JOIN_INNER)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("OUTER JOIN is not supported on incrementally maintainable materialized view")));
+
+				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+			}
+			break;
+		default:
+			expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
+			break;
+	}
+	return false;
+}
+
+/*
+ * CreateIndexOnIMMV
+ *
+ * Create a unique index on incremental maintainable materialized view.
+ * If the view definition query has a GROUP BY clause, the index is created
+ * on the columns of GROUP BY expressions. Otherwise, if the view contains
+ * all primary key attritubes of its base tables in the target list, the index
+ * is created on these attritubes. In other cases, no index is created.
+ */
+void
+CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create)
+{
+	ListCell *lc;
+	IndexStmt  *index;
+	ObjectAddress address;
+	List *constraintList = NIL;
+	char		idxname[NAMEDATALEN];
+	List	   *indexoidlist = RelationGetIndexList(matviewRel);
+	ListCell   *indexoidscan;
+
+	snprintf(idxname, sizeof(idxname), "%s_index", RelationGetRelationName(matviewRel));
+
+	index = makeNode(IndexStmt);
+
+	/*
+	 * We consider null values not distinct to make sure that views with DISTINCT
+	 * or GROUP BY don't contain multiple NULL rows when NULL is inserted to
+	 * a base table concurrently.
+	 */
+	index->nulls_not_distinct = true;
+
+	index->unique = true;
+	index->primary = false;
+	index->isconstraint = false;
+	index->deferrable = false;
+	index->initdeferred = false;
+	index->idxname = idxname;
+	index->relation =
+		makeRangeVar(get_namespace_name(RelationGetNamespace(matviewRel)),
+					 pstrdup(RelationGetRelationName(matviewRel)),
+					 -1);
+	index->accessMethod = DEFAULT_INDEX_TYPE;
+	index->options = NIL;
+	index->tableSpace = get_tablespace_name(matviewRel->rd_rel->reltablespace);
+	index->whereClause = NULL;
+	index->indexParams = NIL;
+	index->indexIncludingParams = NIL;
+	index->excludeOpNames = NIL;
+	index->idxcomment = NULL;
+	index->indexOid = InvalidOid;
+	index->oldNode = InvalidOid;
+	index->oldCreateSubid = InvalidSubTransactionId;
+	index->oldFirstRelfilenodeSubid = InvalidSubTransactionId;
+	index->transformed = true;
+	index->concurrent = false;
+	index->if_not_exists = false;
+
+	if (query->distinctClause)
+	{
+		/* create unique constraint on all columns */
+		foreach(lc, query->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+			IndexElem  *iparam;
+
+			iparam = makeNode(IndexElem);
+			iparam->name = pstrdup(NameStr(attr->attname));
+			iparam->expr = NULL;
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->opclassopts = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+		}
+	}
+	else
+	{
+		Bitmapset *key_attnos;
+
+		/* create index on the base tables' primary key columns */
+		key_attnos = get_primary_key_attnos_from_query(query, &constraintList, is_create);
+		if (key_attnos)
+		{
+			foreach(lc, query->targetList)
+			{
+				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+				Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+
+				if (bms_is_member(tle->resno - FirstLowInvalidHeapAttributeNumber, key_attnos))
+				{
+					IndexElem  *iparam;
+
+					iparam = makeNode(IndexElem);
+					iparam->name = pstrdup(NameStr(attr->attname));
+					iparam->expr = NULL;
+					iparam->indexcolname = NULL;
+					iparam->collation = NIL;
+					iparam->opclass = NIL;
+					iparam->opclassopts = NIL;
+					iparam->ordering = SORTBY_DEFAULT;
+					iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+					index->indexParams = lappend(index->indexParams, iparam);
+				}
+			}
+		}
+		else
+		{
+			/* create no index, just notice that an appropriate index is necessary for efficient IVM */
+			ereport(NOTICE,
+					(errmsg("could not create an index on materialized view \"%s\" automatically",
+							RelationGetRelationName(matviewRel)),
+					 errdetail("This target list does not have all the primary key columns, "
+							   "or this view does not contain DISTINCT clause."),
+					 errhint("Create an index on the materialized view for efficient incremental maintenance.")));
+			return;
+		}
+	}
+
+	/* If we have a compatible index, we don't need to create another. */
+	foreach(indexoidscan, indexoidlist)
+	{
+		Oid			indexoid = lfirst_oid(indexoidscan);
+		Relation	indexRel;
+		bool		hasCompatibleIndex = false;
+
+		indexRel = index_open(indexoid, AccessShareLock);
+
+		if (CheckIndexCompatible(indexRel->rd_id,
+								index->accessMethod,
+								index->indexParams,
+								index->excludeOpNames))
+			hasCompatibleIndex = true;
+
+		index_close(indexRel, AccessShareLock);
+
+		if (hasCompatibleIndex)
+			return;
+	}
+
+	address = DefineIndex(RelationGetRelid(matviewRel),
+						  index,
+						  InvalidOid,
+						  InvalidOid,
+						  InvalidOid,
+						  false, true, false, false, true);
+
+	ereport(NOTICE,
+			(errmsg("created index \"%s\" on materialized view \"%s\"",
+					idxname, RelationGetRelationName(matviewRel))));
+
+	/*
+	 * Make dependencies so that the index is dropped if any base tables's
+	 * primary key is dropped.
+	 */
+	foreach(lc, constraintList)
+	{
+		Oid constraintOid = lfirst_oid(lc);
+		ObjectAddress	refaddr;
+
+		refaddr.classId = ConstraintRelationId;
+		refaddr.objectId = constraintOid;
+		refaddr.objectSubId = 0;
+
+		recordDependencyOn(&address, &refaddr, DEPENDENCY_NORMAL);
+	}
+}
+
+
+/*
+ * get_primary_key_attnos_from_query
+ *
+ * Identify the columns in base tables' primary keys in the target list.
+ *
+ * Returns a Bitmapset of the column attnos of the primary key's columns of
+ * tables that used in the query.  The attnos are offset by
+ * FirstLowInvalidHeapAttributeNumber as same as get_primary_key_attnos.
+ *
+ * If any table has no primary key or any primary key's columns is not in
+ * the target list, return NULL.  We also return NULL if any pkey constraint
+ * is deferrable.
+ *
+ * constraintList is set to a list of the OIDs of the pkey constraints.
+ */
+static Bitmapset *
+get_primary_key_attnos_from_query(Query *query, List **constraintList, bool is_create)
+{
+	List *key_attnos_list = NIL;
+	ListCell *lc;
+	int i;
+	Bitmapset *keys = NULL;
+	Relids	rels_in_from;
+	PlannerInfo root;
+
+
+	/*
+	 * Collect primary key attributes from all tables used in query. The key attributes
+	 * sets for each table are stored in key_attnos_list in order by RTE index.
+	 */
+	i = 1;
+	foreach(lc, query->rtable)
+	{
+		RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+		Bitmapset *key_attnos;
+		bool	has_pkey = true;
+		Index	first_rtindex = is_create ? 1 : PRS2_NEW_VARNO + 1;
+
+		/* skip NEW/OLD entries */
+		if (i >= first_rtindex)
+		{
+			/* for tables, call get_primary_key_attnos */
+			if (r->rtekind == RTE_RELATION)
+			{
+				Oid constraintOid;
+				key_attnos = get_primary_key_attnos(r->relid, false, &constraintOid);
+				*constraintList = lappend_oid(*constraintList, constraintOid);
+				has_pkey = (key_attnos != NULL);
+			}
+			/* for other RTEs, store NULL into key_attnos_list */
+			else
+				key_attnos = NULL;
+		}
+		else
+			key_attnos = NULL;
+
+		/*
+		 * If any table or subquery has no primary key or its pkey constraint is deferrable,
+		 * we cannot get key attributes for this query, so return NULL.
+		 */
+		if (!has_pkey)
+			return NULL;
+
+		key_attnos_list = lappend(key_attnos_list, key_attnos);
+		i++;
+	}
+
+	/* Collect key attributes appearing in the target list */
+	i = 1;
+	foreach(lc, query->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) flatten_join_alias_vars(query, lfirst(lc));
+
+		if (IsA(tle->expr, Var))
+		{
+			Var *var = (Var*) tle->expr;
+			Bitmapset *attnos = list_nth(key_attnos_list, var->varno - 1);
+
+			/* check if this attribute is from a base table's primary key */
+			if (bms_is_member(var->varattno - FirstLowInvalidHeapAttributeNumber, attnos))
+			{
+				/*
+				 * Remove found key attributes from key_attnos_list, and add this
+				 * to the result list.
+				 */
+				bms_del_member(attnos, var->varattno - FirstLowInvalidHeapAttributeNumber);
+				keys = bms_add_member(keys, i - FirstLowInvalidHeapAttributeNumber);
+			}
+		}
+		i++;
+	}
+
+	/* Collect relations appearing in the FROM clause */
+	rels_in_from = pull_varnos_of_level(&root, (Node *)query->jointree, 0);
+
+	/*
+	 * Check if all key attributes of relations in FROM are appearing in the target
+	 * list.  If an attribute remains in key_attnos_list in spite of the table is used
+	 * in FROM clause, the target is missing this key attribute, so we return NULL.
+	 */
+	i = 1;
+	foreach(lc, key_attnos_list)
+	{
+		Bitmapset *bms = (Bitmapset *)lfirst(lc);
+		if (!bms_is_empty(bms) && bms_is_member(i, rels_in_from))
+			return NULL;
+		i++;
+	}
+
+	return keys;
+}
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index cd30f15eba..a94b8ffd7d 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -36,6 +36,7 @@
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
+#include "commands/matview.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
@@ -1055,6 +1056,45 @@ DefineIndex(Oid relationId,
 	safe_index = indexInfo->ii_Expressions == NIL &&
 		indexInfo->ii_Predicate == NIL;
 
+	/*
+	 * We disallow unique indexes on IVM columns of IMMVs.
+	 */
+	if (RelationIsIVM(rel) && stmt->unique)
+	{
+		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
+		{
+			AttrNumber	attno = indexInfo->ii_IndexAttrNumbers[i];
+			if (attno > 0)
+			{
+				char *name = NameStr(TupleDescAttr(rel->rd_att, attno - 1)->attname);
+				if (name && isIvmName(name))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique index creation on IVM columns is not supported")));
+			}
+		}
+
+		if (indexInfo->ii_Expressions)
+		{
+			Bitmapset  *indexattrs = NULL;
+			int			varno = -1;
+
+			pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
+
+			while ((varno = bms_next_member(indexattrs, varno)) >= 0)
+			{
+				int attno = varno + FirstLowInvalidHeapAttributeNumber;
+				char *name = NameStr(TupleDescAttr(rel->rd_att, attno - 1)->attname);
+				if (name && isIvmName(name))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique index creation on IVM columns is not supported")));
+			}
+
+		}
+	}
+
+
 	/*
 	 * Report index creation if appropriate (delay this till after most of the
 	 * error checks)
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 05e7b60059..29cf18360e 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -25,26 +25,47 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_type.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "commands/cluster.h"
+#include "commands/defrem.h"
 #include "commands/matview.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
+#include "commands/createas.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "executor/tstoreReceiver.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_func.h"
 #include "parser/parse_relation.h"
+#include "parser/parse_type.h"
 #include "pgstat.h"
 #include "rewrite/rewriteHandler.h"
+#include "rewrite/rewriteManip.h"
+#include "rewrite/rowsecurity.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
 
 
 typedef struct
@@ -58,6 +79,50 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_transientrel;
 
+#define MV_INIT_QUERYHASHSIZE	16
+
+/*
+ * MV_TriggerHashEntry
+ *
+ * Hash entry for base tables on which IVM trigger is invoked
+ */
+typedef struct MV_TriggerHashEntry
+{
+	Oid	matview_id;			/* OID of the materialized view */
+	int	before_trig_count;	/* count of before triggers invoked */
+	int	after_trig_count;	/* count of after triggers invoked */
+
+	TransactionId	xid;	/* Transaction id before the first table is modified*/
+	CommandId		cid;	/* Command id before the first table is modified */
+
+	List   *tables;		/* List of MV_TriggerTable */
+	bool	has_old;	/* tuples are deleted from any table? */
+	bool	has_new;	/* tuples are inserted into any table? */
+} MV_TriggerHashEntry;
+
+/*
+ * MV_TriggerTable
+ *
+ * IVM related data for tables on which the trigger is invoked.
+ */
+typedef struct MV_TriggerTable
+{
+	Oid		table_id;			/* OID of the modified table */
+	List   *old_tuplestores;	/* tuplestores for deleted tuples */
+	List   *new_tuplestores;	/* tuplestores for inserted tuples */
+	List   *old_rtes;			/* RTEs of ENRs for old_tuplestores*/
+	List   *new_rtes;			/* RTEs of ENRs for new_tuplestores */
+
+	List   *rte_indexes;		/* List of RTE index of the modified table */
+	RangeTblEntry *original_rte;	/* the original RTE saved before rewriting query */
+} MV_TriggerTable;
+
+static HTAB *mv_trigger_info = NULL;
+
+/* ENR name for materialized view delta */
+#define NEW_DELTA_ENRNAME "new_delta"
+#define OLD_DELTA_ENRNAME "old_delta"
+
 static int	matview_maintenance_depth = 0;
 
 static void transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
@@ -65,7 +130,9 @@ static bool transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void transientrel_shutdown(DestReceiver *self);
 static void transientrel_destroy(DestReceiver *self);
 static uint64 refresh_matview_datafill(DestReceiver *dest, Query *query,
-									   const char *queryString);
+						 QueryEnvironment *queryEnv,
+						 TupleDesc *resultTupleDesc,
+						 const char *queryString);
 static char *make_temptable_name_n(char *tempname, int n);
 static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 								   int save_sec_context);
@@ -73,6 +140,45 @@ static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersist
 static bool is_usable_unique_index(Relation indexRel);
 static void OpenMatViewIncrementalMaintenance(void);
 static void CloseMatViewIncrementalMaintenance(void);
+static Query *get_matview_query(Relation matviewRel);
+
+static Query *rewrite_query_for_preupdate_state(Query *query, List *tables,
+								  TransactionId xid, CommandId cid,
+								  ParseState *pstate);
+static void register_delta_ENRs(ParseState *pstate, Query *query, List *tables);
+static char *make_delta_enr_name(const char *prefix, Oid relid, int count);
+static RangeTblEntry *get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
+				 TransactionId xid, CommandId cid,
+				 QueryEnvironment *queryEnv);
+static RangeTblEntry *union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
+		   QueryEnvironment *queryEnv);
+static Query *rewrite_query_for_distinct(Query *query, ParseState *pstate);
+
+static void calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
+			DestReceiver *dest_old, DestReceiver *dest_new,
+			TupleDesc *tupdesc_old, TupleDesc *tupdesc_new,
+			QueryEnvironment *queryEnv);
+static Query *rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte_index);
+
+static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
+			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
+			Query *query, bool use_count, char *count_colname);
+static void apply_old_delta(const char *matviewname, const char *deltaname_old,
+				List *keys);
+static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
+				List *keys, const char *count_colname);
+static void apply_new_delta(const char *matviewname, const char *deltaname_new,
+				StringInfo target_list);
+static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
+				List *keys, StringInfo target_list, const char* count_colname);
+static char *get_matching_condition_string(List *keys);
+static void generate_equal(StringInfo querybuf, Oid opttype,
+			   const char *leftop, const char *rightop);
+
+static void mv_InitHashTables(void);
+static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry);
+
+static List *get_securityQuals(Oid relId, int rt_index, Query *query);
 
 /*
  * SetMatViewPopulatedState
@@ -114,6 +220,46 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
 	CommandCounterIncrement();
 }
 
+/*
+ * SetMatViewIVMState
+ *		Mark a materialized view as IVM, or not.
+ *
+ * NOTE: caller must be holding an appropriate lock on the relation.
+ */
+void
+SetMatViewIVMState(Relation relation, bool newstate)
+{
+	Relation	pgrel;
+	HeapTuple	tuple;
+
+	Assert(relation->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Update relation's pg_class entry.  Crucial side-effect: other backends
+	 * (and this one too!) are sent SI message to make them rebuild relcache
+	 * entries.
+	 */
+	pgrel = table_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(relation)));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u",
+			 RelationGetRelid(relation));
+
+	((Form_pg_class) GETSTRUCT(tuple))->relisivm = newstate;
+
+	CatalogTupleUpdate(pgrel, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	table_close(pgrel, RowExclusiveLock);
+
+	/*
+	 * Advance command counter to make the updated pg_class row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+}
+
 /*
  * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command
  *
@@ -140,9 +286,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 {
 	Oid			matviewOid;
 	Relation	matviewRel;
-	RewriteRule *rule;
-	List	   *actions;
 	Query	   *dataQuery;
+	Query	   *viewQuery;
 	Oid			tableSpace;
 	Oid			relowner;
 	Oid			OIDNewHeap;
@@ -155,6 +300,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 	int			save_sec_context;
 	int			save_nestlevel;
 	ObjectAddress address;
+	bool oldPopulated;
 
 	/* Determine strength of lock needed. */
 	concurrent = stmt->concurrent;
@@ -167,6 +313,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										  lockmode, 0,
 										  RangeVarCallbackOwnsTable, NULL);
 	matviewRel = table_open(matviewOid, NoLock);
+	oldPopulated = RelationIsPopulated(matviewRel);
 
 	/* Make sure it is a materialized view. */
 	if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW)
@@ -188,32 +335,14 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 				 errmsg("%s and %s options cannot be used together",
 						"CONCURRENTLY", "WITH NO DATA")));
 
-	/*
-	 * Check that everything is correct for a refresh. Problems at this point
-	 * are internal errors, so elog is sufficient.
-	 */
-	if (matviewRel->rd_rel->relhasrules == false ||
-		matviewRel->rd_rules->numLocks < 1)
-		elog(ERROR,
-			 "materialized view \"%s\" is missing rewrite information",
-			 RelationGetRelationName(matviewRel));
-
-	if (matviewRel->rd_rules->numLocks > 1)
-		elog(ERROR,
-			 "materialized view \"%s\" has too many rules",
-			 RelationGetRelationName(matviewRel));
 
-	rule = matviewRel->rd_rules->rules[0];
-	if (rule->event != CMD_SELECT || !(rule->isInstead))
-		elog(ERROR,
-			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
-			 RelationGetRelationName(matviewRel));
+	viewQuery = get_matview_query(matviewRel);
 
-	actions = rule->actions;
-	if (list_length(actions) != 1)
-		elog(ERROR,
-			 "the rule for materialized view \"%s\" is not a single action",
-			 RelationGetRelationName(matviewRel));
+	/* For IMMV, we need to rewrite matview query */
+	if (!stmt->skipData && RelationIsIVM(matviewRel))
+		dataQuery = rewriteQueryForIMMV(viewQuery,NIL);
+	else
+		dataQuery = viewQuery;
 
 	/*
 	 * Check that there is a unique index with no WHERE clause on one or more
@@ -248,12 +377,6 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 					 errhint("Create a unique index with no WHERE clause on one or more columns of the materialized view.")));
 	}
 
-	/*
-	 * The stored query was rewritten at the time of the MV definition, but
-	 * has not been scribbled on by the planner.
-	 */
-	dataQuery = linitial_node(Query, actions);
-
 	/*
 	 * Check for active uses of the relation in the current transaction, such
 	 * as open scans.
@@ -294,6 +417,52 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 		relpersistence = matviewRel->rd_rel->relpersistence;
 	}
 
+	/* delete immv triggers */
+	if (RelationIsIVM(matviewRel) && stmt->skipData )
+	{
+		/* use deleted trigger */
+		Relation	depRel;
+		ScanKeyData key;
+		SysScanDesc scan;
+		HeapTuple	tup;
+		ObjectAddresses *immv_triggers;
+
+		immv_triggers = new_object_addresses();
+
+		/*
+		 * We save some cycles by opening pg_depend just once and passing the
+		 * Relation pointer down to all the recursive deletion steps.
+		 */
+		depRel = table_open(DependRelationId, RowExclusiveLock);
+
+		ScanKeyInit(&key,
+					Anum_pg_depend_refobjid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(matviewOid));
+
+		scan = systable_beginscan(depRel, DependReferenceIndexId, true,
+								  NULL, 1, &key);
+		while ((tup = systable_getnext(scan)) != NULL)
+		{
+			ObjectAddress obj;
+			Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
+
+			if (foundDep->deptype == DEPENDENCY_IMMV)
+			{
+				obj.classId = foundDep->classid;
+				obj.objectId = foundDep->objid;
+				obj.objectSubId = foundDep->refobjsubid;
+				add_exact_object_address(&obj, immv_triggers);
+			}
+		}
+		systable_endscan(scan);
+
+		performMultipleDeletions(immv_triggers, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
+
+		table_close(depRel, RowExclusiveLock);
+		free_object_addresses(immv_triggers);
+	}
+
 	/*
 	 * Create the transient table that will receive the regenerated data. Lock
 	 * it against access by any other process until commit (by which time it
@@ -313,7 +482,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 
 	/* Generate the data, if wanted. */
 	if (!stmt->skipData)
-		processed = refresh_matview_datafill(dest, dataQuery, queryString);
+		processed = refresh_matview_datafill(dest, dataQuery, NULL, NULL, queryString);
 
 	/* Make the matview match the newly generated data. */
 	if (concurrent)
@@ -348,6 +517,12 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 			pgstat_count_heap_insert(matviewRel, processed);
 	}
 
+	if (!stmt->skipData && RelationIsIVM(matviewRel) && !oldPopulated)
+	{
+		CreateIndexOnIMMV(viewQuery, matviewRel, false);
+		CreateIvmTriggersOnBaseTables(viewQuery, matviewOid, false);
+	}
+
 	table_close(matviewRel, NoLock);
 
 	/* Roll back any GUC changes */
@@ -382,6 +557,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  */
 static uint64
 refresh_matview_datafill(DestReceiver *dest, Query *query,
+						 QueryEnvironment *queryEnv,
+						 TupleDesc *resultTupleDesc,
 						 const char *queryString)
 {
 	List	   *rewritten;
@@ -418,7 +595,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 	/* Create a QueryDesc, redirecting output to our tuple receiver */
 	queryDesc = CreateQueryDesc(plan, queryString,
 								GetActiveSnapshot(), InvalidSnapshot,
-								dest, NULL, NULL, 0);
+								dest, NULL, queryEnv ? queryEnv: NULL, 0);
 
 	/* call ExecutorStart to prepare the plan for execution */
 	ExecutorStart(queryDesc, 0);
@@ -428,6 +605,9 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 
 	processed = queryDesc->estate->es_processed;
 
+	if (resultTupleDesc)
+		*resultTupleDesc = CreateTupleDescCopy(queryDesc->tupDesc);
+
 	/* and clean up */
 	ExecutorFinish(queryDesc);
 	ExecutorEnd(queryDesc);
@@ -942,3 +1122,1308 @@ CloseMatViewIncrementalMaintenance(void)
 	matview_maintenance_depth--;
 	Assert(matview_maintenance_depth >= 0);
 }
+
+/*
+ * get_matview_query - get the Query from a matview's _RETURN rule.
+ */
+static Query *
+get_matview_query(Relation matviewRel)
+{
+	RewriteRule *rule;
+	List * actions;
+
+	/*
+	 * Check that everything is correct for a refresh. Problems at this point
+	 * are internal errors, so elog is sufficient.
+	 */
+	if (matviewRel->rd_rel->relhasrules == false ||
+		matviewRel->rd_rules->numLocks < 1)
+		elog(ERROR,
+			 "materialized view \"%s\" is missing rewrite information",
+			 RelationGetRelationName(matviewRel));
+
+	if (matviewRel->rd_rules->numLocks > 1)
+		elog(ERROR,
+			 "materialized view \"%s\" has too many rules",
+			 RelationGetRelationName(matviewRel));
+
+	rule = matviewRel->rd_rules->rules[0];
+	if (rule->event != CMD_SELECT || !(rule->isInstead))
+		elog(ERROR,
+			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
+			 RelationGetRelationName(matviewRel));
+
+	actions = rule->actions;
+	if (list_length(actions) != 1)
+		elog(ERROR,
+			 "the rule for materialized view \"%s\" is not a single action",
+			 RelationGetRelationName(matviewRel));
+
+	/*
+	 * The stored query was rewritten at the time of the MV definition, but
+	 * has not been scribbled on by the planner.
+	 */
+	return linitial_node(Query, actions);
+}
+
+
+/* ----------------------------------------------------
+ *		Incremental View Maintenance routines
+ * ---------------------------------------------------
+ */
+
+/*
+ * IVM_immediate_before
+ *
+ * IVM trigger function invoked before base table is modified. If this is
+ * invoked firstly in the same statement, we save the transaction id and the
+ * command id at that time.
+ */
+Datum
+IVM_immediate_before(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	char	   *matviewOid_text = trigdata->tg_trigger->tgargs[0];
+	char	   *ex_lock_text = trigdata->tg_trigger->tgargs[1];
+	Oid			matviewOid;
+	MV_TriggerHashEntry *entry;
+	bool	found;
+	bool	ex_lock;
+
+	matviewOid = DatumGetObjectId(DirectFunctionCall1(oidin, CStringGetDatum(matviewOid_text)));
+	ex_lock = DatumGetBool(DirectFunctionCall1(boolin, CStringGetDatum(ex_lock_text)));
+
+	/* If the view has more than one tables, we have to use an exclusive lock. */
+	if (ex_lock)
+	{
+		/*
+		 * Wait for concurrent transactions which update this materialized view at
+		 * READ COMMITED. This is needed to see changes committed in other
+		 * transactions. No wait and raise an error at REPEATABLE READ or
+		 * SERIALIZABLE to prevent update anomalies of matviews.
+		 * XXX: dead-lock is possible here.
+		 */
+		if (!IsolationUsesXactSnapshot())
+			LockRelationOid(matviewOid, ExclusiveLock);
+		else if (!ConditionalLockRelationOid(matviewOid, ExclusiveLock))
+		{
+			/* try to throw error by name; relation could be deleted... */
+			char	   *relname = get_rel_name(matviewOid);
+
+			if (!relname)
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						errmsg("could not obtain lock on materialized view during incremental maintenance")));
+
+			ereport(ERROR,
+					(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+					errmsg("could not obtain lock on materialized view \"%s\" during incremental maintenance",
+							relname)));
+		}
+	}
+	else
+		LockRelationOid(matviewOid, RowExclusiveLock);
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_trigger_info)
+		mv_InitHashTables();
+
+	entry = (MV_TriggerHashEntry *) hash_search(mv_trigger_info,
+											  (void *) &matviewOid,
+											  HASH_ENTER, &found);
+
+	/* On the first BEFORE to update the view, initialize trigger data */
+	if (!found)
+	{
+		Snapshot snapshot = GetActiveSnapshot();
+
+		entry->matview_id = matviewOid;
+		entry->before_trig_count = 0;
+		entry->after_trig_count = 0;
+		entry->xid = GetCurrentTransactionId();
+		entry->cid = snapshot->curcid;
+		entry->tables = NIL;
+		entry->has_old = false;
+		entry->has_new = false;
+	}
+
+	entry->before_trig_count++;
+
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * IVM_immediate_before
+ *
+ * IVM trigger function invoked after base table is modified.
+ * For each table, tuplestores of transition tables are collected.
+ * and after the last modification
+ */
+Datum
+IVM_immediate_maintenance(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	Relation	rel;
+	Oid			relid;
+	Oid			matviewOid;
+	Query	   *query;
+	Query	   *rewritten = NULL;
+	char	   *matviewOid_text = trigdata->tg_trigger->tgargs[0];
+	Relation	matviewRel;
+	int old_depth = matview_maintenance_depth;
+
+	Oid			relowner;
+	Tuplestorestate *old_tuplestore = NULL;
+	Tuplestorestate *new_tuplestore = NULL;
+	DestReceiver *dest_new = NULL, *dest_old = NULL;
+	Oid			save_userid;
+	int			save_sec_context;
+	int			save_nestlevel;
+
+	MV_TriggerHashEntry *entry;
+	MV_TriggerTable		*table;
+	bool	found;
+
+	ParseState		 *pstate;
+	QueryEnvironment *queryEnv = create_queryEnv();
+	MemoryContext	oldcxt;
+	ListCell   *lc;
+	int			i;
+
+
+	/* Create a ParseState for rewriting the view definition query */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	rel = trigdata->tg_relation;
+	relid = rel->rd_id;
+
+	matviewOid = DatumGetObjectId(DirectFunctionCall1(oidin, CStringGetDatum(matviewOid_text)));
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_trigger_info)
+		mv_InitHashTables();
+
+	/* get the entry for this materialized view */
+	entry = (MV_TriggerHashEntry *) hash_search(mv_trigger_info,
+											  (void *) &matviewOid,
+											  HASH_FIND, &found);
+	Assert (found && entry != NULL);
+	entry->after_trig_count++;
+
+	/* search the entry for the modified table and create new entry if not found */
+	found = false;
+	foreach(lc, entry->tables)
+	{
+		table = (MV_TriggerTable *) lfirst(lc);
+		if (table->table_id == relid)
+		{
+			found = true;
+			break;
+		}
+	}
+	if (!found)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		table = (MV_TriggerTable *) palloc0(sizeof(MV_TriggerTable));
+		table->table_id = relid;
+		table->old_tuplestores = NIL;
+		table->new_tuplestores = NIL;
+		table->old_rtes = NIL;
+		table->new_rtes = NIL;
+		table->rte_indexes = NIL;
+		entry->tables = lappend(entry->tables, table);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* Save the transition tables and make a request to not free immediately */
+	if (trigdata->tg_oldtable)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+		table->old_tuplestores = lappend(table->old_tuplestores, trigdata->tg_oldtable);
+		entry->has_old = true;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (trigdata->tg_newtable)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+		table->new_tuplestores = lappend(table->new_tuplestores, trigdata->tg_newtable);
+		entry->has_new = true;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (entry->has_new || entry->has_old)
+	{
+		CmdType cmd;
+
+		if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+			cmd = CMD_INSERT;
+		else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+			cmd = CMD_DELETE;
+		else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+			cmd = CMD_UPDATE;
+		else
+			elog(ERROR,"unsupported trigger type");
+
+		/* Prolong lifespan of transition tables to the end of the last AFTER trigger */
+		SetTransitionTablePreserved(relid, cmd);
+	}
+
+
+	/* If this is not the last AFTER trigger call, immediately exit. */
+	Assert (entry->before_trig_count >= entry->after_trig_count);
+	if (entry->before_trig_count != entry->after_trig_count)
+		return PointerGetDatum(NULL);
+
+	/*
+	 * If this is the last AFTER trigger call, continue and update the view.
+	 */
+
+	/*
+	 * Advance command counter to make the updated base table row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+
+	matviewRel = table_open(matviewOid, NoLock);
+
+	/* get view query*/
+	query = get_matview_query(matviewRel);
+
+	/* Make sure it is a materialized view. */
+	Assert(matviewRel->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Get and push the latast snapshot to see any changes which is committed
+	 * during waiting in other transactions at READ COMMITTED level.
+	 */
+	PushActiveSnapshot(GetTransactionSnapshot());
+
+	/*
+	 * Check for active uses of the relation in the current transaction, such
+	 * as open scans.
+	 *
+	 * NB: We count on this to protect us against problems with refreshing the
+	 * data using TABLE_INSERT_FROZEN.
+	 */
+	CheckTableNotInUse(matviewRel, "refresh a materialized view incrementally");
+
+	/*
+	 * Switch to the owner's userid, so that any functions are run as that
+	 * user.  Also arrange to make GUC variable changes local to this command.
+	 * We will switch modes when we are about to execute user code.
+	 */
+	relowner = matviewRel->rd_rel->relowner;
+	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+	save_nestlevel = NewGUCNestLevel();
+
+	/*
+	 * rewrite query for calculating deltas
+	 */
+
+	rewritten = copyObject(query);
+
+	/* Replace resnames in a target list with materialized view's attnames */
+	i = 0;
+	foreach (lc, rewritten->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char *resname = NameStr(attr->attname);
+
+		tle->resname = pstrdup(resname);
+		i++;
+	}
+
+	/* Set all tables in the query to pre-update state */
+	rewritten = rewrite_query_for_preupdate_state(rewritten, entry->tables,
+												  entry->xid, entry->cid,
+												  pstate);
+	/* Rewrite for DISTINCT clause */
+	rewritten = rewrite_query_for_distinct(rewritten, pstate);
+
+	/* Create tuplestores to store view deltas */
+	if (entry->has_old)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		old_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+		dest_old = CreateDestReceiver(DestTuplestore);
+		SetTuplestoreDestReceiverParams(dest_old,
+									old_tuplestore,
+									TopTransactionContext,
+									false,
+									NULL,
+									NULL);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (entry->has_new)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		new_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+		dest_new = CreateDestReceiver(DestTuplestore);
+		SetTuplestoreDestReceiverParams(dest_new,
+									new_tuplestore,
+									TopTransactionContext,
+									false,
+									NULL,
+									NULL);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* for all modified tables */
+	foreach(lc, entry->tables)
+	{
+		ListCell *lc2;
+
+		table = (MV_TriggerTable *) lfirst(lc);
+
+		/* loop for self-join */
+		foreach(lc2, table->rte_indexes)
+		{
+			int	rte_index = lfirst_int(lc2);
+			TupleDesc		tupdesc_old;
+			TupleDesc		tupdesc_new;
+			bool	use_count = false;
+			char   *count_colname = NULL;
+
+			count_colname = pstrdup("__ivm_count__");
+
+			if (query->distinctClause)
+				use_count = true;
+
+			/* calculate delta tables */
+			calc_delta(table, rte_index, rewritten, dest_old, dest_new,
+					   &tupdesc_old, &tupdesc_new, queryEnv);
+
+			/* Set the table in the query to post-update state */
+			rewritten = rewrite_query_for_postupdate_state(rewritten, table, rte_index);
+
+			PG_TRY();
+			{
+				/* apply the delta tables to the materialized view */
+				apply_delta(matviewOid, old_tuplestore, new_tuplestore,
+							tupdesc_old, tupdesc_new, query, use_count,
+							count_colname);
+			}
+			PG_CATCH();
+			{
+				matview_maintenance_depth = old_depth;
+				PG_RE_THROW();
+			}
+			PG_END_TRY();
+
+			/* clear view delta tuplestores */
+			if (old_tuplestore)
+				tuplestore_clear(old_tuplestore);
+			if (new_tuplestore)
+				tuplestore_clear(new_tuplestore);
+		}
+	}
+
+	/* Clean up hash entry and delete tuplestores */
+	clean_up_IVM_hash_entry(entry);
+	if (old_tuplestore)
+	{
+		dest_old->rDestroy(dest_old);
+		tuplestore_end(old_tuplestore);
+	}
+	if (new_tuplestore)
+	{
+		dest_new->rDestroy(dest_new);
+		tuplestore_end(new_tuplestore);
+	}
+
+	/* Pop the original snapshot. */
+	PopActiveSnapshot();
+
+	table_close(matviewRel, NoLock);
+
+	/* Roll back any GUC changes */
+	AtEOXact_GUC(false, save_nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(save_userid, save_sec_context);
+
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * rewrite_query_for_preupdate_state
+ *
+ * Rewrite the query so that base tables' RTEs will represent "pre-update"
+ * state of tables. This is necessary to calculate view delta after multiple
+ * tables are modified. xid and cid are the transaction id and command id
+ * before the first table was modified.
+ */
+static Query*
+rewrite_query_for_preupdate_state(Query *query, List *tables,
+								  TransactionId xid, CommandId cid,
+								  ParseState *pstate)
+{
+	ListCell *lc;
+	int num_rte = list_length(query->rtable);
+	int i;
+
+
+	/* register delta ENRs */
+	register_delta_ENRs(pstate, query, tables);
+
+	/* XXX: Is necessary? Is this right timing? */
+	AcquireRewriteLocks(query, true, false);
+
+	i = 1;
+	foreach(lc, query->rtable)
+	{
+		RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+
+		ListCell *lc2;
+		foreach(lc2, tables)
+		{
+			MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc2);
+			/*
+			 * if the modified table is found then replace the original RTE with
+			 * "pre-state" RTE and append its index to the list.
+			 */
+			if (r->relid == table->table_id)
+			{
+				lfirst(lc) = get_prestate_rte(r, table, xid, cid, pstate->p_queryEnv);
+				table->rte_indexes = lappend_int(table->rte_indexes, i);
+				break;
+			}
+		}
+
+		/* finish the loop if we processed all RTE included in the original query */
+		if (i++ >= num_rte)
+			break;
+	}
+
+	return query;
+}
+
+/*
+ * register_delta_ENRs
+ *
+ * For all modified tables, make ENRs for their transition tables
+ * and register them to the queryEnv. ENR's RTEs are also appended
+ * into the list in query tree.
+ */
+static void
+register_delta_ENRs(ParseState *pstate, Query *query, List *tables)
+{
+	QueryEnvironment *queryEnv = pstate->p_queryEnv;
+	ListCell *lc;
+	RangeTblEntry	*rte;
+
+	foreach(lc, tables)
+	{
+		MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc);
+		ListCell *lc2;
+		int count;
+
+		count = 0;
+		foreach(lc2, table->old_tuplestores)
+		{
+			Tuplestorestate *oldtable = (Tuplestorestate *) lfirst(lc2);
+			EphemeralNamedRelation enr =
+				palloc(sizeof(EphemeralNamedRelationData));
+			ParseNamespaceItem *nsitem;
+
+			enr->md.name = make_delta_enr_name("old", table->table_id, count);
+			enr->md.reliddesc = table->table_id;
+			enr->md.tupdesc = NULL;
+			enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+			enr->md.enrtuples = tuplestore_tuple_count(oldtable);
+			enr->reldata = oldtable;
+			register_ENR(queryEnv, enr);
+
+			nsitem = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+			rte = nsitem->p_rte;
+			/* if base table has RLS, set security condition to enr */
+			rte->securityQuals = get_securityQuals(table->table_id, list_length(query->rtable) + 1, query);
+
+			query->rtable = lappend(query->rtable, rte);
+			table->old_rtes = lappend(table->old_rtes, rte);
+
+			count++;
+		}
+
+		count = 0;
+		foreach(lc2, table->new_tuplestores)
+		{
+			Tuplestorestate *newtable = (Tuplestorestate *) lfirst(lc2);
+			EphemeralNamedRelation enr =
+				palloc(sizeof(EphemeralNamedRelationData));
+			ParseNamespaceItem *nsitem;
+
+			enr->md.name = make_delta_enr_name("new", table->table_id, count);
+			enr->md.reliddesc = table->table_id;
+			enr->md.tupdesc = NULL;
+			enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+			enr->md.enrtuples = tuplestore_tuple_count(newtable);
+			enr->reldata = newtable;
+			register_ENR(queryEnv, enr);
+
+			nsitem = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+			rte = nsitem->p_rte;
+			/* if base table has RLS, set security condition to enr*/
+			rte->securityQuals = get_securityQuals(table->table_id, list_length(query->rtable) + 1, query);
+
+			query->rtable = lappend(query->rtable, rte);
+			table->new_rtes = lappend(table->new_rtes, rte);
+
+			count++;
+		}
+	}
+}
+
+/*
+ * get_prestate_rte
+ *
+ * Rewrite RTE of the modified table to a subquery which represents
+ * "pre-state" table. The original RTE is saved in table->rte_original.
+ */
+static RangeTblEntry*
+get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
+				 TransactionId xid, CommandId cid,
+				 QueryEnvironment *queryEnv)
+{
+	StringInfoData str;
+	RawStmt *raw;
+	Query *sub;
+	Relation rel;
+	ParseState *pstate;
+	char *relname;
+	int i;
+
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	/*
+	 * We can use NoLock here since AcquireRewriteLocks should
+	 * have locked the rel already.
+	 */
+	rel = table_open(table->table_id, NoLock);
+	relname = quote_qualified_identifier(
+					get_namespace_name(RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+	table_close(rel, NoLock);
+
+	initStringInfo(&str);
+	appendStringInfo(&str,
+		"SELECT t.* FROM %s t"
+		" WHERE (age(t.xmin) - age(%u::text::xid) > 0) OR"
+		" (t.xmin = %u AND t.cmin::text::int < %u)",
+			relname, xid, xid, cid);
+
+	for (i = 0; i < list_length(table->old_tuplestores); i++)
+	{
+		appendStringInfo(&str, " UNION ALL ");
+		appendStringInfo(&str," SELECT * FROM %s",
+			make_delta_enr_name("old", table->table_id, i));
+	}
+
+	raw = (RawStmt*)linitial(raw_parser(str.data, RAW_PARSE_DEFAULT));
+	sub = transformStmt(pstate, raw->stmt);
+
+	/* If this query has setOperations, RTEs in rtables has a subquery which contains ENR */
+	if (sub->setOperations != NULL)
+	{
+		ListCell *lc;
+
+		/* add securityQuals for tuplestores */
+		foreach (lc, sub->rtable)
+		{
+			RangeTblEntry *rte;
+			RangeTblEntry *sub_rte;
+
+			rte = (RangeTblEntry *)lfirst(lc);
+			Assert(rte->subquery != NULL);
+
+			sub_rte = (RangeTblEntry *)linitial(rte->subquery->rtable);
+			if (sub_rte->rtekind == RTE_NAMEDTUPLESTORE)
+				/* rt_index is always 1, bacause subquery has enr_rte only */
+				sub_rte->securityQuals = get_securityQuals(sub_rte->relid, 1, sub);
+		}
+	}
+
+	/* save the original RTE */
+	table->original_rte = copyObject(rte);
+
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = sub;
+	rte->security_barrier = false;
+	/* Clear fields that should not be set in a subquery RTE */
+	rte->relid = InvalidOid;
+	rte->relkind = 0;
+	rte->rellockmode = 0;
+	rte->tablesample = NULL;
+	rte->inh = false;			/* must not be set for a subquery */
+
+	rte->requiredPerms = 0;		/* no permission check on subquery itself */
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->insertedCols = NULL;
+	rte->updatedCols = NULL;
+	rte->extraUpdatedCols = NULL;
+
+	return rte;
+}
+
+/*
+ * make_delta_enr_name
+ *
+ * Make a name for ENR of a transition table from the base table's oid.
+ * prefix will be "new" or "old" depending on its transition table kind..
+ */
+static char*
+make_delta_enr_name(const char *prefix, Oid relid, int count)
+{
+	char buf[NAMEDATALEN];
+	char *name;
+
+	snprintf(buf, NAMEDATALEN, "__ivm_%s_%u_%u", prefix, relid, count);
+	name = pstrdup(buf);
+
+	return name;
+}
+
+/*
+ * union_ENRs
+ *
+ * Make a single table delta by unionning all transition tables of the modified table
+ * whose RTE is specified by
+ */
+static RangeTblEntry*
+union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
+		   QueryEnvironment *queryEnv)
+{
+	StringInfoData str;
+	ParseState	*pstate;
+	RawStmt *raw;
+	Query *sub;
+	int	i;
+	RangeTblEntry *enr_rte;
+
+	/* Create a ParseState for rewriting the view definition query */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	initStringInfo(&str);
+
+	for (i = 0; i < list_length(enr_rtes); i++)
+	{
+		if (i > 0)
+			appendStringInfo(&str, " UNION ALL ");
+
+		appendStringInfo(&str,
+			" SELECT * FROM %s",
+			make_delta_enr_name(prefix, relid, i));
+	}
+
+	raw = (RawStmt*)linitial(raw_parser(str.data, RAW_PARSE_DEFAULT));
+	sub = transformStmt(pstate, raw->stmt);
+
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = sub;
+	rte->security_barrier = false;
+	/* Clear fields that should not be set in a subquery RTE */
+	rte->relid = InvalidOid;
+	rte->relkind = 0;
+	rte->rellockmode = 0;
+	rte->tablesample = NULL;
+	rte->inh = false;			/* must not be set for a subquery */
+
+	rte->requiredPerms = 0;		/* no permission check on subquery itself */
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->insertedCols = NULL;
+	rte->updatedCols = NULL;
+	rte->extraUpdatedCols = NULL;
+	/* if base table has RLS, set security condition to enr*/
+	enr_rte = (RangeTblEntry *)linitial(sub->rtable);
+	/* rt_index is always 1, bacause subquery has enr_rte only */
+	enr_rte->securityQuals = get_securityQuals(relid, 1, sub);
+
+	return rte;
+}
+
+/*
+ * rewrite_query_for_distinct
+ *
+ * Rewrite query for counting DISTINCT clause.
+ */
+static Query *
+rewrite_query_for_distinct(Query *query, ParseState *pstate)
+{
+	TargetEntry *tle_count;
+	FuncCall *fn;
+	Node *node;
+
+	/* Add count(*) for counting distinct tuples in views */
+	fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+	fn->agg_star = true;
+	if (!query->groupClause && !query->hasAggs)
+		query->groupClause = transformDistinctClause(NULL, &query->targetList, query->sortClause, false);
+
+	node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+	tle_count = makeTargetEntry((Expr *) node,
+								list_length(query->targetList) + 1,
+								pstrdup("__ivm_count__"),
+								false);
+	query->targetList = lappend(query->targetList, tle_count);
+	query->hasAggs = true;
+
+	return query;
+}
+
+/*
+ * calc_delta
+ *
+ * Calculate view deltas generated under the modification of a table specified
+ * by the RTE index.
+ */
+static void
+calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
+			DestReceiver *dest_old, DestReceiver *dest_new,
+			TupleDesc *tupdesc_old, TupleDesc *tupdesc_new,
+			QueryEnvironment *queryEnv)
+{
+	ListCell *lc = list_nth_cell(query->rtable, rte_index - 1);
+	RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+	/* Generate old delta */
+	if (list_length(table->old_rtes) > 0)
+	{
+		/* Replace the modified table with the old delta table and calculate the old view delta. */
+		lfirst(lc) = union_ENRs(rte, table->table_id, table->old_rtes, "old", queryEnv);
+		refresh_matview_datafill(dest_old, query, queryEnv, tupdesc_old, "");
+	}
+
+	/* Generate new delta */
+	if (list_length(table->new_rtes) > 0)
+	{
+		/* Replace the modified table with the new delta table and calculate the new view delta*/
+		lfirst(lc) = union_ENRs(rte, table->table_id, table->new_rtes, "new", queryEnv);
+		refresh_matview_datafill(dest_new, query, queryEnv, tupdesc_new, "");
+	}
+}
+
+/*
+ * rewrite_query_for_postupdate_state
+ *
+ * Rewrite the query so that the specified base table's RTEs will represent
+ * "post-update" state of tables. This is called after the view delta
+ * calculation due to changes on this table finishes.
+ */
+static Query*
+rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte_index)
+{
+	ListCell *lc = list_nth_cell(query->rtable, rte_index - 1);
+
+	/* Retore the original RTE */
+	lfirst(lc) = table->original_rte;
+
+	return query;
+}
+
+/*
+ * apply_delta
+ *
+ * Apply deltas to the materialized view. In outer join cases, this requires
+ * the view maintenance graph.
+ */
+static void
+apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
+			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
+			Query *query, bool use_count, char *count_colname)
+{
+	StringInfoData querybuf;
+	StringInfoData target_list_buf;
+	Relation	matviewRel;
+	char	   *matviewname;
+	ListCell	*lc;
+	int			i;
+	List	   *keys = NIL;
+
+
+	/*
+	 * get names of the materialized view and delta tables
+	 */
+
+	matviewRel = table_open(matviewOid, NoLock);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	/*
+	 * Build parts of the maintenance queries
+	 */
+
+	initStringInfo(&querybuf);
+	initStringInfo(&target_list_buf);
+
+	/* build string of target list */
+	for (i = 0; i < matviewRel->rd_att->natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char   *resname = NameStr(attr->attname);
+
+		if (i != 0)
+			appendStringInfo(&target_list_buf, ", ");
+		appendStringInfo(&target_list_buf, "%s", quote_qualified_identifier(NULL, resname));
+	}
+
+	i = 0;
+	foreach (lc, query->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char *resname = NameStr(attr->attname);
+
+		i++;
+
+		if (tle->resjunk)
+			continue;
+
+		keys = lappend(keys, resname);
+	}
+
+	/* Start maintaining the materialized view. */
+	OpenMatViewIncrementalMaintenance();
+
+	/* Open SPI context. */
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	/* For tuple deletion */
+	if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
+	{
+		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		int				rc;
+
+		/* convert tuplestores to ENR, and register for SPI */
+		enr->md.name = pstrdup(OLD_DELTA_ENRNAME);
+		enr->md.reliddesc = InvalidOid;
+		enr->md.tupdesc = tupdesc_old;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(old_tuplestores);
+		enr->reldata = old_tuplestores;
+
+		rc = SPI_register_relation(enr);
+		if (rc != SPI_OK_REL_REGISTER)
+			elog(ERROR, "SPI_register failed");
+
+		if (use_count)
+			/* apply old delta and get rows to be recalculated */
+			apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME,
+									   keys, count_colname);
+		else
+			apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys);
+
+	}
+	/* For tuple insertion */
+	if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
+	{
+		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		int rc;
+
+		/* convert tuplestores to ENR, and register for SPI */
+		enr->md.name = pstrdup(NEW_DELTA_ENRNAME);
+		enr->md.reliddesc = InvalidOid;
+		enr->md.tupdesc = tupdesc_new;;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(new_tuplestores);
+		enr->reldata = new_tuplestores;
+
+		rc = SPI_register_relation(enr);
+		if (rc != SPI_OK_REL_REGISTER)
+			elog(ERROR, "SPI_register failed");
+
+		/* apply new delta */
+		if (use_count)
+			apply_new_delta_with_count(matviewname, NEW_DELTA_ENRNAME,
+								keys, &target_list_buf, count_colname);
+		else
+			apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf);
+	}
+
+	/* We're done maintaining the materialized view. */
+	CloseMatViewIncrementalMaintenance();
+
+	table_close(matviewRel, NoLock);
+
+	/* Close SPI context. */
+	if (SPI_finish() != SPI_OK_FINISH)
+		elog(ERROR, "SPI_finish failed");
+}
+
+/*
+ * apply_old_delta_with_count
+ *
+ * Execute a query for applying a delta table given by deltname_old
+ * which contains tuples to be deleted from to a materialized view given by
+ * matviewname.  This is used when counting is required, that is, the view
+ * has aggregate or distinct.
+ */
+static void
+apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
+				List *keys, const char *count_colname)
+{
+	StringInfoData	querybuf;
+	char   *match_cond;
+
+	/* build WHERE condition for searching tuples to be deleted */
+	match_cond = get_matching_condition_string(keys);
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"WITH t AS ("			/* collecting tid of target tuples in the view */
+						"SELECT diff.%s, "			/* count column */
+								"(diff.%s OPERATOR(pg_catalog.=) mv.%s) AS for_dlt, "
+								"mv.ctid "
+						"FROM %s AS mv, %s AS diff "
+						"WHERE %s"					/* tuple matching condition */
+					"), updt AS ("			/* update a tuple if this is not to be deleted */
+						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.-) t.%s "
+						"FROM t WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND NOT for_dlt "
+					"), dlt AS ("			/* delete a tuple if this is to be deleted */
+						"DELETE FROM %s AS mv USING t "
+						"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt"
+					")",
+					count_colname,
+					count_colname, count_colname,
+					matviewname, deltaname_old,
+					match_cond,
+					matviewname, count_colname, count_colname, count_colname,
+					matviewname);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_old_delta
+ *
+ * Execute a query for applying a delta table given by deltname_old
+ * which contains tuples to be deleted from to a materialized view given by
+ * matviewname.  This is used when counting is not required.
+ */
+static void
+apply_old_delta(const char *matviewname, const char *deltaname_old,
+				List *keys)
+{
+	StringInfoData	querybuf;
+	StringInfoData	keysbuf;
+	char   *match_cond;
+	ListCell *lc;
+
+	/* build WHERE condition for searching tuples to be deleted */
+	match_cond = get_matching_condition_string(keys);
+
+	/* build string of keys list */
+	initStringInfo(&keysbuf);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char   *resname = NameStr(attr->attname);
+		appendStringInfo(&keysbuf, "%s", quote_qualified_identifier("mv", resname));
+		if (lnext(keys, lc))
+			appendStringInfo(&keysbuf, ", ");
+	}
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+	"DELETE FROM %s WHERE ctid IN ("
+		"SELECT tid FROM (SELECT row_number() over (partition by %s) AS \"__ivm_row_number__\","
+								  "mv.ctid AS tid,"
+								  "diff.\"__ivm_count__\""
+						 "FROM %s AS mv, %s AS diff "
+						 "WHERE %s) v "
+					"WHERE v.\"__ivm_row_number__\" OPERATOR(pg_catalog.<=) v.\"__ivm_count__\")",
+					matviewname,
+					keysbuf.data,
+					matviewname, deltaname_old,
+					match_cond);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_new_delta_with_count
+ *
+ * Execute a query for applying a delta table given by deltname_new
+ * which contains tuples to be inserted into a materialized view given by
+ * matviewname.  This is used when counting is required, that is, the view
+ * has aggregate or distinct. Also, when a table in EXISTS sub queries
+ * is modified.
+ */
+static void
+apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
+				List *keys, StringInfo target_list, const char* count_colname)
+{
+	StringInfoData	querybuf;
+	StringInfoData	returning_keys;
+	ListCell	*lc;
+	char	*match_cond = "";
+
+	/* build WHERE condition for searching tuples to be updated */
+	match_cond = get_matching_condition_string(keys);
+
+	/* build string of keys list */
+	initStringInfo(&returning_keys);
+	if (keys)
+	{
+		foreach (lc, keys)
+		{
+			Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+			char   *resname = NameStr(attr->attname);
+			appendStringInfo(&returning_keys, "%s", quote_qualified_identifier("mv", resname));
+			if (lnext(keys, lc))
+				appendStringInfo(&returning_keys, ", ");
+		}
+	}
+	else
+		appendStringInfo(&returning_keys, "NULL");
+
+	/* Search for matching tuples from the view and update if found or insert if not. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"WITH updt AS ("		/* update a tuple if this exists in the view */
+						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.+) diff.%s "
+						"FROM %s AS diff "
+						"WHERE %s "					/* tuple matching condition */
+						"RETURNING %s"				/* returning keys of updated tuples */
+					") INSERT INTO %s (%s)"	/* insert a new tuple if this doesn't existw */
+						"SELECT %s FROM %s AS diff "
+						"WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE %s);",
+					matviewname, count_colname, count_colname, count_colname,
+					deltaname_new,
+					match_cond,
+					returning_keys.data,
+					matviewname, target_list->data,
+					target_list->data, deltaname_new,
+					match_cond);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_new_delta
+ *
+ * Execute a query for applying a delta table given by deltname_new
+ * which contains tuples to be inserted into a materialized view given by
+ * matviewname.  This is used when counting is not required.
+ */
+static void
+apply_new_delta(const char *matviewname, const char *deltaname_new,
+				StringInfo target_list)
+{
+	StringInfoData	querybuf;
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"INSERT INTO %s (%s) SELECT %s FROM ("
+						"SELECT diff.*, generate_series(1, diff.\"__ivm_count__\") "
+						"FROM %s AS diff) AS v",
+					matviewname, target_list->data, target_list->data,
+					deltaname_new);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * get_matching_condition_string
+ *
+ * Build a predicate string for looking for a tuple with given keys.
+ */
+static char *
+get_matching_condition_string(List *keys)
+{
+	StringInfoData match_cond;
+	ListCell	*lc;
+
+	/* If there is no key columns, the condition is always true. */
+	if (keys == NIL)
+		return "true";
+
+	initStringInfo(&match_cond);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char   *resname = NameStr(attr->attname);
+		char   *mv_resname = quote_qualified_identifier("mv", resname);
+		char   *diff_resname = quote_qualified_identifier("diff", resname);
+		Oid		typid = attr->atttypid;
+
+		/* Considering NULL values, we can not use simple = operator. */
+		appendStringInfo(&match_cond, "(");
+		generate_equal(&match_cond, typid, mv_resname, diff_resname);
+		appendStringInfo(&match_cond, " OR (%s IS NULL AND %s IS NULL))",
+						 mv_resname, diff_resname);
+
+		if (lnext(keys, lc))
+			appendStringInfo(&match_cond, " AND ");
+	}
+
+	return match_cond.data;
+}
+
+/*
+ * generate_equals
+ *
+ * Generate an equality clause using given operands' default equality
+ * operator.
+ */
+static void
+generate_equal(StringInfo querybuf, Oid opttype,
+			   const char *leftop, const char *rightop)
+{
+	TypeCacheEntry *typentry;
+
+	typentry = lookup_type_cache(opttype, TYPECACHE_EQ_OPR);
+	if (!OidIsValid(typentry->eq_opr))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_FUNCTION),
+				 errmsg("could not identify an equality operator for type %s",
+						format_type_be(opttype))));
+
+	generate_operator_clause(querybuf,
+							 leftop, opttype,
+							 typentry->eq_opr,
+							 rightop, opttype);
+}
+
+/*
+ * mv_InitHashTables
+ */
+static void
+mv_InitHashTables(void)
+{
+	HASHCTL		ctl;
+
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(MV_TriggerHashEntry);
+	mv_trigger_info = hash_create("MV trigger info",
+								 MV_INIT_QUERYHASHSIZE,
+								 &ctl, HASH_ELEM | HASH_BLOBS);
+}
+
+/*
+ * AtAbort_IVM
+ *
+ * Clean up hash entries for all materialized views. This is called at
+ * transaction abort.
+ */
+void
+AtAbort_IVM()
+{
+	HASH_SEQ_STATUS seq;
+	MV_TriggerHashEntry *entry;
+
+	if (mv_trigger_info)
+	{
+		hash_seq_init(&seq, mv_trigger_info);
+		while ((entry = hash_seq_search(&seq)) != NULL)
+			clean_up_IVM_hash_entry(entry);
+	}
+}
+
+/*
+ * clean_up_IVM_hash_entry
+ *
+ * Clean up tuple stores and hash entries for a materialized view after its
+ * maintenance finished.
+ */
+static void
+clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry)
+{
+	bool found;
+	ListCell *lc;
+
+	foreach(lc, entry->tables)
+	{
+		MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc);
+
+		list_free(table->old_tuplestores);
+		list_free(table->new_tuplestores);
+	}
+	list_free(entry->tables);
+
+	hash_search(mv_trigger_info, (void *) &entry->matview_id, HASH_REMOVE, &found);
+}
+
+/*
+ * isIvmName
+ *
+ * Check if this is a IVM hidden column from the name.
+ */
+bool
+isIvmName(const char *s)
+{
+	if (s)
+		return (strncmp(s, "__ivm_", 6) == 0);
+	return false;
+}
+
+/*
+ * get_securityQuals
+ *
+ * Get row security policy on a relation.
+ * This is used by IVM for copying RLS from base table to enr.
+ */
+static List *
+get_securityQuals(Oid relId, int rt_index, Query *query)
+{
+	ParseState *pstate;
+	Relation rel;
+	ParseNamespaceItem *nsitem;
+	RangeTblEntry *rte;
+	List *securityQuals;
+	List *withCheckOptions;
+	bool  hasRowSecurity;
+	bool  hasSubLinks;
+
+	securityQuals = NIL;
+	pstate = make_parsestate(NULL);
+
+	rel = table_open(relId, NoLock);
+	nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, false);
+	rte = nsitem->p_rte;
+
+	get_row_security_policies(query, rte, rt_index,
+							  &securityQuals, &withCheckOptions,
+							  &hasRowSecurity, &hasSubLinks);
+
+	/*
+	 * Make sure the query is marked correctly if row level security
+	 * applies, or if the new quals had sublinks.
+	 */
+	if (hasRowSecurity)
+		query->hasRowSecurity = true;
+	if (hasSubLinks)
+		query->hasSubLinks = true;
+
+	table_close(rel, NoLock);
+
+	return securityQuals;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dc5872f988..571f5c4cb9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -51,6 +51,7 @@
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
+#include "commands/matview.h"
 #include "commands/event_trigger.h"
 #include "commands/policy.h"
 #include "commands/sequence.h"
@@ -3415,6 +3416,14 @@ renameatt_internal(Oid myrelid,
 	targetrelation = relation_open(myrelid, AccessExclusiveLock);
 	renameatt_check(myrelid, RelationGetForm(targetrelation), recursing);
 
+	/*
+	 * Don't rename IVM columns.
+	 */
+	if (RelationIsIVM(targetrelation) && isIvmName(oldattname))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("IVM column can not be renamed")));
+
 	/*
 	 * if the 'recurse' flag is set then we are supposed to rename this
 	 * attribute in all classes that inherit from 'relname' (as well as in
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index eb28657791..504ae3546e 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2464,6 +2464,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(relisivm);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 521a87a8ea..56431f5aee 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2776,6 +2776,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(relisivm);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 801c41b978..8cc0b93213 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3259,6 +3259,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index cc6dcb7220..37dff1ec27 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1441,6 +1441,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index cb9e177b5e..dcfd1f3fc0 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -36,6 +36,7 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 #include "utils/varlena.h"
+#include "commands/matview.h"
 
 
 /*
@@ -79,7 +80,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							int count, int offset,
 							int rtindex, int sublevels_up,
 							int location, bool include_dropped,
-							List **colnames, List **colvars);
+							List **colnames, List **colvars, bool is_ivm);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
 
@@ -1433,6 +1434,7 @@ addRangeTableEntry(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -1521,6 +1523,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -2676,7 +2679,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 						expandTupleDesc(tupdesc, rte->eref,
 										rtfunc->funccolcount, atts_done,
 										rtindex, sublevels_up, location,
-										include_dropped, colnames, colvars);
+										include_dropped, colnames, colvars, false);
 					}
 					else if (functypclass == TYPEFUNC_SCALAR)
 					{
@@ -2944,7 +2947,7 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
 	expandTupleDesc(rel->rd_att, eref, rel->rd_att->natts, 0,
 					rtindex, sublevels_up,
 					location, include_dropped,
-					colnames, colvars);
+					colnames, colvars, RelationIsIVM(rel));
 	relation_close(rel, AccessShareLock);
 }
 
@@ -2961,7 +2964,7 @@ static void
 expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 				int rtindex, int sublevels_up,
 				int location, bool include_dropped,
-				List **colnames, List **colvars)
+				List **colnames, List **colvars, bool is_ivm)
 {
 	ListCell   *aliascell;
 	int			varattno;
@@ -2974,6 +2977,9 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 	{
 		Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
 
+		if (is_ivm && isIvmName(NameStr(attr->attname)) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		if (attr->attisdropped)
 		{
 			if (include_dropped)
@@ -3127,6 +3133,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 		Var		   *varnode = (Var *) lfirst(var);
 		TargetEntry *te;
 
+		/* if transform * into columnlist with IMMV, remove IVM columns */
+		if (rte->relisivm && isIvmName(label) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		te = makeTargetEntry((Expr *) varnode,
 							 (AttrNumber) pstate->p_next_resno++,
 							 label,
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 185bf5fbff..d8a8b66196 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -776,7 +776,8 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 														attr->atttypmod))));
 	}
 
-	if (i != resultDesc->natts)
+	/* No check for materialized views since this could have special columns for IVM */
+	if ((!isSelect || requireColumnNameMatch) && i != resultDesc->natts)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 isSelect ?
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d8e8715ed1..2929a6872e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11747,4 +11747,12 @@
   prorettype => 'bytea', proargtypes => 'pg_brin_minmax_multi_summary',
   prosrc => 'brin_minmax_multi_summary_send' },
 
+# IVM
+{ oid => '786', descr => 'ivm trigger (before)',
+  proname => 'IVM_immediate_before', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_before' },
+{ oid => '787', descr => 'ivm trigger (after)',
+  proname => 'IVM_immediate_maintenance', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_maintenance' },
+
 ]
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index 54a38491fb..c369b3ba5e 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -16,6 +16,7 @@
 
 #include "catalog/objectaddress.h"
 #include "nodes/params.h"
+#include "nodes/pathnodes.h"
 #include "parser/parse_node.h"
 #include "tcop/dest.h"
 #include "utils/queryenvironment.h"
@@ -25,6 +26,11 @@ extern ObjectAddress ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *st
 									   ParamListInfo params, QueryEnvironment *queryEnv,
 									   QueryCompletion *qc);
 
+extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create);
+extern void CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create);
+
+extern Query *rewriteQueryForIMMV(Query *query, List *colNames);
+
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
 
 extern DestReceiver *CreateIntoRelDestReceiver(IntoClause *intoClause);
diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h
index a067da39d2..ec479db513 100644
--- a/src/include/commands/matview.h
+++ b/src/include/commands/matview.h
@@ -15,6 +15,7 @@
 #define MATVIEW_H
 
 #include "catalog/objectaddress.h"
+#include "fmgr.h"
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
 #include "tcop/dest.h"
@@ -23,6 +24,8 @@
 
 extern void SetMatViewPopulatedState(Relation relation, bool newstate);
 
+extern void SetMatViewIVMState(Relation relation, bool newstate);
+
 extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										ParamListInfo params, QueryCompletion *qc);
 
@@ -30,4 +33,9 @@ extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
 
 extern bool MatViewIncrementalMaintenanceIsEnabled(void);
 
+extern Datum IVM_immediate_before(PG_FUNCTION_ARGS);
+extern Datum IVM_immediate_maintenance(PG_FUNCTION_ARGS);
+extern void AtAbort_IVM(void);
+extern bool isIvmName(const char *s);
+
 #endif							/* MATVIEW_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1617702d9d..3aa2d9f61c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1036,6 +1036,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		relisivm;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -2195,6 +2196,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		ivm;			/* incremental view maintenance is used by materialized view */
 } CreateStmt;
 
 /* ----------
-- 
2.17.1

v26-0006-Add-Incremental-View-Maintenance-support-to-psql.patchtext/x-diff; name=v26-0006-Add-Incremental-View-Maintenance-support-to-psql.patchDownload
From 7623ceec75607a079eb2240e1ee2520c4a31893b Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:21:54 +0900
Subject: [PATCH v26 06/10] Add Incremental View Maintenance support to psql

Add tab completion and meta-command output for IVM.
---
 src/bin/psql/describe.c     | 32 +++++++++++++++++++++++++++++++-
 src/bin/psql/tab-complete.c | 14 +++++++++-----
 2 files changed, 40 insertions(+), 6 deletions(-)

diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 9229eacb6d..b880f5fe77 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1480,6 +1480,7 @@ describeOneTableDetails(const char *schemaname,
 		char		relpersistence;
 		char		relreplident;
 		char	   *relam;
+		bool		isivm;
 	}			tableinfo;
 	bool		show_column_details = false;
 
@@ -1492,7 +1493,26 @@ describeOneTableDetails(const char *schemaname,
 	initPQExpBuffer(&tmpbuf);
 
 	/* Get general table info */
-	if (pset.sversion >= 120000)
+	if (pset.sversion >= 150000)
+	{
+		printfPQExpBuffer(&buf,
+						  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+						  "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, "
+						  "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
+						  "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
+						  "c.relpersistence, c.relreplident, am.amname, "
+						  "c.relisivm\n"
+						  "FROM pg_catalog.pg_class c\n "
+						  "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+						  "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
+						  "WHERE c.oid = '%s';",
+						  (verbose ?
+						   "pg_catalog.array_to_string(c.reloptions || "
+						   "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+						   : "''"),
+						  oid);
+	}
+	else if (pset.sversion >= 120000)
 	{
 		printfPQExpBuffer(&buf,
 						  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
@@ -1612,6 +1632,10 @@ describeOneTableDetails(const char *schemaname,
 			(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
 	else
 		tableinfo.relam = NULL;
+	if (pset.sversion >= 150000)
+		tableinfo.isivm = strcmp(PQgetvalue(res, 0, 15), "t") == 0;
+	else
+		tableinfo.isivm = false;
 	PQclear(res);
 	res = NULL;
 
@@ -3397,6 +3421,12 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
 			printTableAddFooter(&cont, buf.data);
 		}
+
+		/* Incremental view maintance info */
+		if (verbose && tableinfo.relkind == RELKIND_MATVIEW && tableinfo.isivm)
+		{
+			printTableAddFooter(&cont, _("Incremental view maintenance: yes"));
+		}
 	}
 
 	/* reloptions, if verbose */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 17172827a9..6ee7c356d4 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1181,6 +1181,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"INCREMENTAL MATERIALIZED VIEW", NULL, NULL, &Query_for_list_of_matviews, NULL, THING_NO_DROP | THING_NO_ALTER},
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -3021,7 +3022,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
-		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
+		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW", "INCREMENTAL MATERIALIZED VIEW");
 	/* Complete PARTITION BY with RANGE ( or LIST ( or ... */
 	else if (TailMatches("PARTITION", "BY"))
 		COMPLETE_WITH("RANGE (", "LIST (", "HASH (");
@@ -3337,13 +3338,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches("CREATE", "MATERIALIZED"))
+	else if (Matches("CREATE", "MATERIALIZED") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED"))
 		COMPLETE_WITH("VIEW");
-	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	/* Complete CREATE MATERIALIZED VIEW <name> with AS  */
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny) ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH("SELECT");
 
 /* CREATE EVENT TRIGGER */
-- 
2.17.1

v26-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patchtext/x-diff; name=v26-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patchDownload
From 12b76b74328042f484d21f2a085887dc07ca65fe Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Wed, 11 Nov 2020 17:01:25 +0900
Subject: [PATCH v26 05/10] Add Incremental View Maintenance support to pg_dump

Support CREATE INCREMENTAL MATERIALIZED VIEW syntax.
---
 src/bin/pg_dump/pg_dump.c        | 18 +++++++++++++++---
 src/bin/pg_dump/pg_dump.h        |  1 +
 src/bin/pg_dump/t/002_pg_dump.pl | 15 +++++++++++++++
 3 files changed, 31 insertions(+), 3 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4dd24b8c89..42bcd5509b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6032,6 +6032,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_relacl;
 	int			i_acldefault;
 	int			i_ispartition;
+	int			i_isivm;
 
 	/*
 	 * Find all the tables and table-like objects.
@@ -6134,10 +6135,17 @@ getTables(Archive *fout, int *numTables)
 
 	if (fout->remoteVersion >= 100000)
 		appendPQExpBufferStr(query,
-							 "c.relispartition AS ispartition ");
+							 "c.relispartition AS ispartition, ");
 	else
 		appendPQExpBufferStr(query,
-							 "false AS ispartition ");
+							 "false AS ispartition, ");
+
+	if (fout->remoteVersion >= 150000)
+		appendPQExpBufferStr(query,
+							 "c.relisivm AS isivm ");
+	else
+		appendPQExpBufferStr(query,
+							 "false AS isivm ");
 
 	/*
 	 * Left join to pg_depend to pick up dependency info linking sequences to
@@ -6246,6 +6254,7 @@ getTables(Archive *fout, int *numTables)
 	i_relacl = PQfnumber(res, "relacl");
 	i_acldefault = PQfnumber(res, "acldefault");
 	i_ispartition = PQfnumber(res, "ispartition");
+	i_isivm = PQfnumber(res, "isivm");
 
 	if (dopt->lockWaitTimeout)
 	{
@@ -6323,6 +6332,7 @@ getTables(Archive *fout, int *numTables)
 			tblinfo[i].amname = pg_strdup(PQgetvalue(res, i, i_amname));
 		tblinfo[i].is_identity_sequence = (strcmp(PQgetvalue(res, i, i_is_identity_sequence), "t") == 0);
 		tblinfo[i].ispartition = (strcmp(PQgetvalue(res, i, i_ispartition), "t") == 0);
+		tblinfo[i].isivm = (strcmp(PQgetvalue(res, i, i_isivm), "t") == 0);
 
 		/* other fields were zeroed above */
 
@@ -15058,9 +15068,11 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
-		appendPQExpBuffer(q, "CREATE %s%s %s",
+		appendPQExpBuffer(q, "CREATE %s%s%s %s",
 						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
 						  "UNLOGGED " : "",
+						  tbinfo->relkind == RELKIND_MATVIEW && tbinfo->isivm ?
+						  "INCREMENTAL " : "",
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 772dc0cf7a..756f54360c 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -318,6 +318,7 @@ typedef struct _tableInfo
 	bool		dummy_view;		/* view's real definition must be postponed */
 	bool		postponed_def;	/* matview must be postponed into post-data */
 	bool		ispartition;	/* is table a partition? */
+	bool		isivm;			/* is incrementally maintainable materialized view? */
 
 	/*
 	 * These fields are computed only if we decide the table is interesting
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 3e55ff26f8..f2b29cc0a1 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2233,6 +2233,21 @@ my %tests = (
 		  { exclude_dump_test_schema => 1, no_toast_compression => 1, },
 	},
 
+	'CREATE MATERIALIZED VIEW matview_ivm' => {
+		create_order => 21,
+		create_sql   => 'CREATE INCREMENTAL MATERIALIZED VIEW dump_test.matview_ivm (col1) AS
+					   SELECT col1 FROM dump_test.test_table;',
+		regexp => qr/^
+			\QCREATE INCREMENTAL MATERIALIZED VIEW dump_test.matview_ivm AS\E
+			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QFROM dump_test.test_table\E
+			\n\s+\QWITH NO DATA;\E
+			/xm,
+		like =>
+		  { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, },
+	},
+
 	'CREATE POLICY p1 ON test_table' => {
 		create_order => 22,
 		create_sql   => 'CREATE POLICY p1 ON dump_test.test_table
-- 
2.17.1

v26-0004-Allow-to-prolong-life-span-of-transition-tables-.patchtext/x-diff; name=v26-0004-Allow-to-prolong-life-span-of-transition-tables-.patchDownload
From aa26b66568853e32939f1553099c9d9845bce6f6 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:09:45 +0900
Subject: [PATCH v26 04/10] Allow to prolong life span of transition tables
 until transaction end

Originally, tuplestores of AFTER trigger's transition tables were
freed for each query depth. For our IVM implementation, we would like
to prolong life of the tuplestores because we have to preserve them
for a whole query assuming that some base tables might be changed
in some trigger functions.
---
 src/backend/commands/trigger.c | 80 +++++++++++++++++++++++++++++++++-
 src/include/commands/trigger.h |  2 +
 2 files changed, 80 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index e08bd9a370..adc853a570 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3651,6 +3651,10 @@ typedef struct AfterTriggerEventList
  * end of the list, so it is relatively easy to discard them.  The event
  * list chunks themselves are stored in event_cxt.
  *
+ * prolonged_tuplestored is a list of transition table tuplestores whose
+ * life are prolonged to the end of the outmost query instead of each nested
+ * query.
+ *
  * query_depth is the current depth of nested AfterTriggerBeginQuery calls
  * (-1 when the stack is empty).
  *
@@ -3716,6 +3720,7 @@ typedef struct AfterTriggersData
 	SetConstraintState state;	/* the active S C state */
 	AfterTriggerEventList events;	/* deferred-event list */
 	MemoryContext event_cxt;	/* memory context for events, if any */
+	List   *prolonged_tuplestores;	/* list of prolonged tuplestores */
 
 	/* per-query-level data: */
 	AfterTriggersQueryData *query_stack;	/* array of structs shown below */
@@ -3751,6 +3756,7 @@ struct AfterTriggersTableData
 	bool		closed;			/* true when no longer OK to add tuples */
 	bool		before_trig_done;	/* did we already queue BS triggers? */
 	bool		after_trig_done;	/* did we already queue AS triggers? */
+	bool		prolonged;			/* are transition tables prolonged? */
 	AfterTriggerEventList after_trig_events;	/* if so, saved list pointer */
 	Tuplestorestate *old_tuplestore;	/* "old" transition table, if any */
 	Tuplestorestate *new_tuplestore;	/* "new" transition table, if any */
@@ -4539,6 +4545,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
 }
 
 
+/*
+ * SetTransitionTablePreserved
+ *
+ * Prolong lifespan of transition tables corresponding specified relid and
+ * command type to the end of the outmost query instead of each nested query.
+ * This enables to use nested AFTER trigger's transition tables from outer
+ * query's triggers.  Currently, only immediate incremental view maintenance
+ * uses this.
+ */
+void
+SetTransitionTablePreserved(Oid relid, CmdType cmdType)
+{
+	AfterTriggersTableData *table;
+	AfterTriggersQueryData *qs;
+	bool		found = false;
+	ListCell   *lc;
+
+	/* Check state, like AfterTriggerSaveEvent. */
+	if (afterTriggers.query_depth < 0)
+		elog(ERROR, "SetTransitionTablePreserved() called outside of query");
+
+	qs = &afterTriggers.query_stack[afterTriggers.query_depth];
+
+	foreach(lc, qs->tables)
+	{
+		table = (AfterTriggersTableData *) lfirst(lc);
+		if (table->relid == relid && table->cmdType == cmdType &&
+			table->closed)
+		{
+			table->prolonged = true;
+			found = true;
+		}
+	}
+
+	if (!found)
+		elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType);
+}
+
+
 /*
  * GetAfterTriggersTableData
  *
@@ -4733,6 +4778,7 @@ AfterTriggerBeginXact(void)
 	 */
 	afterTriggers.firing_counter = (CommandId) 1;	/* mustn't be 0 */
 	afterTriggers.query_depth = -1;
+	afterTriggers.prolonged_tuplestores = NIL;
 
 	/*
 	 * Verify that there is no leftover state remaining.  If these assertions
@@ -4893,11 +4939,29 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
 		ts = table->old_tuplestore;
 		table->old_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+		{
+			if (table->prolonged && afterTriggers.query_depth > 0)
+			{
+				MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext);
+				afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts);
+				MemoryContextSwitchTo(oldcxt);
+			}
+			else
+				tuplestore_end(ts);
+		}
 		ts = table->new_tuplestore;
 		table->new_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+		{
+			if (table->prolonged && afterTriggers.query_depth > 0)
+			{
+				MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext);
+				afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts);
+				MemoryContextSwitchTo(oldcxt);
+			}
+			else
+				tuplestore_end(ts);
+		}
 		if (table->storeslot)
 			ExecDropSingleTupleTableSlot(table->storeslot);
 	}
@@ -4909,6 +4973,18 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
 	 */
 	qs->tables = NIL;
 	list_free_deep(tables);
+
+	/* Release prolonged tuplestores at the end of the outmost query */
+	if (afterTriggers.query_depth == 0)
+	{
+		foreach(lc, afterTriggers.prolonged_tuplestores)
+		{
+			ts = (Tuplestorestate *) lfirst(lc);
+			if (ts)
+				tuplestore_end(ts);
+		}
+		afterTriggers.prolonged_tuplestores = NIL;
+	}
 }
 
 
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index e1271420e5..697a6f6d55 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -255,6 +255,8 @@ extern void AfterTriggerEndSubXact(bool isCommit);
 extern void AfterTriggerSetState(ConstraintsSetStmt *stmt);
 extern bool AfterTriggerPendingOnRel(Oid relid);
 
+extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType);
+
 
 /*
  * in utils/adt/ri_triggers.c
-- 
2.17.1

v26-0003-Add-new-deptype-option-m-in-pg_depend-system-cat.patchtext/x-diff; name=v26-0003-Add-new-deptype-option-m-in-pg_depend-system-cat.patchDownload
From fc7718dac617868dac1b58cfc21151523ade31b9 Mon Sep 17 00:00:00 2001
From: Takuma Hoshiai <takuma.hoshiai@gmail.com>
Date: Fri, 17 Jan 2020 16:04:14 +0900
Subject: [PATCH v26 03/10] Add new deptype option 'm' in pg_depend system
 catalog

The deptype option 'm' mean specific database obejects referenced Incrementally
Maintainable Materialized View(IMMV). If set NO DATA flag to IMVM, these
database objects must be dropped.
---
 src/backend/catalog/dependency.c | 2 ++
 src/include/catalog/dependency.h | 3 ++-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index ab9e42d7d1..c7d074d8e6 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -598,6 +598,7 @@ findDependentObjects(const ObjectAddress *object,
 			case DEPENDENCY_NORMAL:
 			case DEPENDENCY_AUTO:
 			case DEPENDENCY_AUTO_EXTENSION:
+			case DEPENDENCY_IMMV:
 				/* no problem */
 				break;
 
@@ -915,6 +916,7 @@ findDependentObjects(const ObjectAddress *object,
 				subflags = DEPFLAG_AUTO;
 				break;
 			case DEPENDENCY_INTERNAL:
+			case DEPENDENCY_IMMV:
 				subflags = DEPFLAG_INTERNAL;
 				break;
 			case DEPENDENCY_PARTITION_PRI:
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 344482ec87..b4578610ea 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -36,7 +36,8 @@ typedef enum DependencyType
 	DEPENDENCY_PARTITION_PRI = 'P',
 	DEPENDENCY_PARTITION_SEC = 'S',
 	DEPENDENCY_EXTENSION = 'e',
-	DEPENDENCY_AUTO_EXTENSION = 'x'
+	DEPENDENCY_AUTO_EXTENSION = 'x',
+	DEPENDENCY_IMMV = 'm'
 } DependencyType;
 
 /*
-- 
2.17.1

v26-0002-Add-relisivm-column-to-pg_class-system-catalog.patchtext/x-diff; name=v26-0002-Add-relisivm-column-to-pg_class-system-catalog.patchDownload
From 5d009c50aae2925a2c490e83fef9e3b83da10530 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:07:23 +0900
Subject: [PATCH v26 02/10] Add relisivm column to pg_class system catalog

If this boolean column is true, a relations is Incrementally Maintainable
Materialized View (IMMV). This is set when IMMV is created.
---
 src/backend/catalog/heap.c          |  1 +
 src/backend/catalog/index.c         |  1 +
 src/backend/utils/cache/lsyscache.c | 24 ++++++++++++++++++++++++
 src/backend/utils/cache/relcache.c  |  2 ++
 src/include/catalog/pg_class.h      |  3 +++
 src/include/utils/lsyscache.h       |  1 +
 src/include/utils/rel.h             |  2 ++
 7 files changed, 34 insertions(+)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 7e99de88b3..212a6293aa 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -935,6 +935,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
+	values[Anum_pg_class_relisivm - 1] = BoolGetDatum(rd_rel->relisivm);
 	if (relacl != (Datum) 0)
 		values[Anum_pg_class_relacl - 1] = relacl;
 	else
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 5e3fc2b35d..d861211f66 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -982,6 +982,7 @@ index_create(Relation heapRelation,
 	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
 	indexRelation->rd_rel->relam = accessMethodObjectId;
 	indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid);
+	indexRelation->rd_rel->relisivm = false;
 
 	/*
 	 * store index's pg_class entry
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1b7e11b93e..3275963886 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2023,6 +2023,30 @@ get_rel_relispartition(Oid relid)
 		return false;
 }
 
+/*
+ * get_rel_relisivm
+ *
+ *		Returns the relisivm flag associated with a given relation.
+ */
+bool
+get_rel_relisivm(Oid relid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp);
+		bool		result;
+
+		result = reltup->relisivm;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return false;
+}
+
 /*
  * get_rel_tablespace
  *
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fccffce572..149c84535c 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1903,6 +1903,8 @@ formrdesc(const char *relationName, Oid relationReltype,
 
 	/* ... and they're always populated, too */
 	relation->rd_rel->relispopulated = true;
+	/* ... and they're always no ivm, too */
+	relation->rd_rel->relisivm = false;
 
 	relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
 	relation->rd_rel->relpages = 0;
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 304e8c18d5..fbaa1438d6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -119,6 +119,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
 	/* is relation a partition? */
 	bool		relispartition BKI_DEFAULT(f);
 
+	/* is relation a matview with ivm? */
+	bool		relisivm BKI_DEFAULT(f);
+
 	/* link to original rel during table rewrite; otherwise 0 */
 	Oid			relrewrite BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_class);
 
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index b8dd27d4a9..cc2f635122 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -137,6 +137,7 @@ extern Oid	get_rel_namespace(Oid relid);
 extern Oid	get_rel_type_id(Oid relid);
 extern char get_rel_relkind(Oid relid);
 extern bool get_rel_relispartition(Oid relid);
+extern bool get_rel_relisivm(Oid relid);
 extern Oid	get_rel_tablespace(Oid relid);
 extern char get_rel_persistence(Oid relid);
 extern Oid	get_transform_fromsql(Oid typid, Oid langid, List *trftypes);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 3b4ab65ae2..fc6b6ddec8 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -650,6 +650,8 @@ RelationGetSmgr(Relation rel)
  */
 #define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated)
 
+#define RelationIsIVM(relation) ((relation)->rd_rel->relisivm)
+
 /*
  * RelationIsAccessibleInLogicalDecoding
  *		True if we need to log enough information to have access via
-- 
2.17.1

v26-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patchtext/x-diff; name=v26-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patchDownload
From 39645077b75d0d7ec4ceadf4cfaf581c433297c0 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:05:02 +0900
Subject: [PATCH v26 01/10] Add a syntax to create Incrementally Maintainable
 Materialized Views

Allow to create Incrementally Maintainable Materialized View (IMMV)
by using INCREMENTAL option in CREATE MATERIALIZED VIEW command
as follow:

     CREATE [INCREMANTAL] MATERIALIZED VIEW xxxxx AS SELECT ....;
---
 src/backend/nodes/copyfuncs.c  |  1 +
 src/backend/nodes/equalfuncs.c |  1 +
 src/backend/nodes/outfuncs.c   |  1 +
 src/backend/nodes/readfuncs.c  |  1 +
 src/backend/parser/gram.y      | 32 +++++++++++++++++++++-----------
 src/include/nodes/primnodes.h  |  1 +
 src/include/parser/kwlist.h    |  1 +
 7 files changed, 27 insertions(+), 11 deletions(-)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d4f8455a2b..eb28657791 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1426,6 +1426,7 @@ _copyIntoClause(const IntoClause *from)
 	COPY_STRING_FIELD(tableSpaceName);
 	COPY_NODE_FIELD(viewQuery);
 	COPY_SCALAR_FIELD(skipData);
+	COPY_SCALAR_FIELD(ivm);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index f1002afe7a..521a87a8ea 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -155,6 +155,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
 	COMPARE_STRING_FIELD(tableSpaceName);
 	COMPARE_NODE_FIELD(viewQuery);
 	COMPARE_SCALAR_FIELD(skipData);
+	COMPARE_SCALAR_FIELD(ivm);
 
 	return true;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6bdad462c7..801c41b978 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1119,6 +1119,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
 	WRITE_STRING_FIELD(tableSpaceName);
 	WRITE_NODE_FIELD(viewQuery);
 	WRITE_BOOL_FIELD(skipData);
+	WRITE_BOOL_FIELD(ivm);
 }
 
 static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3f68f7c18d..cc6dcb7220 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -563,6 +563,7 @@ _readIntoClause(void)
 	READ_STRING_FIELD(tableSpaceName);
 	READ_NODE_FIELD(viewQuery);
 	READ_BOOL_FIELD(skipData);
+	READ_BOOL_FIELD(ivm);
 
 	READ_DONE();
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a03b33b53b..e80ce50f82 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -459,6 +459,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target create_mv_target
+%type <boolean>	incremental
 
 %type <defelt>	createfunc_opt_item common_func_opt_item dostmt_opt_item
 %type <fun_param> func_arg func_arg_with_default table_func_column aggr_arg
@@ -693,7 +694,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
 	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
-	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+	INCLUDING INCREMENT INCREMENTAL INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
 	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
 	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -4368,30 +4369,32 @@ opt_with_data:
  *****************************************************************************/
 
 CreateMatViewStmt:
-		CREATE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
+		CREATE OptNoLog incremental MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $7;
-					ctas->into = $5;
+					ctas->query = $8;
+					ctas->into = $6;
 					ctas->objtype = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = false;
 					/* cram additional flags into the IntoClause */
-					$5->rel->relpersistence = $2;
-					$5->skipData = !($8);
+					$6->rel->relpersistence = $2;
+					$6->skipData = !($9);
+					$6->ivm = $3;
 					$$ = (Node *) ctas;
 				}
-		| CREATE OptNoLog MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
+		| CREATE OptNoLog incremental MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $10;
-					ctas->into = $8;
+					ctas->query = $11;
+					ctas->into = $9;
 					ctas->objtype = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = true;
 					/* cram additional flags into the IntoClause */
-					$8->rel->relpersistence = $2;
-					$8->skipData = !($11);
+					$9->rel->relpersistence = $2;
+					$9->skipData = !($12);
+					$9->ivm = $3;
 					$$ = (Node *) ctas;
 				}
 		;
@@ -4408,9 +4411,14 @@ create_mv_target:
 					$$->tableSpaceName = $5;
 					$$->viewQuery = NULL;		/* filled at analysis time */
 					$$->skipData = false;		/* might get changed later */
+					$$->ivm = false;
 				}
 		;
 
+incremental:	INCREMENTAL				{ $$ = true; }
+				| /*EMPTY*/				{ $$ = false; }
+		;
+
 OptNoLog:	UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
 		;
@@ -15787,6 +15795,7 @@ unreserved_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
@@ -16339,6 +16348,7 @@ bare_label_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index dab5c4ff5d..bc1bcbda13 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -117,6 +117,7 @@ typedef struct IntoClause
 	char	   *tableSpaceName; /* table space to use, or NULL */
 	Node	   *viewQuery;		/* materialized view's SELECT query */
 	bool		skipData;		/* true for WITH NO DATA */
+	bool		ivm;			/* true for WITH IVM */
 } IntoClause;
 
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index bcef7eed2f..a43af4a597 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -205,6 +205,7 @@ PG_KEYWORD("in", IN_P, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("incremental", INCREMENTAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD, BARE_LABEL)
-- 
2.17.1

#217Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Zhihong Yu (#213)
Re: Implementing Incremental View Maintenance

Hello Zhihong Yu,

I already replied to your comments before, but I forgot to include
the list to CC, so I resend the same again. Sorry for the duplicate
emails.

On Thu, 3 Feb 2022 09:51:52 -0800
Zhihong Yu <zyu@yugabyte.com> wrote:

For CreateIndexOnIMMV():

+           ereport(NOTICE,
+                   (errmsg("could not create an index on materialized view
\"%s\" automatically",
...
+           return;
+       }

Should the return type be changed to bool so that the caller knows whether
the index creation succeeds ?
If index creation is unsuccessful, should the call
to CreateIvmTriggersOnBaseTables() be skipped ?

CreateIvmTriggersOnBaseTables() have to be called regardless
of whether an index is created successfully or not, so I think
CreateindexOnIMMV() doesn't have to return the result for now.

For check_ivm_restriction_walker():

+           break;
+           expression_tree_walker(node, check_ivm_restriction_walker,
NULL);
+           break;

Something is missing between the break and expression_tree_walker().

Yes, it's my mistake during making the patch-set. I fixed it in the
updated patch I attached in the other post.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#218Greg Stark
stark@mit.edu
In reply to: Yugo NAGATA (#217)
Re: Implementing Incremental View Maintenance

This patch has bitrotted due to some other patch affecting trigger.c.

Could you post a rebase?

This is the last week of the CF before feature freeze so time is of the essence.

#219Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Greg Stark (#218)
9 attachment(s)
Re: Implementing Incremental View Maintenance

Hi,

On Fri, 1 Apr 2022 11:09:16 -0400
Greg Stark <stark@mit.edu> wrote:

This patch has bitrotted due to some other patch affecting trigger.c.

Could you post a rebase?

This is the last week of the CF before feature freeze so time is of the essence.

I attached a rebased patch-set.

Also, I made the folowing changes from the previous.

1. Fix to not use a new deptye

In the previous patch, we introduced a new deptye 'm' into pg_depend.
This deptype was used for looking for IVM triggers to be removed at
REFRESH WITH NO DATA. However, we decided to not use it for reducing
unnecessary change in the core code. Currently, the trigger name and
dependent objclass are used at that time instead of it.

As a result, the number of patches are reduced to nine from ten.

2. Bump the version numbers in psql and pg_dump

This feature's target is PG 16 now.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

v27-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patchtext/x-diff; name=v27-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patchDownload
From 5437965959d6dc512328ffee8ff9dea6b22e48f8 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:05:02 +0900
Subject: [PATCH v27 1/9] Add a syntax to create Incrementally Maintainable
 Materialized Views

Allow to create Incrementally Maintainable Materialized View (IMMV)
by using INCREMENTAL option in CREATE MATERIALIZED VIEW command
as follow:

     CREATE [INCREMANTAL] MATERIALIZED VIEW xxxxx AS SELECT ....;
---
 src/backend/nodes/copyfuncs.c  |  1 +
 src/backend/nodes/equalfuncs.c |  1 +
 src/backend/nodes/outfuncs.c   |  1 +
 src/backend/nodes/readfuncs.c  |  1 +
 src/backend/parser/gram.y      | 32 +++++++++++++++++++++-----------
 src/include/nodes/primnodes.h  |  1 +
 src/include/parser/kwlist.h    |  1 +
 7 files changed, 27 insertions(+), 11 deletions(-)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 836f427ea8..0490bce664 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1434,6 +1434,7 @@ _copyIntoClause(const IntoClause *from)
 	COPY_STRING_FIELD(tableSpaceName);
 	COPY_NODE_FIELD(viewQuery);
 	COPY_SCALAR_FIELD(skipData);
+	COPY_SCALAR_FIELD(ivm);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e013c1bbfe..55f41263ee 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -211,6 +211,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
 	COMPARE_STRING_FIELD(tableSpaceName);
 	COMPARE_NODE_FIELD(viewQuery);
 	COMPARE_SCALAR_FIELD(skipData);
+	COMPARE_SCALAR_FIELD(ivm);
 
 	return true;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6a02f81ad5..cfd3ce68b4 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1127,6 +1127,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
 	WRITE_STRING_FIELD(tableSpaceName);
 	WRITE_NODE_FIELD(viewQuery);
 	WRITE_BOOL_FIELD(skipData);
+	WRITE_BOOL_FIELD(ivm);
 }
 
 static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ddf76ac778..5165fb3b93 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -605,6 +605,7 @@ _readIntoClause(void)
 	READ_STRING_FIELD(tableSpaceName);
 	READ_NODE_FIELD(viewQuery);
 	READ_BOOL_FIELD(skipData);
+	READ_BOOL_FIELD(ivm);
 
 	READ_DONE();
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c9941d9cb4..aa134eafc9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -468,6 +468,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target create_mv_target
+%type <boolean>	incremental
 
 %type <defelt>	createfunc_opt_item common_func_opt_item dostmt_opt_item
 %type <fun_param> func_arg func_arg_with_default table_func_column aggr_arg
@@ -801,7 +802,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
 	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
-	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+	INCLUDING INCREMENT INCREMENTAL INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
 	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
 	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -4492,30 +4493,32 @@ opt_with_data:
  *****************************************************************************/
 
 CreateMatViewStmt:
-		CREATE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
+		CREATE OptNoLog incremental MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $7;
-					ctas->into = $5;
+					ctas->query = $8;
+					ctas->into = $6;
 					ctas->objtype = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = false;
 					/* cram additional flags into the IntoClause */
-					$5->rel->relpersistence = $2;
-					$5->skipData = !($8);
+					$6->rel->relpersistence = $2;
+					$6->skipData = !($9);
+					$6->ivm = $3;
 					$$ = (Node *) ctas;
 				}
-		| CREATE OptNoLog MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
+		| CREATE OptNoLog incremental MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $10;
-					ctas->into = $8;
+					ctas->query = $11;
+					ctas->into = $9;
 					ctas->objtype = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = true;
 					/* cram additional flags into the IntoClause */
-					$8->rel->relpersistence = $2;
-					$8->skipData = !($11);
+					$9->rel->relpersistence = $2;
+					$9->skipData = !($12);
+					$9->ivm = $3;
 					$$ = (Node *) ctas;
 				}
 		;
@@ -4532,9 +4535,14 @@ create_mv_target:
 					$$->tableSpaceName = $5;
 					$$->viewQuery = NULL;		/* filled at analysis time */
 					$$->skipData = false;		/* might get changed later */
+					$$->ivm = false;
 				}
 		;
 
+incremental:	INCREMENTAL				{ $$ = true; }
+				| /*EMPTY*/				{ $$ = false; }
+		;
+
 OptNoLog:	UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
 		;
@@ -17007,6 +17015,7 @@ unreserved_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
@@ -17588,6 +17597,7 @@ bare_label_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 66d32fc006..acac38b91c 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -126,6 +126,7 @@ typedef struct IntoClause
 	char	   *tableSpaceName; /* table space to use, or NULL */
 	Node	   *viewQuery;		/* materialized view's SELECT query */
 	bool		skipData;		/* true for WITH NO DATA */
+	bool		ivm;			/* true for WITH IVM */
 } IntoClause;
 
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 8a2ab405a2..7cd9b54a01 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -210,6 +210,7 @@ PG_KEYWORD("in", IN_P, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("incremental", INCREMENTAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD, BARE_LABEL)
-- 
2.17.1

v27-0002-Add-relisivm-column-to-pg_class-system-catalog.patchtext/x-diff; name=v27-0002-Add-relisivm-column-to-pg_class-system-catalog.patchDownload
From bd5bcbb73ab7ab535a71021fa80ef6c0a5543d6a Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:07:23 +0900
Subject: [PATCH v27 2/9] Add relisivm column to pg_class system catalog

If this boolean column is true, a relations is Incrementally Maintainable
Materialized View (IMMV). This is set when IMMV is created.
---
 src/backend/catalog/heap.c          |  1 +
 src/backend/catalog/index.c         |  1 +
 src/backend/utils/cache/lsyscache.c | 24 ++++++++++++++++++++++++
 src/backend/utils/cache/relcache.c  |  2 ++
 src/include/catalog/pg_class.h      |  3 +++
 src/include/utils/lsyscache.h       |  1 +
 src/include/utils/rel.h             |  2 ++
 7 files changed, 34 insertions(+)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9b512ccd3c..a9a314fefe 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -920,6 +920,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
+	values[Anum_pg_class_relisivm - 1] = BoolGetDatum(rd_rel->relisivm);
 	if (relacl != (Datum) 0)
 		values[Anum_pg_class_relacl - 1] = relacl;
 	else
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index fd389c28d8..4d1c99a8bd 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -982,6 +982,7 @@ index_create(Relation heapRelation,
 	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
 	indexRelation->rd_rel->relam = accessMethodObjectId;
 	indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid);
+	indexRelation->rd_rel->relisivm = false;
 
 	/*
 	 * store index's pg_class entry
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1b7e11b93e..3275963886 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2023,6 +2023,30 @@ get_rel_relispartition(Oid relid)
 		return false;
 }
 
+/*
+ * get_rel_relisivm
+ *
+ *		Returns the relisivm flag associated with a given relation.
+ */
+bool
+get_rel_relisivm(Oid relid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp);
+		bool		result;
+
+		result = reltup->relisivm;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return false;
+}
+
 /*
  * get_rel_tablespace
  *
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 43f14c233d..a67ba4d8b1 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1923,6 +1923,8 @@ formrdesc(const char *relationName, Oid relationReltype,
 
 	/* ... and they're always populated, too */
 	relation->rd_rel->relispopulated = true;
+	/* ... and they're always no ivm, too */
+	relation->rd_rel->relisivm = false;
 
 	relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
 	relation->rd_rel->relpages = 0;
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 304e8c18d5..fbaa1438d6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -119,6 +119,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
 	/* is relation a partition? */
 	bool		relispartition BKI_DEFAULT(f);
 
+	/* is relation a matview with ivm? */
+	bool		relisivm BKI_DEFAULT(f);
+
 	/* link to original rel during table rewrite; otherwise 0 */
 	Oid			relrewrite BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_class);
 
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index b8dd27d4a9..cc2f635122 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -137,6 +137,7 @@ extern Oid	get_rel_namespace(Oid relid);
 extern Oid	get_rel_type_id(Oid relid);
 extern char get_rel_relkind(Oid relid);
 extern bool get_rel_relispartition(Oid relid);
+extern bool get_rel_relisivm(Oid relid);
 extern Oid	get_rel_tablespace(Oid relid);
 extern char get_rel_persistence(Oid relid);
 extern Oid	get_transform_fromsql(Oid typid, Oid langid, List *trftypes);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index eadbd00904..0e28716e6b 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -661,6 +661,8 @@ RelationGetSmgr(Relation rel)
  */
 #define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated)
 
+#define RelationIsIVM(relation) ((relation)->rd_rel->relisivm)
+
 /*
  * RelationIsAccessibleInLogicalDecoding
  *		True if we need to log enough information to have access via
-- 
2.17.1

v27-0003-Allow-to-prolong-life-span-of-transition-tables-.patchtext/x-diff; name=v27-0003-Allow-to-prolong-life-span-of-transition-tables-.patchDownload
From 2ef59a56b5df4f2449e615af36a7245f1d8a9880 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:09:45 +0900
Subject: [PATCH v27 3/9] Allow to prolong life span of transition tables until
 transaction end

Originally, tuplestores of AFTER trigger's transition tables were
freed for each query depth. For our IVM implementation, we would like
to prolong life of the tuplestores because we have to preserve them
for a whole query assuming that some base tables might be changed
in some trigger functions.
---
 src/backend/commands/trigger.c | 83 ++++++++++++++++++++++++++++++++--
 src/include/commands/trigger.h |  2 +
 2 files changed, 81 insertions(+), 4 deletions(-)

diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 13cb516752..aa1da7b279 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3730,6 +3730,10 @@ typedef struct AfterTriggerEventList
  * end of the list, so it is relatively easy to discard them.  The event
  * list chunks themselves are stored in event_cxt.
  *
+ * prolonged_tuplestored is a list of transition table tuplestores whose
+ * life are prolonged to the end of the outmost query instead of each nested
+ * query.
+ *
  * query_depth is the current depth of nested AfterTriggerBeginQuery calls
  * (-1 when the stack is empty).
  *
@@ -3795,6 +3799,7 @@ typedef struct AfterTriggersData
 	SetConstraintState state;	/* the active S C state */
 	AfterTriggerEventList events;	/* deferred-event list */
 	MemoryContext event_cxt;	/* memory context for events, if any */
+	List   *prolonged_tuplestores;	/* list of prolonged tuplestores */
 
 	/* per-query-level data: */
 	AfterTriggersQueryData *query_stack;	/* array of structs shown below */
@@ -3830,6 +3835,7 @@ struct AfterTriggersTableData
 	bool		closed;			/* true when no longer OK to add tuples */
 	bool		before_trig_done;	/* did we already queue BS triggers? */
 	bool		after_trig_done;	/* did we already queue AS triggers? */
+	bool		prolonged;			/* are transition tables prolonged? */
 	AfterTriggerEventList after_trig_events;	/* if so, saved list pointer */
 
 	/*
@@ -3879,6 +3885,7 @@ static void TransitionTableAddTuple(EState *estate,
 									TupleTableSlot *original_insert_tuple,
 									Tuplestorestate *tuplestore);
 static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs);
+static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged);
 static SetConstraintState SetConstraintStateCreate(int numalloc);
 static SetConstraintState SetConstraintStateCopy(SetConstraintState state);
 static SetConstraintState SetConstraintStateAddItem(SetConstraintState state,
@@ -4725,6 +4732,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
 }
 
 
+/*
+ * SetTransitionTablePreserved
+ *
+ * Prolong lifespan of transition tables corresponding specified relid and
+ * command type to the end of the outmost query instead of each nested query.
+ * This enables to use nested AFTER trigger's transition tables from outer
+ * query's triggers.  Currently, only immediate incremental view maintenance
+ * uses this.
+ */
+void
+SetTransitionTablePreserved(Oid relid, CmdType cmdType)
+{
+	AfterTriggersTableData *table;
+	AfterTriggersQueryData *qs;
+	bool		found = false;
+	ListCell   *lc;
+
+	/* Check state, like AfterTriggerSaveEvent. */
+	if (afterTriggers.query_depth < 0)
+		elog(ERROR, "SetTransitionTablePreserved() called outside of query");
+
+	qs = &afterTriggers.query_stack[afterTriggers.query_depth];
+
+	foreach(lc, qs->tables)
+	{
+		table = (AfterTriggersTableData *) lfirst(lc);
+		if (table->relid == relid && table->cmdType == cmdType &&
+			table->closed)
+		{
+			table->prolonged = true;
+			found = true;
+		}
+	}
+
+	if (!found)
+		elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType);
+}
+
+
 /*
  * GetAfterTriggersTableData
  *
@@ -4933,6 +4979,7 @@ AfterTriggerBeginXact(void)
 	 */
 	afterTriggers.firing_counter = (CommandId) 1;	/* mustn't be 0 */
 	afterTriggers.query_depth = -1;
+	afterTriggers.prolonged_tuplestores = NIL;
 
 	/*
 	 * Verify that there is no leftover state remaining.  If these assertions
@@ -5093,19 +5140,19 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
 		ts = table->old_upd_tuplestore;
 		table->old_upd_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+			release_or_prolong_tuplestore(ts, table->prolonged);
 		ts = table->new_upd_tuplestore;
 		table->new_upd_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+			release_or_prolong_tuplestore(ts, table->prolonged);
 		ts = table->old_del_tuplestore;
 		table->old_del_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+			release_or_prolong_tuplestore(ts, table->prolonged);
 		ts = table->new_ins_tuplestore;
 		table->new_ins_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+			release_or_prolong_tuplestore(ts, table->prolonged);
 		if (table->storeslot)
 			ExecDropSingleTupleTableSlot(table->storeslot);
 	}
@@ -5117,6 +5164,34 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
 	 */
 	qs->tables = NIL;
 	list_free_deep(tables);
+
+	/* Release prolonged tuplestores at the end of the outmost query */
+	if (afterTriggers.query_depth == 0)
+	{
+		foreach(lc, afterTriggers.prolonged_tuplestores)
+		{
+			ts = (Tuplestorestate *) lfirst(lc);
+			if (ts)
+				tuplestore_end(ts);
+		}
+		afterTriggers.prolonged_tuplestores = NIL;
+	}
+}
+
+/*
+ * Release the tuplestore, or append it to the prolonged tuplestores list.
+ */
+static void
+release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged)
+{
+	if (prolonged && afterTriggers.query_depth > 0)
+	{
+		MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext);
+		afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts);
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else
+		tuplestore_end(ts);
 }
 
 
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 194e8d5bc1..2e9f1ac8c7 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -261,6 +261,8 @@ extern void AfterTriggerEndSubXact(bool isCommit);
 extern void AfterTriggerSetState(ConstraintsSetStmt *stmt);
 extern bool AfterTriggerPendingOnRel(Oid relid);
 
+extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType);
+
 
 /*
  * in utils/adt/ri_triggers.c
-- 
2.17.1

v27-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patchtext/x-diff; name=v27-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patchDownload
From df9faf0cc923c6aaa26f13b9334444add42f4dbe Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Wed, 11 Nov 2020 17:01:25 +0900
Subject: [PATCH v27 4/9] Add Incremental View Maintenance support to pg_dump

Support CREATE INCREMENTAL MATERIALIZED VIEW syntax.
---
 src/bin/pg_dump/pg_dump.c        | 18 +++++++++++++++---
 src/bin/pg_dump/pg_dump.h        |  1 +
 src/bin/pg_dump/t/002_pg_dump.pl | 15 +++++++++++++++
 3 files changed, 31 insertions(+), 3 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 969e2a7a46..3c764076d2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6089,6 +6089,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_relacl;
 	int			i_acldefault;
 	int			i_ispartition;
+	int			i_isivm;
 
 	/*
 	 * Find all the tables and table-like objects.
@@ -6191,10 +6192,17 @@ getTables(Archive *fout, int *numTables)
 
 	if (fout->remoteVersion >= 100000)
 		appendPQExpBufferStr(query,
-							 "c.relispartition AS ispartition ");
+							 "c.relispartition AS ispartition, ");
 	else
 		appendPQExpBufferStr(query,
-							 "false AS ispartition ");
+							 "false AS ispartition, ");
+
+	if (fout->remoteVersion >= 160000)
+		appendPQExpBufferStr(query,
+							 "c.relisivm AS isivm ");
+	else
+		appendPQExpBufferStr(query,
+							 "false AS isivm ");
 
 	/*
 	 * Left join to pg_depend to pick up dependency info linking sequences to
@@ -6303,6 +6311,7 @@ getTables(Archive *fout, int *numTables)
 	i_relacl = PQfnumber(res, "relacl");
 	i_acldefault = PQfnumber(res, "acldefault");
 	i_ispartition = PQfnumber(res, "ispartition");
+	i_isivm = PQfnumber(res, "isivm");
 
 	if (dopt->lockWaitTimeout)
 	{
@@ -6380,6 +6389,7 @@ getTables(Archive *fout, int *numTables)
 			tblinfo[i].amname = pg_strdup(PQgetvalue(res, i, i_amname));
 		tblinfo[i].is_identity_sequence = (strcmp(PQgetvalue(res, i, i_is_identity_sequence), "t") == 0);
 		tblinfo[i].ispartition = (strcmp(PQgetvalue(res, i, i_ispartition), "t") == 0);
+		tblinfo[i].isivm = (strcmp(PQgetvalue(res, i, i_isivm), "t") == 0);
 
 		/* other fields were zeroed above */
 
@@ -15115,9 +15125,11 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
-		appendPQExpBuffer(q, "CREATE %s%s %s",
+		appendPQExpBuffer(q, "CREATE %s%s%s %s",
 						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
 						  "UNLOGGED " : "",
+						  tbinfo->relkind == RELKIND_MATVIEW && tbinfo->isivm ?
+						  "INCREMENTAL " : "",
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 1d21c2906f..0b798ad5de 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -318,6 +318,7 @@ typedef struct _tableInfo
 	bool		dummy_view;		/* view's real definition must be postponed */
 	bool		postponed_def;	/* matview must be postponed into post-data */
 	bool		ispartition;	/* is table a partition? */
+	bool		isivm;			/* is incrementally maintainable materialized view? */
 
 	/*
 	 * These fields are computed only if we decide the table is interesting
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c65c92bfb0..3655727472 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2295,6 +2295,21 @@ my %tests = (
 		  { exclude_dump_test_schema => 1, no_toast_compression => 1, },
 	},
 
+	'CREATE MATERIALIZED VIEW matview_ivm' => {
+		create_order => 21,
+		create_sql   => 'CREATE INCREMENTAL MATERIALIZED VIEW dump_test.matview_ivm (col1) AS
+					   SELECT col1 FROM dump_test.test_table;',
+		regexp => qr/^
+			\QCREATE INCREMENTAL MATERIALIZED VIEW dump_test.matview_ivm AS\E
+			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QFROM dump_test.test_table\E
+			\n\s+\QWITH NO DATA;\E
+			/xm,
+		like =>
+		  { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, },
+	},
+
 	'CREATE POLICY p1 ON test_table' => {
 		create_order => 22,
 		create_sql   => 'CREATE POLICY p1 ON dump_test.test_table
-- 
2.17.1

v27-0005-Add-Incremental-View-Maintenance-support-to-psql.patchtext/x-diff; name=v27-0005-Add-Incremental-View-Maintenance-support-to-psql.patchDownload
From 33a9b0fcb9c62d7a1ce23b3fc0bce47f86ac9c3d Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:21:54 +0900
Subject: [PATCH v27 5/9] Add Incremental View Maintenance support to psql

Add tab completion and meta-command output for IVM.
---
 src/bin/psql/describe.c     | 32 +++++++++++++++++++++++++++++++-
 src/bin/psql/tab-complete.c | 14 +++++++++-----
 2 files changed, 40 insertions(+), 6 deletions(-)

diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 583817b0cc..aacfe8b82d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1492,6 +1492,7 @@ describeOneTableDetails(const char *schemaname,
 		char		relpersistence;
 		char		relreplident;
 		char	   *relam;
+		bool		isivm;
 	}			tableinfo;
 	bool		show_column_details = false;
 
@@ -1504,7 +1505,26 @@ describeOneTableDetails(const char *schemaname,
 	initPQExpBuffer(&tmpbuf);
 
 	/* Get general table info */
-	if (pset.sversion >= 120000)
+	if (pset.sversion >= 160000)
+	{
+		printfPQExpBuffer(&buf,
+						  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+						  "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, "
+						  "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
+						  "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
+						  "c.relpersistence, c.relreplident, am.amname, "
+						  "c.relisivm\n"
+						  "FROM pg_catalog.pg_class c\n "
+						  "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+						  "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
+						  "WHERE c.oid = '%s';",
+						  (verbose ?
+						   "pg_catalog.array_to_string(c.reloptions || "
+						   "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+						   : "''"),
+						  oid);
+	}
+	else if (pset.sversion >= 120000)
 	{
 		printfPQExpBuffer(&buf,
 						  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
@@ -1624,6 +1644,10 @@ describeOneTableDetails(const char *schemaname,
 			(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
 	else
 		tableinfo.relam = NULL;
+	if (pset.sversion >= 160000)
+		tableinfo.isivm = strcmp(PQgetvalue(res, 0, 15), "t") == 0;
+	else
+		tableinfo.isivm = false;
 	PQclear(res);
 	res = NULL;
 
@@ -3424,6 +3448,12 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
 			printTableAddFooter(&cont, buf.data);
 		}
+
+		/* Incremental view maintance info */
+		if (verbose && tableinfo.relkind == RELKIND_MATVIEW && tableinfo.isivm)
+		{
+			printTableAddFooter(&cont, _("Incremental view maintenance: yes"));
+		}
 	}
 
 	/* reloptions, if verbose */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 588c0841fe..87b5d3e107 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1221,6 +1221,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"INCREMENTAL MATERIALIZED VIEW", NULL, NULL, &Query_for_list_of_matviews, NULL, THING_NO_DROP | THING_NO_ALTER},
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -3074,7 +3075,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
-		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
+		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW", "INCREMENTAL MATERIALIZED VIEW");
 	/* Complete PARTITION BY with RANGE ( or LIST ( or ... */
 	else if (TailMatches("PARTITION", "BY"))
 		COMPLETE_WITH("RANGE (", "LIST (", "HASH (");
@@ -3390,13 +3391,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches("CREATE", "MATERIALIZED"))
+	else if (Matches("CREATE", "MATERIALIZED") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED"))
 		COMPLETE_WITH("VIEW");
-	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	/* Complete CREATE MATERIALIZED VIEW <name> with AS  */
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny) ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH("SELECT");
 
 /* CREATE EVENT TRIGGER */
-- 
2.17.1

v27-0006-Add-Incremental-View-Maintenance-support.patchtext/x-diff; name=v27-0006-Add-Incremental-View-Maintenance-support.patchDownload
From ec38ae79124c7bccdbd6b93727cda1d26152a16a Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Thu, 21 Apr 2022 11:58:53 +0900
Subject: [PATCH v27 6/9] Add Incremental View Maintenance support

In this implementation, AFTER triggers are used to collect
tuplestores containing transition table contents. When multiple tables
are changed, multiple AFTER triggers are invoked, then the final AFTER
trigger performs actual update of the matview. In addition, BEFORE
triggers are also used to handle global information for view
maintenance. To calculate view deltas, we need both pre-state and
post-state of base tables. Post-update states are available in AFTER
trigger, and pre-update states can be calculated by filtering inserted
tuples using cmin/xmin system columns, and append deleted tuples which
are contained in an old transition table.

This patch also allows self-join, simultaneous updates of more than
one base table, and multiple updates of the same base table.

Incrementally Maintainable Materialized Views (IMMV) can contain
duplicated tuples. Also, DISTINCT clause is supported. When IMMV is
created with DISTINCT, multiplicity of tuples is counted and stored
in  "__ivm_count__" column, which is a hidden column of IMMV.
The value in __ivm_count__ is updated when IMMV is maintained
incrementally. A tuple in IMMV can be removed if and only if the
count becomes zero.
---
 src/backend/access/transam/xact.c   |    5 +
 src/backend/commands/createas.c     |  751 +++++++++++++
 src/backend/commands/indexcmds.c    |   40 +
 src/backend/commands/matview.c      | 1577 ++++++++++++++++++++++++++-
 src/backend/commands/tablecmds.c    |    9 +
 src/backend/nodes/copyfuncs.c       |    1 +
 src/backend/nodes/equalfuncs.c      |    1 +
 src/backend/nodes/outfuncs.c        |    1 +
 src/backend/nodes/readfuncs.c       |    1 +
 src/backend/parser/parse_relation.c |   18 +-
 src/backend/rewrite/rewriteDefine.c |    3 +-
 src/include/catalog/pg_proc.dat     |    8 +
 src/include/commands/createas.h     |    6 +
 src/include/commands/matview.h      |    8 +
 src/include/nodes/parsenodes.h      |    2 +
 15 files changed, 2391 insertions(+), 40 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 53f3e7fd1a..0377f5a88b 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -36,6 +36,7 @@
 #include "catalog/pg_enum.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/matview.h"
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "common/pg_prng.h"
@@ -2792,6 +2793,7 @@ AbortTransaction(void)
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
 	AtAbort_Twophase();
+	AtAbort_IVM();
 
 	/*
 	 * Advertise the fact that we aborted in pg_xact (assuming that we got as
@@ -5032,6 +5034,9 @@ AbortSubTransaction(void)
 	AbortBufferIO();
 	UnlockBuffers();
 
+	/* Clean up hash entries for incremental view maintenance */
+	AtAbort_IVM();
+
 	/* Reset WAL record construction state */
 	XLogResetInsertion();
 
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 9abbb6b555..1224a3b075 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -32,24 +32,41 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
 #include "catalog/toasting.h"
 #include "commands/createas.h"
+#include "commands/defrem.h"
 #include "commands/matview.h"
 #include "commands/prepare.h"
 #include "commands/tablecmds.h"
+#include "commands/tablespace.h"
+#include "commands/trigger.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "optimizer/clauses.h"
+#include "optimizer/optimizer.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_func.h"
+#include "parser/parse_type.h"
 #include "rewrite/rewriteHandler.h"
+#include "rewrite/rewriteManip.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
+#include "utils/syscache.h"
 
 typedef struct
 {
@@ -73,6 +90,13 @@ static bool intorel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void intorel_shutdown(DestReceiver *self);
 static void intorel_destroy(DestReceiver *self);
 
+static void CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid,
+									 Relids *relids, bool ex_lock);
+static void CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock);
+static void check_ivm_restriction(Node *node);
+static bool check_ivm_restriction_walker(Node *node, void *context);
+static Bitmapset *get_primary_key_attnos_from_query(Query *qry, List **constraintList, bool is_create);
+static Bitmapset *get_primary_key_attnos_from_query(Query *query, List **constraintList, bool is_create);
 
 /*
  * create_ctas_internal
@@ -108,6 +132,8 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	create->oncommit = into->onCommit;
 	create->tablespacename = into->tableSpaceName;
 	create->if_not_exists = false;
+	/* Using Materialized view only */
+	create->ivm = into->ivm;
 	create->accessMethod = into->accessMethod;
 
 	/*
@@ -282,6 +308,21 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 		save_nestlevel = NewGUCNestLevel();
 	}
 
+	if (is_matview && into->ivm)
+	{
+		/* check if the query is supported in IMMV definition */
+		if (contain_mutable_functions((Node *) query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("mutable function is not supported on incrementally maintainable materialized view"),
+					 errhint("functions must be marked IMMUTABLE")));
+
+		check_ivm_restriction((Node *) query);
+
+		/* For IMMV, we need to rewrite matview query */
+		query = rewriteQueryForIMMV(query, into->colNames);
+	}
+
 	if (into->skipData)
 	{
 		/*
@@ -358,11 +399,74 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 
 		/* Restore userid and security context */
 		SetUserIdAndSecContext(save_userid, save_sec_context);
+
+		if (into->ivm)
+		{
+			Oid matviewOid = address.objectId;
+			Relation matviewRel = table_open(matviewOid, NoLock);
+
+			/*
+			 * Mark relisivm field, if it's a matview and into->ivm is true.
+			 */
+			SetMatViewIVMState(matviewRel, true);
+
+			if (!into->skipData)
+			{
+				/* Create an index on incremental maintainable materialized view, if possible */
+				CreateIndexOnIMMV((Query *) into->viewQuery, matviewRel, true);
+
+				/* Create triggers on incremental maintainable materialized view */
+				CreateIvmTriggersOnBaseTables((Query *) into->viewQuery, matviewOid, true);
+			}
+			table_close(matviewRel, NoLock);
+		}
 	}
 
 	return address;
 }
 
+/*
+ * rewriteQueryForIMMV -- rewrite view definition query for IMMV
+ *
+ * count(*) is added for counting distinct tuples in views.
+ */
+Query *
+rewriteQueryForIMMV(Query *query, List *colNames)
+{
+	Query *rewritten;
+
+	TargetEntry *tle;
+	Node *node;
+	ParseState *pstate = make_parsestate(NULL);
+	FuncCall *fn;
+
+	rewritten = copyObject(query);
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	/*
+	 * Convert DISTINCT to GROUP BY and add count(*) for counting distinct
+	 * tuples in views.
+	 */
+	if (rewritten->distinctClause)
+	{
+		rewritten->groupClause = transformDistinctClause(NULL, &rewritten->targetList, rewritten->sortClause, false);
+
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+		fn->agg_star = true;
+
+		node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+		tle = makeTargetEntry((Expr *) node,
+								list_length(rewritten->targetList) + 1,
+								pstrdup("__ivm_count__"),
+								false);
+		rewritten->targetList = lappend(rewritten->targetList, tle);
+		rewritten->hasAggs = true;
+	}
+
+	return rewritten;
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -623,3 +727,650 @@ intorel_destroy(DestReceiver *self)
 {
 	pfree(self);
 }
+
+/*
+ * CreateIvmTriggersOnBaseTables -- create IVM triggers on all base tables
+ */
+void
+CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create)
+{
+	Relids	relids = NULL;
+	bool	ex_lock = false;
+	Index	first_rtindex = is_create ? 1 : PRS2_NEW_VARNO + 1;
+	RangeTblEntry *rte;
+
+	/* Immediately return if we don't have any base tables. */
+	if (list_length(qry->rtable) < first_rtindex)
+		return;
+
+	/*
+	 * If the view has more than one base tables, we need an exclusive lock
+	 * on the view so that the view would be maintained serially to avoid
+	 * the inconsistency that occurs when two base tables are modified in
+	 * concurrent transactions. However, if the view has only one table,
+	 * we can use a weaker lock.
+	 *
+	 * The type of lock should be determined here, because if we check the
+	 * view definition at maintenance time, we need to acquire a weaker lock,
+	 * and upgrading the lock level after this increases probability of
+	 * deadlock.
+	 */
+
+	rte = list_nth(qry->rtable, first_rtindex - 1);
+	if (list_length(qry->rtable) > first_rtindex ||
+		rte->rtekind != RTE_RELATION)
+		ex_lock = true;
+
+	CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *)qry, matviewOid, &relids, ex_lock);
+
+	bms_free(relids);
+}
+
+static void
+CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid,
+									 Relids *relids, bool ex_lock)
+{
+	if (node == NULL)
+		return;
+
+	/* This can recurse, so check for excessive recursion */
+	check_stack_depth();
+
+	switch (nodeTag(node))
+	{
+		case T_Query:
+			{
+				Query *query = (Query *) node;
+
+				CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *)query->jointree, matviewOid, relids, ex_lock);
+			}
+			break;
+
+		case T_RangeTblRef:
+			{
+				int			rti = ((RangeTblRef *) node)->rtindex;
+				RangeTblEntry *rte = rt_fetch(rti, qry->rtable);
+
+				if (rte->rtekind == RTE_RELATION && !bms_is_member(rte->relid, *relids))
+				{
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_BEFORE, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_BEFORE, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_BEFORE, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_AFTER, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_AFTER, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_AFTER, ex_lock);
+
+					*relids = bms_add_member(*relids, rte->relid);
+				}
+			}
+			break;
+
+		case T_FromExpr:
+			{
+				FromExpr   *f = (FromExpr *) node;
+				ListCell   *l;
+
+				foreach(l, f->fromlist)
+					CreateIvmTriggersOnBaseTablesRecurse(qry, lfirst(l), matviewOid, relids, ex_lock);
+			}
+			break;
+
+		case T_JoinExpr:
+			{
+				JoinExpr   *j = (JoinExpr *) node;
+
+				CreateIvmTriggersOnBaseTablesRecurse(qry, j->larg, matviewOid, relids, ex_lock);
+				CreateIvmTriggersOnBaseTablesRecurse(qry, j->rarg, matviewOid, relids, ex_lock);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
+	}
+}
+
+/*
+ * CreateIvmTrigger -- create IVM trigger on a base table
+ */
+static void
+CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock)
+{
+	ObjectAddress	refaddr;
+	ObjectAddress	address;
+	CreateTrigStmt *ivm_trigger;
+	List *transitionRels = NIL;
+
+	Assert(timing == TRIGGER_TYPE_BEFORE || timing == TRIGGER_TYPE_AFTER);
+
+	refaddr.classId = RelationRelationId;
+	refaddr.objectId = viewOid;
+	refaddr.objectSubId = 0;
+
+	ivm_trigger = makeNode(CreateTrigStmt);
+	ivm_trigger->relation = NULL;
+	ivm_trigger->row = false;
+
+	ivm_trigger->timing = timing;
+	ivm_trigger->events = type;
+
+	switch (type)
+	{
+		case TRIGGER_TYPE_INSERT:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_ins_before" : "IVM_trigger_ins_after");
+			break;
+		case TRIGGER_TYPE_DELETE:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_del_before" : "IVM_trigger_del_after");
+			break;
+		case TRIGGER_TYPE_UPDATE:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_upd_before" : "IVM_trigger_upd_after");
+			break;
+		default:
+			elog(ERROR, "unsupported trigger type");
+	}
+
+	if (timing == TRIGGER_TYPE_AFTER)
+	{
+		if (type == TRIGGER_TYPE_INSERT || type == TRIGGER_TYPE_UPDATE)
+		{
+			TriggerTransition *n = makeNode(TriggerTransition);
+			n->name = "__ivm_newtable";
+			n->isNew = true;
+			n->isTable = true;
+
+			transitionRels = lappend(transitionRels, n);
+		}
+		if (type == TRIGGER_TYPE_DELETE || type == TRIGGER_TYPE_UPDATE)
+		{
+			TriggerTransition *n = makeNode(TriggerTransition);
+			n->name = "__ivm_oldtable";
+			n->isNew = false;
+			n->isTable = true;
+
+			transitionRels = lappend(transitionRels, n);
+		}
+	}
+
+	ivm_trigger->funcname =
+		(timing == TRIGGER_TYPE_BEFORE ? SystemFuncName("IVM_immediate_before") : SystemFuncName("IVM_immediate_maintenance"));
+
+	ivm_trigger->columns = NIL;
+	ivm_trigger->transitionRels = transitionRels;
+	ivm_trigger->whenClause = NULL;
+	ivm_trigger->isconstraint = false;
+	ivm_trigger->deferrable = false;
+	ivm_trigger->initdeferred = false;
+	ivm_trigger->constrrel = NULL;
+	ivm_trigger->args = list_make2(
+		makeString(DatumGetPointer(DirectFunctionCall1(oidout, ObjectIdGetDatum(viewOid)))),
+		makeString(DatumGetPointer(DirectFunctionCall1(boolout, BoolGetDatum(ex_lock))))
+		);
+
+	address = CreateTrigger(ivm_trigger, NULL, relOid, InvalidOid, InvalidOid,
+						 InvalidOid, InvalidOid, InvalidOid, NULL, true, false);
+
+	recordDependencyOn(&address, &refaddr, DEPENDENCY_AUTO);
+
+	/* Make changes-so-far visible */
+	CommandCounterIncrement();
+}
+
+/*
+ * check_ivm_restriction --- look for specify nodes in the query tree
+ */
+static void
+check_ivm_restriction(Node *node)
+{
+	check_ivm_restriction_walker(node, NULL);
+}
+
+static bool
+check_ivm_restriction_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	/*
+	 * We currently don't support Sub-Query.
+	 */
+	if (IsA(node, SubPlan) || IsA(node, SubLink))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("subquery is not supported on incrementally maintainable materialized view")));
+
+	/* This can recurse, so check for excessive recursion */
+	check_stack_depth();
+
+	switch (nodeTag(node))
+	{
+		case T_Query:
+			{
+				Query *qry = (Query *)node;
+				ListCell   *lc;
+				List       *vars;
+
+				/* if contained CTE, return error */
+				if (qry->cteList != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("CTE is not supported on incrementally maintainable materialized view")));
+				if (qry->havingQual != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg(" HAVING clause is not supported on incrementally maintainable materialized view")));
+				if (qry->sortClause != NIL)	/* There is a possibility that we don't need to return an error */
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("ORDER BY clause is not supported on incrementally maintainable materialized view")));
+				if (qry->limitOffset != NULL || qry->limitCount != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("LIMIT/OFFSET clause is not supported on incrementally maintainable materialized view")));
+				if (qry->hasDistinctOn)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DISTINCT ON is not supported on incrementally maintainable materialized view")));
+				if (qry->hasWindowFuncs)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("window functions are not supported on incrementally maintainable materialized view")));
+				if (qry->groupingSets != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("GROUPING SETS, ROLLUP, or CUBE clauses is not supported on incrementally maintainable materialized view")));
+				if (qry->setOperations != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("UNION/INTERSECT/EXCEPT statements are not supported on incrementally maintainable materialized view")));
+				if (list_length(qry->targetList) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("empty target list is not supported on incrementally maintainable materialized view")));
+				if (qry->rowMarks != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("FOR UPDATE/SHARE clause is not supported on incrementally maintainable materialized view")));
+
+				/* system column restrictions */
+				vars = pull_vars_of_level((Node *) qry, 0);
+				foreach(lc, vars)
+				{
+					if (IsA(lfirst(lc), Var))
+					{
+						Var *var = (Var *) lfirst(lc);
+						/* if system column, return error */
+						if (var->varattno < 0)
+							ereport(ERROR,
+									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									 errmsg("system column is not supported on incrementally maintainable materialized view")));
+					}
+				}
+
+				/* restrictions for rtable */
+				foreach(lc, qry->rtable)
+				{
+					RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+					if (rte->subquery)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("subquery is not supported on incrementally maintainable materialized view")));
+
+					if (rte->tablesample != NULL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("TABLESAMPLE clause is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("partitioned table is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_RELATION && has_superclass(rte->relid))
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("partitions is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_RELATION && find_inheritance_children(rte->relid, NoLock) != NIL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("inheritance parent is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_FOREIGN_TABLE)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("foreign table is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_VIEW ||
+						rte->relkind == RELKIND_MATVIEW)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view")));
+
+					if (rte->rtekind == RTE_VALUES)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("VALUES is not supported on incrementally maintainable materialized view")));
+
+				}
+
+				query_tree_walker(qry, check_ivm_restriction_walker, NULL, QTW_IGNORE_RANGE_TABLE);
+
+				break;
+			}
+		case T_TargetEntry:
+			{
+				TargetEntry *tle = (TargetEntry *)node;
+				if (isIvmName(tle->resname))
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("column name %s is not supported on incrementally maintainable materialized view", tle->resname)));
+
+				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				break;
+			}
+		case T_JoinExpr:
+			{
+				JoinExpr *joinexpr = (JoinExpr *)node;
+
+				if (joinexpr->jointype > JOIN_INNER)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("OUTER JOIN is not supported on incrementally maintainable materialized view")));
+
+				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+			}
+			break;
+		case T_Aggref:
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("aggregate function is not supported on incrementally maintainable materialized view")));
+			break;
+		default:
+			expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
+			break;
+	}
+	return false;
+}
+
+/*
+ * CreateIndexOnIMMV
+ *
+ * Create a unique index on incremental maintainable materialized view.
+ * If the view definition query has a GROUP BY clause, the index is created
+ * on the columns of GROUP BY expressions. Otherwise, if the view contains
+ * all primary key attritubes of its base tables in the target list, the index
+ * is created on these attritubes. In other cases, no index is created.
+ */
+void
+CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create)
+{
+	ListCell *lc;
+	IndexStmt  *index;
+	ObjectAddress address;
+	List *constraintList = NIL;
+	char		idxname[NAMEDATALEN];
+	List	   *indexoidlist = RelationGetIndexList(matviewRel);
+	ListCell   *indexoidscan;
+
+	snprintf(idxname, sizeof(idxname), "%s_index", RelationGetRelationName(matviewRel));
+
+	index = makeNode(IndexStmt);
+
+	/*
+	 * We consider null values not distinct to make sure that views with DISTINCT
+	 * or GROUP BY don't contain multiple NULL rows when NULL is inserted to
+	 * a base table concurrently.
+	 */
+	index->nulls_not_distinct = true;
+
+	index->unique = true;
+	index->primary = false;
+	index->isconstraint = false;
+	index->deferrable = false;
+	index->initdeferred = false;
+	index->idxname = idxname;
+	index->relation =
+		makeRangeVar(get_namespace_name(RelationGetNamespace(matviewRel)),
+					 pstrdup(RelationGetRelationName(matviewRel)),
+					 -1);
+	index->accessMethod = DEFAULT_INDEX_TYPE;
+	index->options = NIL;
+	index->tableSpace = get_tablespace_name(matviewRel->rd_rel->reltablespace);
+	index->whereClause = NULL;
+	index->indexParams = NIL;
+	index->indexIncludingParams = NIL;
+	index->excludeOpNames = NIL;
+	index->idxcomment = NULL;
+	index->indexOid = InvalidOid;
+	index->oldNode = InvalidOid;
+	index->oldCreateSubid = InvalidSubTransactionId;
+	index->oldFirstRelfilenodeSubid = InvalidSubTransactionId;
+	index->transformed = true;
+	index->concurrent = false;
+	index->if_not_exists = false;
+
+	if (query->distinctClause)
+	{
+		/* create unique constraint on all columns */
+		foreach(lc, query->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+			IndexElem  *iparam;
+
+			iparam = makeNode(IndexElem);
+			iparam->name = pstrdup(NameStr(attr->attname));
+			iparam->expr = NULL;
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->opclassopts = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+		}
+	}
+	else
+	{
+		Bitmapset *key_attnos;
+
+		/* create index on the base tables' primary key columns */
+		key_attnos = get_primary_key_attnos_from_query(query, &constraintList, is_create);
+		if (key_attnos)
+		{
+			foreach(lc, query->targetList)
+			{
+				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+				Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+
+				if (bms_is_member(tle->resno - FirstLowInvalidHeapAttributeNumber, key_attnos))
+				{
+					IndexElem  *iparam;
+
+					iparam = makeNode(IndexElem);
+					iparam->name = pstrdup(NameStr(attr->attname));
+					iparam->expr = NULL;
+					iparam->indexcolname = NULL;
+					iparam->collation = NIL;
+					iparam->opclass = NIL;
+					iparam->opclassopts = NIL;
+					iparam->ordering = SORTBY_DEFAULT;
+					iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+					index->indexParams = lappend(index->indexParams, iparam);
+				}
+			}
+		}
+		else
+		{
+			/* create no index, just notice that an appropriate index is necessary for efficient IVM */
+			ereport(NOTICE,
+					(errmsg("could not create an index on materialized view \"%s\" automatically",
+							RelationGetRelationName(matviewRel)),
+					 errdetail("This target list does not have all the primary key columns, "
+							   "or this view does not contain DISTINCT clause."),
+					 errhint("Create an index on the materialized view for efficient incremental maintenance.")));
+			return;
+		}
+	}
+
+	/* If we have a compatible index, we don't need to create another. */
+	foreach(indexoidscan, indexoidlist)
+	{
+		Oid			indexoid = lfirst_oid(indexoidscan);
+		Relation	indexRel;
+		bool		hasCompatibleIndex = false;
+
+		indexRel = index_open(indexoid, AccessShareLock);
+
+		if (CheckIndexCompatible(indexRel->rd_id,
+								index->accessMethod,
+								index->indexParams,
+								index->excludeOpNames))
+			hasCompatibleIndex = true;
+
+		index_close(indexRel, AccessShareLock);
+
+		if (hasCompatibleIndex)
+			return;
+	}
+
+	address = DefineIndex(RelationGetRelid(matviewRel),
+						  index,
+						  InvalidOid,
+						  InvalidOid,
+						  InvalidOid,
+						  false, true, false, false, true);
+
+	ereport(NOTICE,
+			(errmsg("created index \"%s\" on materialized view \"%s\"",
+					idxname, RelationGetRelationName(matviewRel))));
+
+	/*
+	 * Make dependencies so that the index is dropped if any base tables's
+	 * primary key is dropped.
+	 */
+	foreach(lc, constraintList)
+	{
+		Oid constraintOid = lfirst_oid(lc);
+		ObjectAddress	refaddr;
+
+		refaddr.classId = ConstraintRelationId;
+		refaddr.objectId = constraintOid;
+		refaddr.objectSubId = 0;
+
+		recordDependencyOn(&address, &refaddr, DEPENDENCY_NORMAL);
+	}
+}
+
+
+/*
+ * get_primary_key_attnos_from_query
+ *
+ * Identify the columns in base tables' primary keys in the target list.
+ *
+ * Returns a Bitmapset of the column attnos of the primary key's columns of
+ * tables that used in the query.  The attnos are offset by
+ * FirstLowInvalidHeapAttributeNumber as same as get_primary_key_attnos.
+ *
+ * If any table has no primary key or any primary key's columns is not in
+ * the target list, return NULL.  We also return NULL if any pkey constraint
+ * is deferrable.
+ *
+ * constraintList is set to a list of the OIDs of the pkey constraints.
+ */
+static Bitmapset *
+get_primary_key_attnos_from_query(Query *query, List **constraintList, bool is_create)
+{
+	List *key_attnos_list = NIL;
+	ListCell *lc;
+	int i;
+	Bitmapset *keys = NULL;
+	Relids	rels_in_from;
+	PlannerInfo root;
+
+
+	/*
+	 * Collect primary key attributes from all tables used in query. The key attributes
+	 * sets for each table are stored in key_attnos_list in order by RTE index.
+	 */
+	i = 1;
+	foreach(lc, query->rtable)
+	{
+		RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+		Bitmapset *key_attnos;
+		bool	has_pkey = true;
+		Index	first_rtindex = is_create ? 1 : PRS2_NEW_VARNO + 1;
+
+		/* skip NEW/OLD entries */
+		if (i >= first_rtindex)
+		{
+			/* for tables, call get_primary_key_attnos */
+			if (r->rtekind == RTE_RELATION)
+			{
+				Oid constraintOid;
+				key_attnos = get_primary_key_attnos(r->relid, false, &constraintOid);
+				*constraintList = lappend_oid(*constraintList, constraintOid);
+				has_pkey = (key_attnos != NULL);
+			}
+			/* for other RTEs, store NULL into key_attnos_list */
+			else
+				key_attnos = NULL;
+		}
+		else
+			key_attnos = NULL;
+
+		/*
+		 * If any table or subquery has no primary key or its pkey constraint is deferrable,
+		 * we cannot get key attributes for this query, so return NULL.
+		 */
+		if (!has_pkey)
+			return NULL;
+
+		key_attnos_list = lappend(key_attnos_list, key_attnos);
+		i++;
+	}
+
+	/* Collect key attributes appearing in the target list */
+	i = 1;
+	foreach(lc, query->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) flatten_join_alias_vars(query, lfirst(lc));
+
+		if (IsA(tle->expr, Var))
+		{
+			Var *var = (Var*) tle->expr;
+			Bitmapset *attnos = list_nth(key_attnos_list, var->varno - 1);
+
+			/* check if this attribute is from a base table's primary key */
+			if (bms_is_member(var->varattno - FirstLowInvalidHeapAttributeNumber, attnos))
+			{
+				/*
+				 * Remove found key attributes from key_attnos_list, and add this
+				 * to the result list.
+				 */
+				bms_del_member(attnos, var->varattno - FirstLowInvalidHeapAttributeNumber);
+				keys = bms_add_member(keys, i - FirstLowInvalidHeapAttributeNumber);
+			}
+		}
+		i++;
+	}
+
+	/* Collect relations appearing in the FROM clause */
+	rels_in_from = pull_varnos_of_level(&root, (Node *)query->jointree, 0);
+
+	/*
+	 * Check if all key attributes of relations in FROM are appearing in the target
+	 * list.  If an attribute remains in key_attnos_list in spite of the table is used
+	 * in FROM clause, the target is missing this key attribute, so we return NULL.
+	 */
+	i = 1;
+	foreach(lc, key_attnos_list)
+	{
+		Bitmapset *bms = (Bitmapset *)lfirst(lc);
+		if (!bms_is_empty(bms) && bms_is_member(i, rels_in_from))
+			return NULL;
+		i++;
+	}
+
+	return keys;
+}
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index cd30f15eba..a94b8ffd7d 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -36,6 +36,7 @@
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
+#include "commands/matview.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
@@ -1055,6 +1056,45 @@ DefineIndex(Oid relationId,
 	safe_index = indexInfo->ii_Expressions == NIL &&
 		indexInfo->ii_Predicate == NIL;
 
+	/*
+	 * We disallow unique indexes on IVM columns of IMMVs.
+	 */
+	if (RelationIsIVM(rel) && stmt->unique)
+	{
+		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
+		{
+			AttrNumber	attno = indexInfo->ii_IndexAttrNumbers[i];
+			if (attno > 0)
+			{
+				char *name = NameStr(TupleDescAttr(rel->rd_att, attno - 1)->attname);
+				if (name && isIvmName(name))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique index creation on IVM columns is not supported")));
+			}
+		}
+
+		if (indexInfo->ii_Expressions)
+		{
+			Bitmapset  *indexattrs = NULL;
+			int			varno = -1;
+
+			pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
+
+			while ((varno = bms_next_member(indexattrs, varno)) >= 0)
+			{
+				int attno = varno + FirstLowInvalidHeapAttributeNumber;
+				char *name = NameStr(TupleDescAttr(rel->rd_att, attno - 1)->attname);
+				if (name && isIvmName(name))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique index creation on IVM columns is not supported")));
+			}
+
+		}
+	}
+
+
 	/*
 	 * Report index creation if appropriate (delay this till after most of the
 	 * error checks)
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 9ab248d25e..cb713328a0 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -25,26 +25,48 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "commands/cluster.h"
+#include "commands/defrem.h"
 #include "commands/matview.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
+#include "commands/createas.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "executor/tstoreReceiver.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_func.h"
 #include "parser/parse_relation.h"
+#include "parser/parse_type.h"
 #include "pgstat.h"
 #include "rewrite/rewriteHandler.h"
+#include "rewrite/rewriteManip.h"
+#include "rewrite/rowsecurity.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
 
 
 typedef struct
@@ -58,6 +80,50 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_transientrel;
 
+#define MV_INIT_QUERYHASHSIZE	16
+
+/*
+ * MV_TriggerHashEntry
+ *
+ * Hash entry for base tables on which IVM trigger is invoked
+ */
+typedef struct MV_TriggerHashEntry
+{
+	Oid	matview_id;			/* OID of the materialized view */
+	int	before_trig_count;	/* count of before triggers invoked */
+	int	after_trig_count;	/* count of after triggers invoked */
+
+	TransactionId	xid;	/* Transaction id before the first table is modified*/
+	CommandId		cid;	/* Command id before the first table is modified */
+
+	List   *tables;		/* List of MV_TriggerTable */
+	bool	has_old;	/* tuples are deleted from any table? */
+	bool	has_new;	/* tuples are inserted into any table? */
+} MV_TriggerHashEntry;
+
+/*
+ * MV_TriggerTable
+ *
+ * IVM related data for tables on which the trigger is invoked.
+ */
+typedef struct MV_TriggerTable
+{
+	Oid		table_id;			/* OID of the modified table */
+	List   *old_tuplestores;	/* tuplestores for deleted tuples */
+	List   *new_tuplestores;	/* tuplestores for inserted tuples */
+	List   *old_rtes;			/* RTEs of ENRs for old_tuplestores*/
+	List   *new_rtes;			/* RTEs of ENRs for new_tuplestores */
+
+	List   *rte_indexes;		/* List of RTE index of the modified table */
+	RangeTblEntry *original_rte;	/* the original RTE saved before rewriting query */
+} MV_TriggerTable;
+
+static HTAB *mv_trigger_info = NULL;
+
+/* ENR name for materialized view delta */
+#define NEW_DELTA_ENRNAME "new_delta"
+#define OLD_DELTA_ENRNAME "old_delta"
+
 static int	matview_maintenance_depth = 0;
 
 static void transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
@@ -65,7 +131,9 @@ static bool transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void transientrel_shutdown(DestReceiver *self);
 static void transientrel_destroy(DestReceiver *self);
 static uint64 refresh_matview_datafill(DestReceiver *dest, Query *query,
-									   const char *queryString);
+						 QueryEnvironment *queryEnv,
+						 TupleDesc *resultTupleDesc,
+						 const char *queryString);
 static char *make_temptable_name_n(char *tempname, int n);
 static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 								   int save_sec_context);
@@ -73,6 +141,45 @@ static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersist
 static bool is_usable_unique_index(Relation indexRel);
 static void OpenMatViewIncrementalMaintenance(void);
 static void CloseMatViewIncrementalMaintenance(void);
+static Query *get_matview_query(Relation matviewRel);
+
+static Query *rewrite_query_for_preupdate_state(Query *query, List *tables,
+								  TransactionId xid, CommandId cid,
+								  ParseState *pstate);
+static void register_delta_ENRs(ParseState *pstate, Query *query, List *tables);
+static char *make_delta_enr_name(const char *prefix, Oid relid, int count);
+static RangeTblEntry *get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
+				 TransactionId xid, CommandId cid,
+				 QueryEnvironment *queryEnv);
+static RangeTblEntry *union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
+		   QueryEnvironment *queryEnv);
+static Query *rewrite_query_for_distinct(Query *query, ParseState *pstate);
+
+static void calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
+			DestReceiver *dest_old, DestReceiver *dest_new,
+			TupleDesc *tupdesc_old, TupleDesc *tupdesc_new,
+			QueryEnvironment *queryEnv);
+static Query *rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte_index);
+
+static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
+			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
+			Query *query, bool use_count, char *count_colname);
+static void apply_old_delta(const char *matviewname, const char *deltaname_old,
+				List *keys);
+static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
+				List *keys, const char *count_colname);
+static void apply_new_delta(const char *matviewname, const char *deltaname_new,
+				StringInfo target_list);
+static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
+				List *keys, StringInfo target_list, const char* count_colname);
+static char *get_matching_condition_string(List *keys);
+static void generate_equal(StringInfo querybuf, Oid opttype,
+			   const char *leftop, const char *rightop);
+
+static void mv_InitHashTables(void);
+static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry);
+
+static List *get_securityQuals(Oid relId, int rt_index, Query *query);
 
 /*
  * SetMatViewPopulatedState
@@ -114,6 +221,46 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
 	CommandCounterIncrement();
 }
 
+/*
+ * SetMatViewIVMState
+ *		Mark a materialized view as IVM, or not.
+ *
+ * NOTE: caller must be holding an appropriate lock on the relation.
+ */
+void
+SetMatViewIVMState(Relation relation, bool newstate)
+{
+	Relation	pgrel;
+	HeapTuple	tuple;
+
+	Assert(relation->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Update relation's pg_class entry.  Crucial side-effect: other backends
+	 * (and this one too!) are sent SI message to make them rebuild relcache
+	 * entries.
+	 */
+	pgrel = table_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(relation)));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u",
+			 RelationGetRelid(relation));
+
+	((Form_pg_class) GETSTRUCT(tuple))->relisivm = newstate;
+
+	CatalogTupleUpdate(pgrel, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	table_close(pgrel, RowExclusiveLock);
+
+	/*
+	 * Advance command counter to make the updated pg_class row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+}
+
 /*
  * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command
  *
@@ -140,9 +287,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 {
 	Oid			matviewOid;
 	Relation	matviewRel;
-	RewriteRule *rule;
-	List	   *actions;
 	Query	   *dataQuery;
+	Query	   *viewQuery;
 	Oid			tableSpace;
 	Oid			relowner;
 	Oid			OIDNewHeap;
@@ -155,6 +301,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 	int			save_sec_context;
 	int			save_nestlevel;
 	ObjectAddress address;
+	bool oldPopulated;
 
 	/* Determine strength of lock needed. */
 	concurrent = stmt->concurrent;
@@ -167,6 +314,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										  lockmode, 0,
 										  RangeVarCallbackOwnsTable, NULL);
 	matviewRel = table_open(matviewOid, NoLock);
+	oldPopulated = RelationIsPopulated(matviewRel);
 
 	/* Make sure it is a materialized view. */
 	if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW)
@@ -188,32 +336,14 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 				 errmsg("%s and %s options cannot be used together",
 						"CONCURRENTLY", "WITH NO DATA")));
 
-	/*
-	 * Check that everything is correct for a refresh. Problems at this point
-	 * are internal errors, so elog is sufficient.
-	 */
-	if (matviewRel->rd_rel->relhasrules == false ||
-		matviewRel->rd_rules->numLocks < 1)
-		elog(ERROR,
-			 "materialized view \"%s\" is missing rewrite information",
-			 RelationGetRelationName(matviewRel));
-
-	if (matviewRel->rd_rules->numLocks > 1)
-		elog(ERROR,
-			 "materialized view \"%s\" has too many rules",
-			 RelationGetRelationName(matviewRel));
 
-	rule = matviewRel->rd_rules->rules[0];
-	if (rule->event != CMD_SELECT || !(rule->isInstead))
-		elog(ERROR,
-			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
-			 RelationGetRelationName(matviewRel));
+	viewQuery = get_matview_query(matviewRel);
 
-	actions = rule->actions;
-	if (list_length(actions) != 1)
-		elog(ERROR,
-			 "the rule for materialized view \"%s\" is not a single action",
-			 RelationGetRelationName(matviewRel));
+	/* For IMMV, we need to rewrite matview query */
+	if (!stmt->skipData && RelationIsIVM(matviewRel))
+		dataQuery = rewriteQueryForIMMV(viewQuery,NIL);
+	else
+		dataQuery = viewQuery;
 
 	/*
 	 * Check that there is a unique index with no WHERE clause on one or more
@@ -248,12 +378,6 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 					 errhint("Create a unique index with no WHERE clause on one or more columns of the materialized view.")));
 	}
 
-	/*
-	 * The stored query was rewritten at the time of the MV definition, but
-	 * has not been scribbled on by the planner.
-	 */
-	dataQuery = linitial_node(Query, actions);
-
 	/*
 	 * Check for active uses of the relation in the current transaction, such
 	 * as open scans.
@@ -294,6 +418,74 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 		relpersistence = matviewRel->rd_rel->relpersistence;
 	}
 
+	/* delete IMMV triggers. */
+	if (RelationIsIVM(matviewRel) && stmt->skipData )
+	{
+		Relation	tgRel;
+		Relation	depRel;
+		ScanKeyData key;
+		SysScanDesc scan;
+		HeapTuple	tup;
+		ObjectAddresses *immv_triggers;
+
+		immv_triggers = new_object_addresses();
+
+		tgRel = table_open(TriggerRelationId, RowExclusiveLock);
+		depRel = table_open(DependRelationId, RowExclusiveLock);
+
+		/* search triggers that depends on IMMV. */
+		ScanKeyInit(&key,
+					Anum_pg_depend_refobjid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(matviewOid));
+		scan = systable_beginscan(depRel, DependReferenceIndexId, true,
+								  NULL, 1, &key);
+		while ((tup = systable_getnext(scan)) != NULL)
+		{
+			ObjectAddress obj;
+			Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
+
+			if (foundDep->classid == TriggerRelationId)
+			{
+				HeapTuple	tgtup;
+				ScanKeyData tgkey[1];
+				SysScanDesc tgscan;
+				Form_pg_trigger tgform;
+
+				/* Find the trigger name. */
+				ScanKeyInit(&tgkey[0],
+							Anum_pg_trigger_oid,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(foundDep->objid));
+
+				tgscan = systable_beginscan(tgRel, TriggerOidIndexId, true,
+											NULL, 1, tgkey);
+				tgtup = systable_getnext(tgscan);
+				if (!HeapTupleIsValid(tgtup))
+					elog(ERROR, "could not find tuple for immv trigger %u", foundDep->objid);
+
+				tgform = (Form_pg_trigger) GETSTRUCT(tgtup);
+
+				/* If trigger is created by IMMV, delete it. */
+				if (strncmp(NameStr(tgform->tgname), "IVM_trigger_", 12) == 0)
+				{
+					obj.classId = foundDep->classid;
+					obj.objectId = foundDep->objid;
+					obj.objectSubId = foundDep->refobjsubid;
+					add_exact_object_address(&obj, immv_triggers);
+				}
+				systable_endscan(tgscan);
+			}
+		}
+		systable_endscan(scan);
+
+		performMultipleDeletions(immv_triggers, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
+
+		table_close(depRel, RowExclusiveLock);
+		table_close(tgRel, RowExclusiveLock);
+		free_object_addresses(immv_triggers);
+	}
+
 	/*
 	 * Create the transient table that will receive the regenerated data. Lock
 	 * it against access by any other process until commit (by which time it
@@ -313,7 +505,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 
 	/* Generate the data, if wanted. */
 	if (!stmt->skipData)
-		processed = refresh_matview_datafill(dest, dataQuery, queryString);
+		processed = refresh_matview_datafill(dest, dataQuery, NULL, NULL, queryString);
 
 	/* Make the matview match the newly generated data. */
 	if (concurrent)
@@ -348,6 +540,12 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 			pgstat_count_heap_insert(matviewRel, processed);
 	}
 
+	if (!stmt->skipData && RelationIsIVM(matviewRel) && !oldPopulated)
+	{
+		CreateIndexOnIMMV(viewQuery, matviewRel, false);
+		CreateIvmTriggersOnBaseTables(viewQuery, matviewOid, false);
+	}
+
 	table_close(matviewRel, NoLock);
 
 	/* Roll back any GUC changes */
@@ -382,6 +580,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  */
 static uint64
 refresh_matview_datafill(DestReceiver *dest, Query *query,
+						 QueryEnvironment *queryEnv,
+						 TupleDesc *resultTupleDesc,
 						 const char *queryString)
 {
 	List	   *rewritten;
@@ -418,7 +618,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 	/* Create a QueryDesc, redirecting output to our tuple receiver */
 	queryDesc = CreateQueryDesc(plan, queryString,
 								GetActiveSnapshot(), InvalidSnapshot,
-								dest, NULL, NULL, 0);
+								dest, NULL, queryEnv ? queryEnv: NULL, 0);
 
 	/* call ExecutorStart to prepare the plan for execution */
 	ExecutorStart(queryDesc, 0);
@@ -428,6 +628,9 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 
 	processed = queryDesc->estate->es_processed;
 
+	if (resultTupleDesc)
+		*resultTupleDesc = CreateTupleDescCopy(queryDesc->tupDesc);
+
 	/* and clean up */
 	ExecutorFinish(queryDesc);
 	ExecutorEnd(queryDesc);
@@ -942,3 +1145,1307 @@ CloseMatViewIncrementalMaintenance(void)
 	matview_maintenance_depth--;
 	Assert(matview_maintenance_depth >= 0);
 }
+
+/*
+ * get_matview_query - get the Query from a matview's _RETURN rule.
+ */
+static Query *
+get_matview_query(Relation matviewRel)
+{
+	RewriteRule *rule;
+	List * actions;
+
+	/*
+	 * Check that everything is correct for a refresh. Problems at this point
+	 * are internal errors, so elog is sufficient.
+	 */
+	if (matviewRel->rd_rel->relhasrules == false ||
+		matviewRel->rd_rules->numLocks < 1)
+		elog(ERROR,
+			 "materialized view \"%s\" is missing rewrite information",
+			 RelationGetRelationName(matviewRel));
+
+	if (matviewRel->rd_rules->numLocks > 1)
+		elog(ERROR,
+			 "materialized view \"%s\" has too many rules",
+			 RelationGetRelationName(matviewRel));
+
+	rule = matviewRel->rd_rules->rules[0];
+	if (rule->event != CMD_SELECT || !(rule->isInstead))
+		elog(ERROR,
+			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
+			 RelationGetRelationName(matviewRel));
+
+	actions = rule->actions;
+	if (list_length(actions) != 1)
+		elog(ERROR,
+			 "the rule for materialized view \"%s\" is not a single action",
+			 RelationGetRelationName(matviewRel));
+
+	/*
+	 * The stored query was rewritten at the time of the MV definition, but
+	 * has not been scribbled on by the planner.
+	 */
+	return linitial_node(Query, actions);
+}
+
+
+/* ----------------------------------------------------
+ *		Incremental View Maintenance routines
+ * ---------------------------------------------------
+ */
+
+/*
+ * IVM_immediate_before
+ *
+ * IVM trigger function invoked before base table is modified. If this is
+ * invoked firstly in the same statement, we save the transaction id and the
+ * command id at that time.
+ */
+Datum
+IVM_immediate_before(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	char	   *matviewOid_text = trigdata->tg_trigger->tgargs[0];
+	char	   *ex_lock_text = trigdata->tg_trigger->tgargs[1];
+	Oid			matviewOid;
+	MV_TriggerHashEntry *entry;
+	bool	found;
+	bool	ex_lock;
+
+	matviewOid = DatumGetObjectId(DirectFunctionCall1(oidin, CStringGetDatum(matviewOid_text)));
+	ex_lock = DatumGetBool(DirectFunctionCall1(boolin, CStringGetDatum(ex_lock_text)));
+
+	/* If the view has more than one tables, we have to use an exclusive lock. */
+	if (ex_lock)
+	{
+		/*
+		 * Wait for concurrent transactions which update this materialized view at
+		 * READ COMMITED. This is needed to see changes committed in other
+		 * transactions. No wait and raise an error at REPEATABLE READ or
+		 * SERIALIZABLE to prevent update anomalies of matviews.
+		 * XXX: dead-lock is possible here.
+		 */
+		if (!IsolationUsesXactSnapshot())
+			LockRelationOid(matviewOid, ExclusiveLock);
+		else if (!ConditionalLockRelationOid(matviewOid, ExclusiveLock))
+		{
+			/* try to throw error by name; relation could be deleted... */
+			char	   *relname = get_rel_name(matviewOid);
+
+			if (!relname)
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						errmsg("could not obtain lock on materialized view during incremental maintenance")));
+
+			ereport(ERROR,
+					(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+					errmsg("could not obtain lock on materialized view \"%s\" during incremental maintenance",
+							relname)));
+		}
+	}
+	else
+		LockRelationOid(matviewOid, RowExclusiveLock);
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_trigger_info)
+		mv_InitHashTables();
+
+	entry = (MV_TriggerHashEntry *) hash_search(mv_trigger_info,
+											  (void *) &matviewOid,
+											  HASH_ENTER, &found);
+
+	/* On the first BEFORE to update the view, initialize trigger data */
+	if (!found)
+	{
+		Snapshot snapshot = GetActiveSnapshot();
+
+		entry->matview_id = matviewOid;
+		entry->before_trig_count = 0;
+		entry->after_trig_count = 0;
+		entry->xid = GetCurrentTransactionId();
+		entry->cid = snapshot->curcid;
+		entry->tables = NIL;
+		entry->has_old = false;
+		entry->has_new = false;
+	}
+
+	entry->before_trig_count++;
+
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * IVM_immediate_before
+ *
+ * IVM trigger function invoked after base table is modified.
+ * For each table, tuplestores of transition tables are collected.
+ * and after the last modification
+ */
+Datum
+IVM_immediate_maintenance(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	Relation	rel;
+	Oid			relid;
+	Oid			matviewOid;
+	Query	   *query;
+	Query	   *rewritten = NULL;
+	char	   *matviewOid_text = trigdata->tg_trigger->tgargs[0];
+	Relation	matviewRel;
+	int old_depth = matview_maintenance_depth;
+
+	Oid			relowner;
+	Tuplestorestate *old_tuplestore = NULL;
+	Tuplestorestate *new_tuplestore = NULL;
+	DestReceiver *dest_new = NULL, *dest_old = NULL;
+	Oid			save_userid;
+	int			save_sec_context;
+	int			save_nestlevel;
+
+	MV_TriggerHashEntry *entry;
+	MV_TriggerTable		*table;
+	bool	found;
+
+	ParseState		 *pstate;
+	QueryEnvironment *queryEnv = create_queryEnv();
+	MemoryContext	oldcxt;
+	ListCell   *lc;
+	int			i;
+
+
+	/* Create a ParseState for rewriting the view definition query */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	rel = trigdata->tg_relation;
+	relid = rel->rd_id;
+
+	matviewOid = DatumGetObjectId(DirectFunctionCall1(oidin, CStringGetDatum(matviewOid_text)));
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_trigger_info)
+		mv_InitHashTables();
+
+	/* get the entry for this materialized view */
+	entry = (MV_TriggerHashEntry *) hash_search(mv_trigger_info,
+											  (void *) &matviewOid,
+											  HASH_FIND, &found);
+	Assert (found && entry != NULL);
+	entry->after_trig_count++;
+
+	/* search the entry for the modified table and create new entry if not found */
+	found = false;
+	foreach(lc, entry->tables)
+	{
+		table = (MV_TriggerTable *) lfirst(lc);
+		if (table->table_id == relid)
+		{
+			found = true;
+			break;
+		}
+	}
+	if (!found)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		table = (MV_TriggerTable *) palloc0(sizeof(MV_TriggerTable));
+		table->table_id = relid;
+		table->old_tuplestores = NIL;
+		table->new_tuplestores = NIL;
+		table->old_rtes = NIL;
+		table->new_rtes = NIL;
+		table->rte_indexes = NIL;
+		entry->tables = lappend(entry->tables, table);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* Save the transition tables and make a request to not free immediately */
+	if (trigdata->tg_oldtable)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+		table->old_tuplestores = lappend(table->old_tuplestores, trigdata->tg_oldtable);
+		entry->has_old = true;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (trigdata->tg_newtable)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+		table->new_tuplestores = lappend(table->new_tuplestores, trigdata->tg_newtable);
+		entry->has_new = true;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (entry->has_new || entry->has_old)
+	{
+		CmdType cmd;
+
+		if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+			cmd = CMD_INSERT;
+		else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+			cmd = CMD_DELETE;
+		else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+			cmd = CMD_UPDATE;
+		else
+			elog(ERROR,"unsupported trigger type");
+
+		/* Prolong lifespan of transition tables to the end of the last AFTER trigger */
+		SetTransitionTablePreserved(relid, cmd);
+	}
+
+
+	/* If this is not the last AFTER trigger call, immediately exit. */
+	Assert (entry->before_trig_count >= entry->after_trig_count);
+	if (entry->before_trig_count != entry->after_trig_count)
+		return PointerGetDatum(NULL);
+
+	/*
+	 * If this is the last AFTER trigger call, continue and update the view.
+	 */
+
+	/*
+	 * Advance command counter to make the updated base table row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+
+	matviewRel = table_open(matviewOid, NoLock);
+
+	/* get view query*/
+	query = get_matview_query(matviewRel);
+
+	/* Make sure it is a materialized view. */
+	Assert(matviewRel->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Get and push the latast snapshot to see any changes which is committed
+	 * during waiting in other transactions at READ COMMITTED level.
+	 */
+	PushActiveSnapshot(GetTransactionSnapshot());
+
+	/*
+	 * Check for active uses of the relation in the current transaction, such
+	 * as open scans.
+	 *
+	 * NB: We count on this to protect us against problems with refreshing the
+	 * data using TABLE_INSERT_FROZEN.
+	 */
+	CheckTableNotInUse(matviewRel, "refresh a materialized view incrementally");
+
+	/*
+	 * Switch to the owner's userid, so that any functions are run as that
+	 * user.  Also arrange to make GUC variable changes local to this command.
+	 * We will switch modes when we are about to execute user code.
+	 */
+	relowner = matviewRel->rd_rel->relowner;
+	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+	save_nestlevel = NewGUCNestLevel();
+
+	/*
+	 * rewrite query for calculating deltas
+	 */
+
+	rewritten = copyObject(query);
+
+	/* Replace resnames in a target list with materialized view's attnames */
+	i = 0;
+	foreach (lc, rewritten->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char *resname = NameStr(attr->attname);
+
+		tle->resname = pstrdup(resname);
+		i++;
+	}
+
+	/* Set all tables in the query to pre-update state */
+	rewritten = rewrite_query_for_preupdate_state(rewritten, entry->tables,
+												  entry->xid, entry->cid,
+												  pstate);
+	/* Rewrite for DISTINCT clause */
+	rewritten = rewrite_query_for_distinct(rewritten, pstate);
+
+	/* Create tuplestores to store view deltas */
+	if (entry->has_old)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		old_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+		dest_old = CreateDestReceiver(DestTuplestore);
+		SetTuplestoreDestReceiverParams(dest_old,
+									old_tuplestore,
+									TopTransactionContext,
+									false,
+									NULL,
+									NULL);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (entry->has_new)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		new_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+		dest_new = CreateDestReceiver(DestTuplestore);
+		SetTuplestoreDestReceiverParams(dest_new,
+									new_tuplestore,
+									TopTransactionContext,
+									false,
+									NULL,
+									NULL);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* for all modified tables */
+	foreach(lc, entry->tables)
+	{
+		ListCell *lc2;
+
+		table = (MV_TriggerTable *) lfirst(lc);
+
+		/* loop for self-join */
+		foreach(lc2, table->rte_indexes)
+		{
+			int	rte_index = lfirst_int(lc2);
+			TupleDesc		tupdesc_old;
+			TupleDesc		tupdesc_new;
+			bool	use_count = false;
+			char   *count_colname = NULL;
+
+			count_colname = pstrdup("__ivm_count__");
+
+			if (query->distinctClause)
+				use_count = true;
+
+			/* calculate delta tables */
+			calc_delta(table, rte_index, rewritten, dest_old, dest_new,
+					   &tupdesc_old, &tupdesc_new, queryEnv);
+
+			/* Set the table in the query to post-update state */
+			rewritten = rewrite_query_for_postupdate_state(rewritten, table, rte_index);
+
+			PG_TRY();
+			{
+				/* apply the delta tables to the materialized view */
+				apply_delta(matviewOid, old_tuplestore, new_tuplestore,
+							tupdesc_old, tupdesc_new, query, use_count,
+							count_colname);
+			}
+			PG_CATCH();
+			{
+				matview_maintenance_depth = old_depth;
+				PG_RE_THROW();
+			}
+			PG_END_TRY();
+
+			/* clear view delta tuplestores */
+			if (old_tuplestore)
+				tuplestore_clear(old_tuplestore);
+			if (new_tuplestore)
+				tuplestore_clear(new_tuplestore);
+		}
+	}
+
+	/* Clean up hash entry and delete tuplestores */
+	clean_up_IVM_hash_entry(entry);
+	if (old_tuplestore)
+	{
+		dest_old->rDestroy(dest_old);
+		tuplestore_end(old_tuplestore);
+	}
+	if (new_tuplestore)
+	{
+		dest_new->rDestroy(dest_new);
+		tuplestore_end(new_tuplestore);
+	}
+
+	/* Pop the original snapshot. */
+	PopActiveSnapshot();
+
+	table_close(matviewRel, NoLock);
+
+	/* Roll back any GUC changes */
+	AtEOXact_GUC(false, save_nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(save_userid, save_sec_context);
+
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * rewrite_query_for_preupdate_state
+ *
+ * Rewrite the query so that base tables' RTEs will represent "pre-update"
+ * state of tables. This is necessary to calculate view delta after multiple
+ * tables are modified. xid and cid are the transaction id and command id
+ * before the first table was modified.
+ */
+static Query*
+rewrite_query_for_preupdate_state(Query *query, List *tables,
+								  TransactionId xid, CommandId cid,
+								  ParseState *pstate)
+{
+	ListCell *lc;
+	int num_rte = list_length(query->rtable);
+	int i;
+
+
+	/* register delta ENRs */
+	register_delta_ENRs(pstate, query, tables);
+
+	/* XXX: Is necessary? Is this right timing? */
+	AcquireRewriteLocks(query, true, false);
+
+	i = 1;
+	foreach(lc, query->rtable)
+	{
+		RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+
+		ListCell *lc2;
+		foreach(lc2, tables)
+		{
+			MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc2);
+			/*
+			 * if the modified table is found then replace the original RTE with
+			 * "pre-state" RTE and append its index to the list.
+			 */
+			if (r->relid == table->table_id)
+			{
+				lfirst(lc) = get_prestate_rte(r, table, xid, cid, pstate->p_queryEnv);
+				table->rte_indexes = lappend_int(table->rte_indexes, i);
+				break;
+			}
+		}
+
+		/* finish the loop if we processed all RTE included in the original query */
+		if (i++ >= num_rte)
+			break;
+	}
+
+	return query;
+}
+
+/*
+ * register_delta_ENRs
+ *
+ * For all modified tables, make ENRs for their transition tables
+ * and register them to the queryEnv. ENR's RTEs are also appended
+ * into the list in query tree.
+ */
+static void
+register_delta_ENRs(ParseState *pstate, Query *query, List *tables)
+{
+	QueryEnvironment *queryEnv = pstate->p_queryEnv;
+	ListCell *lc;
+	RangeTblEntry	*rte;
+
+	foreach(lc, tables)
+	{
+		MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc);
+		ListCell *lc2;
+		int count;
+
+		count = 0;
+		foreach(lc2, table->old_tuplestores)
+		{
+			Tuplestorestate *oldtable = (Tuplestorestate *) lfirst(lc2);
+			EphemeralNamedRelation enr =
+				palloc(sizeof(EphemeralNamedRelationData));
+			ParseNamespaceItem *nsitem;
+
+			enr->md.name = make_delta_enr_name("old", table->table_id, count);
+			enr->md.reliddesc = table->table_id;
+			enr->md.tupdesc = NULL;
+			enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+			enr->md.enrtuples = tuplestore_tuple_count(oldtable);
+			enr->reldata = oldtable;
+			register_ENR(queryEnv, enr);
+
+			nsitem = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+			rte = nsitem->p_rte;
+			/* if base table has RLS, set security condition to enr */
+			rte->securityQuals = get_securityQuals(table->table_id, list_length(query->rtable) + 1, query);
+
+			query->rtable = lappend(query->rtable, rte);
+			table->old_rtes = lappend(table->old_rtes, rte);
+
+			count++;
+		}
+
+		count = 0;
+		foreach(lc2, table->new_tuplestores)
+		{
+			Tuplestorestate *newtable = (Tuplestorestate *) lfirst(lc2);
+			EphemeralNamedRelation enr =
+				palloc(sizeof(EphemeralNamedRelationData));
+			ParseNamespaceItem *nsitem;
+
+			enr->md.name = make_delta_enr_name("new", table->table_id, count);
+			enr->md.reliddesc = table->table_id;
+			enr->md.tupdesc = NULL;
+			enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+			enr->md.enrtuples = tuplestore_tuple_count(newtable);
+			enr->reldata = newtable;
+			register_ENR(queryEnv, enr);
+
+			nsitem = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+			rte = nsitem->p_rte;
+			/* if base table has RLS, set security condition to enr*/
+			rte->securityQuals = get_securityQuals(table->table_id, list_length(query->rtable) + 1, query);
+
+			query->rtable = lappend(query->rtable, rte);
+			table->new_rtes = lappend(table->new_rtes, rte);
+
+			count++;
+		}
+	}
+}
+
+/*
+ * get_prestate_rte
+ *
+ * Rewrite RTE of the modified table to a subquery which represents
+ * "pre-state" table. The original RTE is saved in table->rte_original.
+ */
+static RangeTblEntry*
+get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
+				 TransactionId xid, CommandId cid,
+				 QueryEnvironment *queryEnv)
+{
+	StringInfoData str;
+	RawStmt *raw;
+	Query *sub;
+	Relation rel;
+	ParseState *pstate;
+	char *relname;
+	int i;
+
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	/*
+	 * We can use NoLock here since AcquireRewriteLocks should
+	 * have locked the rel already.
+	 */
+	rel = table_open(table->table_id, NoLock);
+	relname = quote_qualified_identifier(
+					get_namespace_name(RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+	table_close(rel, NoLock);
+
+	initStringInfo(&str);
+	appendStringInfo(&str,
+		"SELECT t.* FROM %s t"
+		" WHERE (age(t.xmin) - age(%u::text::xid) > 0) OR"
+		" (t.xmin = %u AND t.cmin::text::int < %u)",
+			relname, xid, xid, cid);
+
+	for (i = 0; i < list_length(table->old_tuplestores); i++)
+	{
+		appendStringInfo(&str, " UNION ALL ");
+		appendStringInfo(&str," SELECT * FROM %s",
+			make_delta_enr_name("old", table->table_id, i));
+	}
+
+	raw = (RawStmt*)linitial(raw_parser(str.data, RAW_PARSE_DEFAULT));
+	sub = transformStmt(pstate, raw->stmt);
+
+	/* If this query has setOperations, RTEs in rtables has a subquery which contains ENR */
+	if (sub->setOperations != NULL)
+	{
+		ListCell *lc;
+
+		/* add securityQuals for tuplestores */
+		foreach (lc, sub->rtable)
+		{
+			RangeTblEntry *rte;
+			RangeTblEntry *sub_rte;
+
+			rte = (RangeTblEntry *)lfirst(lc);
+			Assert(rte->subquery != NULL);
+
+			sub_rte = (RangeTblEntry *)linitial(rte->subquery->rtable);
+			if (sub_rte->rtekind == RTE_NAMEDTUPLESTORE)
+				/* rt_index is always 1, bacause subquery has enr_rte only */
+				sub_rte->securityQuals = get_securityQuals(sub_rte->relid, 1, sub);
+		}
+	}
+
+	/* save the original RTE */
+	table->original_rte = copyObject(rte);
+
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = sub;
+	rte->security_barrier = false;
+	/* Clear fields that should not be set in a subquery RTE */
+	rte->relid = InvalidOid;
+	rte->relkind = 0;
+	rte->rellockmode = 0;
+	rte->tablesample = NULL;
+	rte->inh = false;			/* must not be set for a subquery */
+
+	rte->requiredPerms = 0;		/* no permission check on subquery itself */
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->insertedCols = NULL;
+	rte->updatedCols = NULL;
+	rte->extraUpdatedCols = NULL;
+
+	return rte;
+}
+
+/*
+ * make_delta_enr_name
+ *
+ * Make a name for ENR of a transition table from the base table's oid.
+ * prefix will be "new" or "old" depending on its transition table kind..
+ */
+static char*
+make_delta_enr_name(const char *prefix, Oid relid, int count)
+{
+	char buf[NAMEDATALEN];
+	char *name;
+
+	snprintf(buf, NAMEDATALEN, "__ivm_%s_%u_%u", prefix, relid, count);
+	name = pstrdup(buf);
+
+	return name;
+}
+
+/*
+ * union_ENRs
+ *
+ * Make a single table delta by unionning all transition tables of the modified table
+ * whose RTE is specified by
+ */
+static RangeTblEntry*
+union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
+		   QueryEnvironment *queryEnv)
+{
+	StringInfoData str;
+	ParseState	*pstate;
+	RawStmt *raw;
+	Query *sub;
+	int	i;
+	RangeTblEntry *enr_rte;
+
+	/* Create a ParseState for rewriting the view definition query */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	initStringInfo(&str);
+
+	for (i = 0; i < list_length(enr_rtes); i++)
+	{
+		if (i > 0)
+			appendStringInfo(&str, " UNION ALL ");
+
+		appendStringInfo(&str,
+			" SELECT * FROM %s",
+			make_delta_enr_name(prefix, relid, i));
+	}
+
+	raw = (RawStmt*)linitial(raw_parser(str.data, RAW_PARSE_DEFAULT));
+	sub = transformStmt(pstate, raw->stmt);
+
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = sub;
+	rte->security_barrier = false;
+	/* Clear fields that should not be set in a subquery RTE */
+	rte->relid = InvalidOid;
+	rte->relkind = 0;
+	rte->rellockmode = 0;
+	rte->tablesample = NULL;
+	rte->inh = false;			/* must not be set for a subquery */
+
+	rte->requiredPerms = 0;		/* no permission check on subquery itself */
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->insertedCols = NULL;
+	rte->updatedCols = NULL;
+	rte->extraUpdatedCols = NULL;
+	/* if base table has RLS, set security condition to enr*/
+	enr_rte = (RangeTblEntry *)linitial(sub->rtable);
+	/* rt_index is always 1, bacause subquery has enr_rte only */
+	enr_rte->securityQuals = get_securityQuals(relid, 1, sub);
+
+	return rte;
+}
+
+/*
+ * rewrite_query_for_distinct
+ *
+ * Rewrite query for counting DISTINCT clause.
+ */
+static Query *
+rewrite_query_for_distinct(Query *query, ParseState *pstate)
+{
+	TargetEntry *tle_count;
+	FuncCall *fn;
+	Node *node;
+
+	/* Add count(*) for counting distinct tuples in views */
+	fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+	fn->agg_star = true;
+	if (!query->groupClause && !query->hasAggs)
+		query->groupClause = transformDistinctClause(NULL, &query->targetList, query->sortClause, false);
+
+	node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+	tle_count = makeTargetEntry((Expr *) node,
+								list_length(query->targetList) + 1,
+								pstrdup("__ivm_count__"),
+								false);
+	query->targetList = lappend(query->targetList, tle_count);
+	query->hasAggs = true;
+
+	return query;
+}
+
+/*
+ * calc_delta
+ *
+ * Calculate view deltas generated under the modification of a table specified
+ * by the RTE index.
+ */
+static void
+calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
+			DestReceiver *dest_old, DestReceiver *dest_new,
+			TupleDesc *tupdesc_old, TupleDesc *tupdesc_new,
+			QueryEnvironment *queryEnv)
+{
+	ListCell *lc = list_nth_cell(query->rtable, rte_index - 1);
+	RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+	/* Generate old delta */
+	if (list_length(table->old_rtes) > 0)
+	{
+		/* Replace the modified table with the old delta table and calculate the old view delta. */
+		lfirst(lc) = union_ENRs(rte, table->table_id, table->old_rtes, "old", queryEnv);
+		refresh_matview_datafill(dest_old, query, queryEnv, tupdesc_old, "");
+	}
+
+	/* Generate new delta */
+	if (list_length(table->new_rtes) > 0)
+	{
+		/* Replace the modified table with the new delta table and calculate the new view delta*/
+		lfirst(lc) = union_ENRs(rte, table->table_id, table->new_rtes, "new", queryEnv);
+		refresh_matview_datafill(dest_new, query, queryEnv, tupdesc_new, "");
+	}
+}
+
+/*
+ * rewrite_query_for_postupdate_state
+ *
+ * Rewrite the query so that the specified base table's RTEs will represent
+ * "post-update" state of tables. This is called after the view delta
+ * calculation due to changes on this table finishes.
+ */
+static Query*
+rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte_index)
+{
+	ListCell *lc = list_nth_cell(query->rtable, rte_index - 1);
+
+	/* Retore the original RTE */
+	lfirst(lc) = table->original_rte;
+
+	return query;
+}
+
+/*
+ * apply_delta
+ *
+ * Apply deltas to the materialized view. In outer join cases, this requires
+ * the view maintenance graph.
+ */
+static void
+apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
+			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
+			Query *query, bool use_count, char *count_colname)
+{
+	StringInfoData querybuf;
+	StringInfoData target_list_buf;
+	Relation	matviewRel;
+	char	   *matviewname;
+	ListCell	*lc;
+	int			i;
+	List	   *keys = NIL;
+
+
+	/*
+	 * get names of the materialized view and delta tables
+	 */
+
+	matviewRel = table_open(matviewOid, NoLock);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	/*
+	 * Build parts of the maintenance queries
+	 */
+
+	initStringInfo(&querybuf);
+	initStringInfo(&target_list_buf);
+
+	/* build string of target list */
+	for (i = 0; i < matviewRel->rd_att->natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char   *resname = NameStr(attr->attname);
+
+		if (i != 0)
+			appendStringInfo(&target_list_buf, ", ");
+		appendStringInfo(&target_list_buf, "%s", quote_qualified_identifier(NULL, resname));
+	}
+
+	i = 0;
+	foreach (lc, query->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+
+		i++;
+
+		if (tle->resjunk)
+			continue;
+
+		keys = lappend(keys, attr);
+	}
+
+	/* Start maintaining the materialized view. */
+	OpenMatViewIncrementalMaintenance();
+
+	/* Open SPI context. */
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	/* For tuple deletion */
+	if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
+	{
+		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		int				rc;
+
+		/* convert tuplestores to ENR, and register for SPI */
+		enr->md.name = pstrdup(OLD_DELTA_ENRNAME);
+		enr->md.reliddesc = InvalidOid;
+		enr->md.tupdesc = tupdesc_old;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(old_tuplestores);
+		enr->reldata = old_tuplestores;
+
+		rc = SPI_register_relation(enr);
+		if (rc != SPI_OK_REL_REGISTER)
+			elog(ERROR, "SPI_register failed");
+
+		if (use_count)
+			/* apply old delta and get rows to be recalculated */
+			apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME,
+									   keys, count_colname);
+		else
+			apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys);
+
+	}
+	/* For tuple insertion */
+	if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
+	{
+		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		int rc;
+
+		/* convert tuplestores to ENR, and register for SPI */
+		enr->md.name = pstrdup(NEW_DELTA_ENRNAME);
+		enr->md.reliddesc = InvalidOid;
+		enr->md.tupdesc = tupdesc_new;;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(new_tuplestores);
+		enr->reldata = new_tuplestores;
+
+		rc = SPI_register_relation(enr);
+		if (rc != SPI_OK_REL_REGISTER)
+			elog(ERROR, "SPI_register failed");
+
+		/* apply new delta */
+		if (use_count)
+			apply_new_delta_with_count(matviewname, NEW_DELTA_ENRNAME,
+								keys, &target_list_buf, count_colname);
+		else
+			apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf);
+	}
+
+	/* We're done maintaining the materialized view. */
+	CloseMatViewIncrementalMaintenance();
+
+	table_close(matviewRel, NoLock);
+
+	/* Close SPI context. */
+	if (SPI_finish() != SPI_OK_FINISH)
+		elog(ERROR, "SPI_finish failed");
+}
+
+/*
+ * apply_old_delta_with_count
+ *
+ * Execute a query for applying a delta table given by deltname_old
+ * which contains tuples to be deleted from to a materialized view given by
+ * matviewname.  This is used when counting is required, that is, the view
+ * has aggregate or distinct.
+ */
+static void
+apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
+				List *keys, const char *count_colname)
+{
+	StringInfoData	querybuf;
+	char   *match_cond;
+
+	/* build WHERE condition for searching tuples to be deleted */
+	match_cond = get_matching_condition_string(keys);
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"WITH t AS ("			/* collecting tid of target tuples in the view */
+						"SELECT diff.%s, "			/* count column */
+								"(diff.%s OPERATOR(pg_catalog.=) mv.%s) AS for_dlt, "
+								"mv.ctid "
+						"FROM %s AS mv, %s AS diff "
+						"WHERE %s"					/* tuple matching condition */
+					"), updt AS ("			/* update a tuple if this is not to be deleted */
+						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.-) t.%s "
+						"FROM t WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND NOT for_dlt "
+					")"
+					/* delete a tuple if this is to be deleted */
+					"DELETE FROM %s AS mv USING t "
+					"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt",
+					count_colname,
+					count_colname, count_colname,
+					matviewname, deltaname_old,
+					match_cond,
+					matviewname, count_colname, count_colname, count_colname,
+					matviewname);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_old_delta
+ *
+ * Execute a query for applying a delta table given by deltname_old
+ * which contains tuples to be deleted from to a materialized view given by
+ * matviewname.  This is used when counting is not required.
+ */
+static void
+apply_old_delta(const char *matviewname, const char *deltaname_old,
+				List *keys)
+{
+	StringInfoData	querybuf;
+	StringInfoData	keysbuf;
+	char   *match_cond;
+	ListCell *lc;
+
+	/* build WHERE condition for searching tuples to be deleted */
+	match_cond = get_matching_condition_string(keys);
+
+	/* build string of keys list */
+	initStringInfo(&keysbuf);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char   *resname = NameStr(attr->attname);
+		appendStringInfo(&keysbuf, "%s", quote_qualified_identifier("mv", resname));
+		if (lnext(keys, lc))
+			appendStringInfo(&keysbuf, ", ");
+	}
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+	"DELETE FROM %s WHERE ctid IN ("
+		"SELECT tid FROM (SELECT row_number() over (partition by %s) AS \"__ivm_row_number__\","
+								  "mv.ctid AS tid,"
+								  "diff.\"__ivm_count__\""
+						 "FROM %s AS mv, %s AS diff "
+						 "WHERE %s) v "
+					"WHERE v.\"__ivm_row_number__\" OPERATOR(pg_catalog.<=) v.\"__ivm_count__\")",
+					matviewname,
+					keysbuf.data,
+					matviewname, deltaname_old,
+					match_cond);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_new_delta_with_count
+ *
+ * Execute a query for applying a delta table given by deltname_new
+ * which contains tuples to be inserted into a materialized view given by
+ * matviewname.  This is used when counting is required, that is, the view
+ * has aggregate or distinct. Also, when a table in EXISTS sub queries
+ * is modified.
+ */
+static void
+apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
+				List *keys, StringInfo target_list, const char* count_colname)
+{
+	StringInfoData	querybuf;
+	StringInfoData	returning_keys;
+	ListCell	*lc;
+	char	*match_cond = "";
+
+	/* build WHERE condition for searching tuples to be updated */
+	match_cond = get_matching_condition_string(keys);
+
+	/* build string of keys list */
+	initStringInfo(&returning_keys);
+	if (keys)
+	{
+		foreach (lc, keys)
+		{
+			Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+			char   *resname = NameStr(attr->attname);
+			appendStringInfo(&returning_keys, "%s", quote_qualified_identifier("mv", resname));
+			if (lnext(keys, lc))
+				appendStringInfo(&returning_keys, ", ");
+		}
+	}
+	else
+		appendStringInfo(&returning_keys, "NULL");
+
+	/* Search for matching tuples from the view and update if found or insert if not. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"WITH updt AS ("		/* update a tuple if this exists in the view */
+						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.+) diff.%s "
+						"FROM %s AS diff "
+						"WHERE %s "					/* tuple matching condition */
+						"RETURNING %s"				/* returning keys of updated tuples */
+					") INSERT INTO %s (%s)"	/* insert a new tuple if this doesn't existw */
+						"SELECT %s FROM %s AS diff "
+						"WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE %s);",
+					matviewname, count_colname, count_colname, count_colname,
+					deltaname_new,
+					match_cond,
+					returning_keys.data,
+					matviewname, target_list->data,
+					target_list->data, deltaname_new,
+					match_cond);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_new_delta
+ *
+ * Execute a query for applying a delta table given by deltname_new
+ * which contains tuples to be inserted into a materialized view given by
+ * matviewname.  This is used when counting is not required.
+ */
+static void
+apply_new_delta(const char *matviewname, const char *deltaname_new,
+				StringInfo target_list)
+{
+	StringInfoData	querybuf;
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"INSERT INTO %s (%s) SELECT %s FROM ("
+						"SELECT diff.*, generate_series(1, diff.\"__ivm_count__\") "
+						"FROM %s AS diff) AS v",
+					matviewname, target_list->data, target_list->data,
+					deltaname_new);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * get_matching_condition_string
+ *
+ * Build a predicate string for looking for a tuple with given keys.
+ */
+static char *
+get_matching_condition_string(List *keys)
+{
+	StringInfoData match_cond;
+	ListCell	*lc;
+
+	/* If there is no key columns, the condition is always true. */
+	if (keys == NIL)
+		return "true";
+
+	initStringInfo(&match_cond);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char   *resname = NameStr(attr->attname);
+		char   *mv_resname = quote_qualified_identifier("mv", resname);
+		char   *diff_resname = quote_qualified_identifier("diff", resname);
+		Oid		typid = attr->atttypid;
+
+		/* Considering NULL values, we can not use simple = operator. */
+		appendStringInfo(&match_cond, "(");
+		generate_equal(&match_cond, typid, mv_resname, diff_resname);
+		appendStringInfo(&match_cond, " OR (%s IS NULL AND %s IS NULL))",
+						 mv_resname, diff_resname);
+
+		if (lnext(keys, lc))
+			appendStringInfo(&match_cond, " AND ");
+	}
+
+	return match_cond.data;
+}
+
+/*
+ * generate_equals
+ *
+ * Generate an equality clause using given operands' default equality
+ * operator.
+ */
+static void
+generate_equal(StringInfo querybuf, Oid opttype,
+			   const char *leftop, const char *rightop)
+{
+	TypeCacheEntry *typentry;
+
+	typentry = lookup_type_cache(opttype, TYPECACHE_EQ_OPR);
+	if (!OidIsValid(typentry->eq_opr))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_FUNCTION),
+				 errmsg("could not identify an equality operator for type %s",
+						format_type_be(opttype))));
+
+	generate_operator_clause(querybuf,
+							 leftop, opttype,
+							 typentry->eq_opr,
+							 rightop, opttype);
+}
+
+/*
+ * mv_InitHashTables
+ */
+static void
+mv_InitHashTables(void)
+{
+	HASHCTL		ctl;
+
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(MV_TriggerHashEntry);
+	mv_trigger_info = hash_create("MV trigger info",
+								 MV_INIT_QUERYHASHSIZE,
+								 &ctl, HASH_ELEM | HASH_BLOBS);
+}
+
+/*
+ * AtAbort_IVM
+ *
+ * Clean up hash entries for all materialized views. This is called at
+ * transaction abort.
+ */
+void
+AtAbort_IVM()
+{
+	HASH_SEQ_STATUS seq;
+	MV_TriggerHashEntry *entry;
+
+	if (mv_trigger_info)
+	{
+		hash_seq_init(&seq, mv_trigger_info);
+		while ((entry = hash_seq_search(&seq)) != NULL)
+			clean_up_IVM_hash_entry(entry);
+	}
+}
+
+/*
+ * clean_up_IVM_hash_entry
+ *
+ * Clean up tuple stores and hash entries for a materialized view after its
+ * maintenance finished.
+ */
+static void
+clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry)
+{
+	bool found;
+	ListCell *lc;
+
+	foreach(lc, entry->tables)
+	{
+		MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc);
+
+		list_free(table->old_tuplestores);
+		list_free(table->new_tuplestores);
+	}
+	list_free(entry->tables);
+
+	hash_search(mv_trigger_info, (void *) &entry->matview_id, HASH_REMOVE, &found);
+}
+
+/*
+ * isIvmName
+ *
+ * Check if this is a IVM hidden column from the name.
+ */
+bool
+isIvmName(const char *s)
+{
+	if (s)
+		return (strncmp(s, "__ivm_", 6) == 0);
+	return false;
+}
+
+/*
+ * get_securityQuals
+ *
+ * Get row security policy on a relation.
+ * This is used by IVM for copying RLS from base table to enr.
+ */
+static List *
+get_securityQuals(Oid relId, int rt_index, Query *query)
+{
+	ParseState *pstate;
+	Relation rel;
+	ParseNamespaceItem *nsitem;
+	RangeTblEntry *rte;
+	List *securityQuals;
+	List *withCheckOptions;
+	bool  hasRowSecurity;
+	bool  hasSubLinks;
+
+	securityQuals = NIL;
+	pstate = make_parsestate(NULL);
+
+	rel = table_open(relId, NoLock);
+	nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, false);
+	rte = nsitem->p_rte;
+
+	get_row_security_policies(query, rte, rt_index,
+							  &securityQuals, &withCheckOptions,
+							  &hasRowSecurity, &hasSubLinks);
+
+	/*
+	 * Make sure the query is marked correctly if row level security
+	 * applies, or if the new quals had sublinks.
+	 */
+	if (hasRowSecurity)
+		query->hasRowSecurity = true;
+	if (hasSubLinks)
+		query->hasSubLinks = true;
+
+	table_close(rel, NoLock);
+
+	return securityQuals;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2cd8546d47..1c85df888e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
+#include "commands/matview.h"
 #include "commands/event_trigger.h"
 #include "commands/policy.h"
 #include "commands/sequence.h"
@@ -3441,6 +3442,14 @@ renameatt_internal(Oid myrelid,
 	targetrelation = relation_open(myrelid, AccessExclusiveLock);
 	renameatt_check(myrelid, RelationGetForm(targetrelation), recursing);
 
+	/*
+	 * Don't rename IVM columns.
+	 */
+	if (RelationIsIVM(targetrelation) && isIvmName(oldattname))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("IVM column can not be renamed")));
+
 	/*
 	 * if the 'recurse' flag is set then we are supposed to rename this
 	 * attribute in all classes that inherit from 'relname' (as well as in
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 0490bce664..4afc8f5826 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2949,6 +2949,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(relisivm);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 55f41263ee..08d7805b7c 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -3131,6 +3131,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(relisivm);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index cfd3ce68b4..e5a9467e99 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3436,6 +3436,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 5165fb3b93..50cc852c32 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1669,6 +1669,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 5448cb01fa..b08c742678 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -36,6 +36,7 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 #include "utils/varlena.h"
+#include "commands/matview.h"
 
 
 /*
@@ -79,7 +80,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							int count, int offset,
 							int rtindex, int sublevels_up,
 							int location, bool include_dropped,
-							List **colnames, List **colvars);
+							List **colnames, List **colvars, bool is_ivm);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
 
@@ -1444,6 +1445,7 @@ addRangeTableEntry(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -1532,6 +1534,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -2688,7 +2691,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 						expandTupleDesc(tupdesc, rte->eref,
 										rtfunc->funccolcount, atts_done,
 										rtindex, sublevels_up, location,
-										include_dropped, colnames, colvars);
+										include_dropped, colnames, colvars, false);
 					}
 					else if (functypclass == TYPEFUNC_SCALAR)
 					{
@@ -2956,7 +2959,7 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
 	expandTupleDesc(rel->rd_att, eref, rel->rd_att->natts, 0,
 					rtindex, sublevels_up,
 					location, include_dropped,
-					colnames, colvars);
+					colnames, colvars, RelationIsIVM(rel));
 	relation_close(rel, AccessShareLock);
 }
 
@@ -2973,7 +2976,7 @@ static void
 expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 				int rtindex, int sublevels_up,
 				int location, bool include_dropped,
-				List **colnames, List **colvars)
+				List **colnames, List **colvars, bool is_ivm)
 {
 	ListCell   *aliascell;
 	int			varattno;
@@ -2986,6 +2989,9 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 	{
 		Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
 
+		if (is_ivm && isIvmName(NameStr(attr->attname)) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		if (attr->attisdropped)
 		{
 			if (include_dropped)
@@ -3140,6 +3146,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 		Var		   *varnode = (Var *) lfirst(var);
 		TargetEntry *te;
 
+		/* if transform * into columnlist with IMMV, remove IVM columns */
+		if (rte->relisivm && isIvmName(label) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		te = makeTargetEntry((Expr *) varnode,
 							 (AttrNumber) pstate->p_next_resno++,
 							 label,
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 185bf5fbff..d8a8b66196 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -776,7 +776,8 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 														attr->atttypmod))));
 	}
 
-	if (i != resultDesc->natts)
+	/* No check for materialized views since this could have special columns for IVM */
+	if ((!isSelect || requireColumnNameMatch) && i != resultDesc->natts)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 isSelect ?
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6d378ff785..7d88a84682 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11882,4 +11882,12 @@
   prorettype => 'bytea', proargtypes => 'pg_brin_minmax_multi_summary',
   prosrc => 'brin_minmax_multi_summary_send' },
 
+# IVM
+{ oid => '786', descr => 'ivm trigger (before)',
+  proname => 'IVM_immediate_before', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_before' },
+{ oid => '787', descr => 'ivm trigger (after)',
+  proname => 'IVM_immediate_maintenance', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_maintenance' },
+
 ]
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index 54a38491fb..c369b3ba5e 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -16,6 +16,7 @@
 
 #include "catalog/objectaddress.h"
 #include "nodes/params.h"
+#include "nodes/pathnodes.h"
 #include "parser/parse_node.h"
 #include "tcop/dest.h"
 #include "utils/queryenvironment.h"
@@ -25,6 +26,11 @@ extern ObjectAddress ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *st
 									   ParamListInfo params, QueryEnvironment *queryEnv,
 									   QueryCompletion *qc);
 
+extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create);
+extern void CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create);
+
+extern Query *rewriteQueryForIMMV(Query *query, List *colNames);
+
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
 
 extern DestReceiver *CreateIntoRelDestReceiver(IntoClause *intoClause);
diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h
index a067da39d2..ec479db513 100644
--- a/src/include/commands/matview.h
+++ b/src/include/commands/matview.h
@@ -15,6 +15,7 @@
 #define MATVIEW_H
 
 #include "catalog/objectaddress.h"
+#include "fmgr.h"
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
 #include "tcop/dest.h"
@@ -23,6 +24,8 @@
 
 extern void SetMatViewPopulatedState(Relation relation, bool newstate);
 
+extern void SetMatViewIVMState(Relation relation, bool newstate);
+
 extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										ParamListInfo params, QueryCompletion *qc);
 
@@ -30,4 +33,9 @@ extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
 
 extern bool MatViewIncrementalMaintenanceIsEnabled(void);
 
+extern Datum IVM_immediate_before(PG_FUNCTION_ARGS);
+extern Datum IVM_immediate_maintenance(PG_FUNCTION_ARGS);
+extern void AtAbort_IVM(void);
+extern bool isIvmName(const char *s);
+
 #endif							/* MATVIEW_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index da02658c81..d1a754936b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1042,6 +1042,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		relisivm;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -2545,6 +2546,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		ivm;			/* incremental view maintenance is used by materialized view */
 } CreateStmt;
 
 /* ----------
-- 
2.17.1

v27-0007-Add-aggregates-support-in-IVM.patchtext/x-diff; name=v27-0007-Add-aggregates-support-in-IVM.patchDownload
From 3d0a4063f0eae1c6dd5938d0eb982ae7f0682f14 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Mon, 2 Aug 2021 14:59:27 +0900
Subject: [PATCH v27 7/9] Add aggregates support in IVM
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Currently, count, sum, avg, min and max are supported.

As a restriction, expressions specified in GROUP BY must appear in
the target list because tuples to be updated in IMMV are identified
by using this group key. However, in the case of aggregates without
GROUP BY, there is only one tuple in the view, so keys are not uses
to identify tuples.

When creating a IMMV, in addition to __ivm_count column, some hidden
columns for each aggregate are added to the target list. For example,
names of these hidden columns are ivm_count_avg and ivm_sum_avg for
the average function, and so on.

In the case of views without aggregate functions, only the number of
tuple multiplicities in __ivm_count__ column are updated at incremental
maintenance. On the other hand, in the case of view with aggregates,
the aggregated values and related hidden columns are also updated. The
way of update depends the kind of aggregate function. Specifically,
sum and count are updated by simply adding or subtracting delta value
calculated from delta tables. avg is updated by using values of sum
and count stored in views as hidden columns and deltas calculated
from delta tables.

In min or max cases, it becomes more complicated. For an example of min,
when tuples are inserted, the smaller value between the current min value
in the view and the value calculated from the new delta table is used.
When tuples are deleted, if the current min value in the view equals to
the min in the old delta table, we need re-computation the latest min
value from base tables. Otherwise, the current value in the view remains.

As to sum, avg, min, and max (any aggregate functions except count),
NULL in input values is ignored, and this returns a null value when no
rows are selected. To support this specification, the number of not-NULL
input values is counted and stored in views as a hidden column. In the
case of count(), count(x) returns zero when no rows are selected, and
count(*) doesn't ignore NULL input. These specification are also supported.
---
 src/backend/commands/createas.c |  299 ++++++++-
 src/backend/commands/matview.c  | 1023 ++++++++++++++++++++++++++++++-
 src/include/commands/createas.h |    1 +
 3 files changed, 1281 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 1224a3b075..d2ae50d5ee 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/regproc.h"
 #include "utils/rel.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
@@ -80,6 +81,11 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_intorel;
 
+typedef struct
+{
+	bool	has_agg;
+} check_ivm_restriction_context;
+
 /* utility functions for CTAS definition creation */
 static ObjectAddress create_ctas_internal(List *attrList, IntoClause *into);
 static ObjectAddress create_ctas_nodata(List *tlist, IntoClause *into);
@@ -94,9 +100,9 @@ static void CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid mat
 									 Relids *relids, bool ex_lock);
 static void CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock);
 static void check_ivm_restriction(Node *node);
-static bool check_ivm_restriction_walker(Node *node, void *context);
-static Bitmapset *get_primary_key_attnos_from_query(Query *qry, List **constraintList, bool is_create);
+static bool check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context);
 static Bitmapset *get_primary_key_attnos_from_query(Query *query, List **constraintList, bool is_create);
+static bool check_aggregate_supports_ivm(Oid aggfnoid);
 
 /*
  * create_ctas_internal
@@ -429,6 +435,7 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
  * rewriteQueryForIMMV -- rewrite view definition query for IMMV
  *
  * count(*) is added for counting distinct tuples in views.
+ * Also, additional hidden columns are added for aggregate values.
  */
 Query *
 rewriteQueryForIMMV(Query *query, List *colNames)
@@ -443,14 +450,46 @@ rewriteQueryForIMMV(Query *query, List *colNames)
 	rewritten = copyObject(query);
 	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
 
-	/*
-	 * Convert DISTINCT to GROUP BY and add count(*) for counting distinct
-	 * tuples in views.
-	 */
-	if (rewritten->distinctClause)
+	/* group keys must be in targetlist */
+	if (rewritten->groupClause)
 	{
+		ListCell *lc;
+		foreach(lc, rewritten->groupClause)
+		{
+			SortGroupClause *scl = (SortGroupClause *) lfirst(lc);
+			TargetEntry *tle = get_sortgroupclause_tle(scl, rewritten->targetList);
+
+			if (tle->resjunk)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("GROUP BY expression not appearing in select list is not supported on incrementally maintainable materialized view")));
+		}
+	}
+	/* Convert DISTINCT to GROUP BY.  count(*) will be added afterward. */
+	else if (!rewritten->hasAggs && rewritten->distinctClause)
 		rewritten->groupClause = transformDistinctClause(NULL, &rewritten->targetList, rewritten->sortClause, false);
 
+	/* Add additional columns for aggregate values */
+	if (rewritten->hasAggs)
+	{
+		ListCell *lc;
+		List *aggs = NIL;
+		AttrNumber next_resno = list_length(rewritten->targetList) + 1;
+
+		foreach(lc, rewritten->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+			char *resname = (colNames == NIL ? tle->resname : strVal(list_nth(colNames, tle->resno - 1)));
+
+			if (IsA(tle->expr, Aggref))
+				makeIvmAggColumn(pstate, (Aggref *)tle->expr, resname, &next_resno, &aggs);
+		}
+		rewritten->targetList = list_concat(rewritten->targetList, aggs);
+	}
+
+	/* Add count(*) for counting distinct tuples in views */
+	if (rewritten->distinctClause || rewritten->hasAggs)
+	{
 		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
 		fn->agg_star = true;
 
@@ -467,6 +506,91 @@ rewriteQueryForIMMV(Query *query, List *colNames)
 	return rewritten;
 }
 
+/*
+ * makeIvmAggColumn -- make additional aggregate columns for IVM
+ *
+ * For an aggregate column specified by aggref, additional aggregate columns
+ * are added, which are used to calculate the new aggregate value in IMMV.
+ * An additional aggregate columns has a name based on resname
+ * (ex. ivm_count_resname), and resno specified by next_resno. The created
+ * columns are returned to aggs, and the resno for the next column is also
+ * returned to next_resno.
+ *
+ * Currently, an additional count() is created for aggref other than count.
+ * In addition, sum() is created for avg aggregate column.
+ */
+void
+makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber *next_resno, List **aggs)
+{
+	TargetEntry *tle_count;
+	Node *node;
+	FuncCall *fn;
+	Const	*dmy_arg = makeConst(INT4OID,
+								 -1,
+								 InvalidOid,
+								 sizeof(int32),
+								 Int32GetDatum(1),
+								 false,
+								 true); /* pass by value */
+	const char *aggname = get_func_name(aggref->aggfnoid);
+
+	/*
+	 * For aggregate functions except count, add count() func with the same arg parameters.
+	 * This count result is used for determining if the aggregate value should be NULL or not.
+	 * Also, add sum() func for avg because we need to calculate an average value as sum/count.
+	 *
+	 * XXX: If there are same expressions explicitly in the target list, we can use this instead
+	 * of adding new duplicated one.
+	 */
+	if (strcmp(aggname, "count") != 0)
+	{
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+
+		/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+		node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+		((Aggref *)node)->args = aggref->args;
+
+		tle_count = makeTargetEntry((Expr *) node,
+									*next_resno,
+									pstrdup(makeObjectName("__ivm_count",resname, "_")),
+									false);
+		*aggs = lappend(*aggs, tle_count);
+		(*next_resno)++;
+	}
+	if (strcmp(aggname, "avg") == 0)
+	{
+		List *dmy_args = NIL;
+		ListCell *lc;
+		foreach(lc, aggref->aggargtypes)
+		{
+			Oid		typeid = lfirst_oid(lc);
+			Type	type = typeidType(typeid);
+
+			Const *con = makeConst(typeid,
+								   -1,
+								   typeTypeCollation(type),
+								   typeLen(type),
+								   (Datum) 0,
+								   true,
+								   typeByVal(type));
+			dmy_args = lappend(dmy_args, con);
+			ReleaseSysCache(type);
+		}
+		fn = makeFuncCall(list_make1(makeString("sum")), NIL, COERCE_EXPLICIT_CALL, -1);
+
+		/* Make a Func with dummy args, and then override this by the original agg's args. */
+		node = ParseFuncOrColumn(pstate, fn->funcname, dmy_args, NULL, fn, false, -1);
+		((Aggref *)node)->args = aggref->args;
+
+		tle_count = makeTargetEntry((Expr *) node,
+									*next_resno,
+									pstrdup(makeObjectName("__ivm_sum",resname, "_")),
+									false);
+		*aggs = lappend(*aggs, tle_count);
+		(*next_resno)++;
+	}
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -920,11 +1044,13 @@ CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock
 static void
 check_ivm_restriction(Node *node)
 {
-	check_ivm_restriction_walker(node, NULL);
+	check_ivm_restriction_context context = {false};
+
+	check_ivm_restriction_walker(node, &context);
 }
 
 static bool
-check_ivm_restriction_walker(Node *node, void *context)
+check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context)
 {
 	if (node == NULL)
 		return false;
@@ -1005,6 +1131,8 @@ check_ivm_restriction_walker(Node *node, void *context)
 					}
 				}
 
+				context->has_agg |= qry->hasAggs;
+
 				/* restrictions for rtable */
 				foreach(lc, qry->rtable)
 				{
@@ -1053,7 +1181,7 @@ check_ivm_restriction_walker(Node *node, void *context)
 
 				}
 
-				query_tree_walker(qry, check_ivm_restriction_walker, NULL, QTW_IGNORE_RANGE_TABLE);
+				query_tree_walker(qry, check_ivm_restriction_walker, (void *) context, QTW_IGNORE_RANGE_TABLE);
 
 				break;
 			}
@@ -1064,8 +1192,12 @@ check_ivm_restriction_walker(Node *node, void *context)
 						ereport(ERROR,
 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								 errmsg("column name %s is not supported on incrementally maintainable materialized view", tle->resname)));
+				if (context->has_agg && !IsA(tle->expr, Aggref) && contain_aggs_of_level((Node *) tle->expr, 0))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("expression containing an aggregate in it is not supported on incrementally maintainable materialized view")));
 
-				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
 				break;
 			}
 		case T_JoinExpr:
@@ -1077,14 +1209,36 @@ check_ivm_restriction_walker(Node *node, void *context)
 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								 errmsg("OUTER JOIN is not supported on incrementally maintainable materialized view")));
 
-				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
+				break;
 			}
-			break;
 		case T_Aggref:
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("aggregate function is not supported on incrementally maintainable materialized view")));
-			break;
+			{
+				/* Check if this supports IVM */
+				Aggref *aggref = (Aggref *) node;
+				const char *aggname = format_procedure(aggref->aggfnoid);
+
+				if (aggref->aggfilter != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with FILTER clause is not supported on incrementally maintainable materialized view")));
+
+				if (aggref->aggdistinct != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with DISTINCT arguments is not supported on incrementally maintainable materialized view")));
+
+				if (aggref->aggorder != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with ORDER clause is not supported on incrementally maintainable materialized view")));
+
+				if (!check_aggregate_supports_ivm(aggref->aggfnoid))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function %s is not supported on incrementally maintainable materialized view", aggname)));
+				break;
+			}
 		default:
 			expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
 			break;
@@ -1092,6 +1246,91 @@ check_ivm_restriction_walker(Node *node, void *context)
 	return false;
 }
 
+/*
+ * check_aggregate_supports_ivm
+ *
+ * Check if the given aggregate function is supporting IVM
+ */
+static bool
+check_aggregate_supports_ivm(Oid aggfnoid)
+{
+	switch (aggfnoid)
+	{
+		/* count */
+		case F_COUNT_ANY:
+		case F_COUNT_:
+
+		/* sum */
+		case F_SUM_INT8:
+		case F_SUM_INT4:
+		case F_SUM_INT2:
+		case F_SUM_FLOAT4:
+		case F_SUM_FLOAT8:
+		case F_SUM_MONEY:
+		case F_SUM_INTERVAL:
+		case F_SUM_NUMERIC:
+
+		/* avg */
+		case F_AVG_INT8:
+		case F_AVG_INT4:
+		case F_AVG_INT2:
+		case F_AVG_NUMERIC:
+		case F_AVG_FLOAT4:
+		case F_AVG_FLOAT8:
+		case F_AVG_INTERVAL:
+
+		/* min */
+		case F_MIN_ANYARRAY:
+		case F_MIN_INT8:
+		case F_MIN_INT4:
+		case F_MIN_INT2:
+		case F_MIN_OID:
+		case F_MIN_FLOAT4:
+		case F_MIN_FLOAT8:
+		case F_MIN_DATE:
+		case F_MIN_TIME:
+		case F_MIN_TIMETZ:
+		case F_MIN_MONEY:
+		case F_MIN_TIMESTAMP:
+		case F_MIN_TIMESTAMPTZ:
+		case F_MIN_INTERVAL:
+		case F_MIN_TEXT:
+		case F_MIN_NUMERIC:
+		case F_MIN_BPCHAR:
+		case F_MIN_TID:
+		case F_MIN_ANYENUM:
+		case F_MIN_INET:
+		case F_MIN_PG_LSN:
+
+		/* max */
+		case F_MAX_ANYARRAY:
+		case F_MAX_INT8:
+		case F_MAX_INT4:
+		case F_MAX_INT2:
+		case F_MAX_OID:
+		case F_MAX_FLOAT4:
+		case F_MAX_FLOAT8:
+		case F_MAX_DATE:
+		case F_MAX_TIME:
+		case F_MAX_TIMETZ:
+		case F_MAX_MONEY:
+		case F_MAX_TIMESTAMP:
+		case F_MAX_TIMESTAMPTZ:
+		case F_MAX_INTERVAL:
+		case F_MAX_TEXT:
+		case F_MAX_NUMERIC:
+		case F_MAX_BPCHAR:
+		case F_MAX_TID:
+		case F_MAX_ANYENUM:
+		case F_MAX_INET:
+		case F_MAX_PG_LSN:
+			return true;
+
+		default:
+			return false;
+	}
+}
+
 /*
  * CreateIndexOnIMMV
  *
@@ -1149,7 +1388,29 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create)
 	index->concurrent = false;
 	index->if_not_exists = false;
 
-	if (query->distinctClause)
+	if (query->groupClause)
+	{
+		/* create unique constraint on GROUP BY expression columns */
+		foreach(lc, query->groupClause)
+		{
+			SortGroupClause *scl = (SortGroupClause *) lfirst(lc);
+			TargetEntry *tle = get_sortgroupclause_tle(scl, query->targetList);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+			IndexElem  *iparam;
+
+			iparam = makeNode(IndexElem);
+			iparam->name = pstrdup(NameStr(attr->attname));
+			iparam->expr = NULL;
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->opclassopts = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+		}
+	}
+	else if (query->distinctClause)
 	{
 		/* create unique constraint on all columns */
 		foreach(lc, query->targetList)
@@ -1207,7 +1468,7 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create)
 					(errmsg("could not create an index on materialized view \"%s\" automatically",
 							RelationGetRelationName(matviewRel)),
 					 errdetail("This target list does not have all the primary key columns, "
-							   "or this view does not contain DISTINCT clause."),
+							   "or this view does not contain GROUP BY or DISTINCT clause."),
 					 errhint("Create an index on the materialized view for efficient incremental maintenance.")));
 			return;
 		}
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index cb713328a0..721d91c009 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -82,6 +82,32 @@ typedef struct
 
 #define MV_INIT_QUERYHASHSIZE	16
 
+/* MV query type codes */
+#define MV_PLAN_RECALC			1
+#define MV_PLAN_SET_VALUE		2
+
+/*
+ * MI_QueryKey
+ *
+ * The key identifying a prepared SPI plan in our query hashtable
+ */
+typedef struct MV_QueryKey
+{
+	Oid			matview_id;	/* OID of materialized view */
+	int32		query_type;	/* query type ID, see MV_PLAN_XXX above */
+} MV_QueryKey;
+
+/*
+ * MV_QueryHashEntry
+ *
+ * Hash entry for cached plans used to maintain materialized views.
+ */
+typedef struct MV_QueryHashEntry
+{
+	MV_QueryKey key;
+	SPIPlanPtr	plan;
+} MV_QueryHashEntry;
+
 /*
  * MV_TriggerHashEntry
  *
@@ -118,8 +144,16 @@ typedef struct MV_TriggerTable
 	RangeTblEntry *original_rte;	/* the original RTE saved before rewriting query */
 } MV_TriggerTable;
 
+static HTAB *mv_query_cache = NULL;
 static HTAB *mv_trigger_info = NULL;
 
+/* kind of IVM operation for the view */
+typedef enum
+{
+	IVM_ADD,
+	IVM_SUB
+} IvmOp;
+
 /* ENR name for materialized view delta */
 #define NEW_DELTA_ENRNAME "new_delta"
 #define OLD_DELTA_ENRNAME "old_delta"
@@ -153,7 +187,7 @@ static RangeTblEntry *get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *tabl
 				 QueryEnvironment *queryEnv);
 static RangeTblEntry *union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
 		   QueryEnvironment *queryEnv);
-static Query *rewrite_query_for_distinct(Query *query, ParseState *pstate);
+static Query *rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate);
 
 static void calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
 			DestReceiver *dest_old, DestReceiver *dest_new,
@@ -164,19 +198,48 @@ static Query *rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *
 static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
 			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
 			Query *query, bool use_count, char *count_colname);
+static void append_set_clause_for_count(const char *resname, StringInfo buf_old,
+							StringInfo buf_new,StringInfo aggs_list);
+static void append_set_clause_for_sum(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list);
+static void append_set_clause_for_avg(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list,
+						  const char *aggtype);
+static void append_set_clause_for_minmax(const char *resname, StringInfo buf_old,
+							 StringInfo buf_new, StringInfo aggs_list,
+							 bool is_min);
+static char *get_operation_string(IvmOp op, const char *col, const char *arg1, const char *arg2,
+					 const char* count_col, const char *castType);
+static char *get_null_condition_string(IvmOp op, const char *arg1, const char *arg2,
+						  const char* count_col);
 static void apply_old_delta(const char *matviewname, const char *deltaname_old,
 				List *keys);
 static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
-				List *keys, const char *count_colname);
+				List *keys, StringInfo aggs_list, StringInfo aggs_set,
+				List *minmax_list, List *is_min_list,
+				const char *count_colname,
+				SPITupleTable **tuptable_recalc, uint64 *num_recalc);
 static void apply_new_delta(const char *matviewname, const char *deltaname_new,
 				StringInfo target_list);
 static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
-				List *keys, StringInfo target_list, const char* count_colname);
+				List *keys, StringInfo target_list, StringInfo aggs_set,
+				const char* count_colname);
 static char *get_matching_condition_string(List *keys);
+static char *get_returning_string(List *minmax_list, List *is_min_list, List *keys);
+static char *get_minmax_recalc_condition_string(List *minmax_list, List *is_min_list);
+static char *get_select_for_recalc_string(List *keys);
+static void recalc_and_set_values(SPITupleTable *tuptable_recalc, int64 num_tuples,
+					  List *namelist, List *keys, Relation matviewRel);
+static SPIPlanPtr get_plan_for_recalc(Oid matviewOid, List *namelist, List *keys, Oid *keyTypes);
+static SPIPlanPtr get_plan_for_set_values(Oid matviewOid, char *matviewname, List *namelist,
+						Oid *valTypes);
 static void generate_equal(StringInfo querybuf, Oid opttype,
 			   const char *leftop, const char *rightop);
 
 static void mv_InitHashTables(void);
+static SPIPlanPtr mv_FetchPreparedPlan(MV_QueryKey *key);
+static void mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan);
+static void mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type);
 static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry);
 
 static List *get_securityQuals(Oid relId, int rt_index, Query *query);
@@ -1470,8 +1533,8 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
 	rewritten = rewrite_query_for_preupdate_state(rewritten, entry->tables,
 												  entry->xid, entry->cid,
 												  pstate);
-	/* Rewrite for DISTINCT clause */
-	rewritten = rewrite_query_for_distinct(rewritten, pstate);
+	/* Rewrite for DISTINCT clause and aggregates functions */
+	rewritten = rewrite_query_for_distinct_and_aggregates(rewritten, pstate);
 
 	/* Create tuplestores to store view deltas */
 	if (entry->has_old)
@@ -1522,7 +1585,7 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
 
 			count_colname = pstrdup("__ivm_count__");
 
-			if (query->distinctClause)
+			if (query->hasAggs || query->distinctClause)
 				use_count = true;
 
 			/* calculate delta tables */
@@ -1884,17 +1947,34 @@ union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
 }
 
 /*
- * rewrite_query_for_distinct
+ * rewrite_query_for_distinct_and_aggregates
  *
- * Rewrite query for counting DISTINCT clause.
+ * Rewrite query for counting DISTINCT clause and aggregate functions.
  */
 static Query *
-rewrite_query_for_distinct(Query *query, ParseState *pstate)
+rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate)
 {
 	TargetEntry *tle_count;
 	FuncCall *fn;
 	Node *node;
 
+	/* For aggregate views */
+	if (query->hasAggs)
+	{
+		ListCell *lc;
+		List *aggs = NIL;
+		AttrNumber next_resno = list_length(query->targetList) + 1;
+
+		foreach(lc, query->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+			if (IsA(tle->expr, Aggref))
+				makeIvmAggColumn(pstate, (Aggref *)tle->expr, tle->resname, &next_resno, &aggs);
+		}
+		query->targetList = list_concat(query->targetList, aggs);
+	}
+
 	/* Add count(*) for counting distinct tuples in views */
 	fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
 	fn->agg_star = true;
@@ -1963,6 +2043,8 @@ rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte
 	return query;
 }
 
+#define IVM_colname(type, col) makeObjectName("__ivm_" type, col, "_")
+
 /*
  * apply_delta
  *
@@ -1976,11 +2058,16 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 {
 	StringInfoData querybuf;
 	StringInfoData target_list_buf;
+	StringInfo	aggs_list_buf = NULL;
+	StringInfo	aggs_set_old = NULL;
+	StringInfo	aggs_set_new = NULL;
 	Relation	matviewRel;
 	char	   *matviewname;
 	ListCell	*lc;
 	int			i;
 	List	   *keys = NIL;
+	List	   *minmax_list = NIL;
+	List	   *is_min_list = NIL;
 
 
 	/*
@@ -1998,6 +2085,15 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 	initStringInfo(&querybuf);
 	initStringInfo(&target_list_buf);
 
+	if (query->hasAggs)
+	{
+		if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
+			aggs_set_old = makeStringInfo();
+		if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
+			aggs_set_new = makeStringInfo();
+		aggs_list_buf = makeStringInfo();
+	}
+
 	/* build string of target list */
 	for (i = 0; i < matviewRel->rd_att->natts; i++)
 	{
@@ -2014,13 +2110,72 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 	{
 		TargetEntry *tle = (TargetEntry *) lfirst(lc);
 		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char *resname = NameStr(attr->attname);
 
 		i++;
 
 		if (tle->resjunk)
 			continue;
 
-		keys = lappend(keys, attr);
+		/*
+		 * For views without aggregates, all attributes are used as keys to identify a
+		 * tuple in a view.
+		 */
+		if (!query->hasAggs)
+			keys = lappend(keys, attr);
+
+		/* For views with aggregates, we need to build SET clause for updating aggregate
+		 * values. */
+		if (query->hasAggs && IsA(tle->expr, Aggref))
+		{
+			Aggref *aggref = (Aggref *) tle->expr;
+			const char *aggname = get_func_name(aggref->aggfnoid);
+
+			/*
+			 * We can use function names here because it is already checked if these
+			 * can be used in IMMV by its OID at the definition time.
+			 */
+
+			/* count */
+			if (!strcmp(aggname, "count"))
+				append_set_clause_for_count(resname, aggs_set_old, aggs_set_new, aggs_list_buf);
+
+			/* sum */
+			else if (!strcmp(aggname, "sum"))
+				append_set_clause_for_sum(resname, aggs_set_old, aggs_set_new, aggs_list_buf);
+
+			/* avg */
+			else if (!strcmp(aggname, "avg"))
+				append_set_clause_for_avg(resname, aggs_set_old, aggs_set_new, aggs_list_buf,
+										  format_type_be(aggref->aggtype));
+
+			/* min/max */
+			else if (!strcmp(aggname, "min") || !strcmp(aggname, "max"))
+			{
+				bool	is_min = (!strcmp(aggname, "min"));
+
+				append_set_clause_for_minmax(resname, aggs_set_old, aggs_set_new, aggs_list_buf, is_min);
+
+				/* make a resname list of min and max aggregates */
+				minmax_list = lappend(minmax_list, resname);
+				is_min_list = lappend_int(is_min_list, is_min);
+			}
+			else
+				elog(ERROR, "unsupported aggregate function: %s", aggname);
+		}
+	}
+
+	/* If we have GROUP BY clause, we use its entries as keys. */
+	if (query->hasAggs && query->groupClause)
+	{
+		foreach (lc, query->groupClause)
+		{
+			SortGroupClause *sgcl = (SortGroupClause *) lfirst(lc);
+			TargetEntry		*tle = get_sortgroupclause_tle(sgcl, query->targetList);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+
+			keys = lappend(keys, attr);
+		}
 	}
 
 	/* Start maintaining the materialized view. */
@@ -2034,6 +2189,8 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 	if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
 	{
 		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		SPITupleTable  *tuptable_recalc = NULL;
+		uint64			num_recalc;
 		int				rc;
 
 		/* convert tuplestores to ENR, and register for SPI */
@@ -2051,10 +2208,19 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		if (use_count)
 			/* apply old delta and get rows to be recalculated */
 			apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME,
-									   keys, count_colname);
+									   keys, aggs_list_buf, aggs_set_old,
+									   minmax_list, is_min_list,
+									   count_colname, &tuptable_recalc, &num_recalc);
 		else
 			apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys);
 
+		/*
+		 * If we have min or max, we might have to recalculate aggregate values from base tables
+		 * on some tuples. TIDs and keys such tuples are returned as a result of the above query.
+		 */
+		if (minmax_list && tuptable_recalc)
+			recalc_and_set_values(tuptable_recalc, num_recalc, minmax_list, keys, matviewRel);
+
 	}
 	/* For tuple insertion */
 	if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
@@ -2077,7 +2243,7 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		/* apply new delta */
 		if (use_count)
 			apply_new_delta_with_count(matviewname, NEW_DELTA_ENRNAME,
-								keys, &target_list_buf, count_colname);
+								keys, aggs_set_new, &target_list_buf, count_colname);
 		else
 			apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf);
 	}
@@ -2092,49 +2258,410 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		elog(ERROR, "SPI_finish failed");
 }
 
+/*
+ * append_set_clause_for_count
+ *
+ * Append SET clause string for count aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_count(const char *resname, StringInfo buf_old,
+							StringInfo buf_new,StringInfo aggs_list)
+{
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* resname = mv.resname - t.resname */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, resname, "mv", "t", NULL, NULL));
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* resname = mv.resname + diff.resname */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, resname, "mv", "diff", NULL, NULL));
+	}
+
+	appendStringInfo(aggs_list, ", %s",
+		quote_qualified_identifier("diff", resname)
+	);
+}
+
+/*
+ * append_set_clause_for_sum
+ *
+ * Append SET clause string for sum aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_sum(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list)
+{
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* sum = mv.sum - t.sum */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, resname, "mv", "t", count_col, NULL)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* sum = mv.sum + diff.sum */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, resname, "mv", "diff", count_col, NULL)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * append_set_clause_for_avg
+ *
+ * Append SET clause string for avg aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_avg(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list,
+						  const char *aggtype)
+{
+	char *sum_col = IVM_colname("sum", resname);
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* avg = (mv.sum - t.sum)::aggtype / (mv.count - t.count) */
+		appendStringInfo(buf_old,
+			", %s = %s OPERATOR(pg_catalog./) %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, aggtype),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+		/* sum = mv.sum - t.sum */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, sum_col),
+			get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, NULL)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* avg = (mv.sum + diff.sum)::aggtype / (mv.count + diff.count) */
+		appendStringInfo(buf_new,
+			", %s = %s OPERATOR(pg_catalog./) %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, aggtype),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+		/* sum = mv.sum + diff.sum */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, sum_col),
+			get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, NULL)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("sum", resname)),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * append_set_clause_for_minmax
+ *
+ * Append SET clause string for min or max aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ * is_min is true if this is min, false if not.
+ */
+static void
+append_set_clause_for_minmax(const char *resname, StringInfo buf_old,
+							 StringInfo buf_new, StringInfo aggs_list,
+							 bool is_min)
+{
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/*
+		 * If the new value doesn't became NULL then use the value remaining
+		 * in the view although this will be recomputated afterwords.
+		 */
+		appendStringInfo(buf_old,
+			", %s = CASE WHEN %s THEN NULL ELSE %s END",
+			quote_qualified_identifier(NULL, resname),
+			get_null_condition_string(IVM_SUB, "mv", "t", count_col),
+			quote_qualified_identifier("mv", resname)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/*
+		 * min = LEAST(mv.min, diff.min)
+		 * max = GREATEST(mv.max, diff.max)
+		 */
+		appendStringInfo(buf_new,
+			", %s = CASE WHEN %s THEN NULL ELSE %s(%s,%s) END",
+			quote_qualified_identifier(NULL, resname),
+			get_null_condition_string(IVM_ADD, "mv", "diff", count_col),
+
+			is_min ? "LEAST" : "GREATEST",
+			quote_qualified_identifier("mv", resname),
+			quote_qualified_identifier("diff", resname)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * get_operation_string
+ *
+ * Build a string to calculate the new aggregate values.
+ */
+static char *
+get_operation_string(IvmOp op, const char *col, const char *arg1, const char *arg2,
+					 const char* count_col, const char *castType)
+{
+	StringInfoData buf;
+	StringInfoData castString;
+	char   *col1 = quote_qualified_identifier(arg1, col);
+	char   *col2 = quote_qualified_identifier(arg2, col);
+	char	op_char = (op == IVM_SUB ? '-' : '+');
+
+	initStringInfo(&buf);
+	initStringInfo(&castString);
+
+	if (castType)
+		appendStringInfo(&castString, "::%s", castType);
+
+	if (!count_col)
+	{
+		/*
+		 * If the attributes don't have count columns then calc the result
+		 * by using the operator simply.
+		 */
+		appendStringInfo(&buf, "(%s OPERATOR(pg_catalog.%c) %s)%s",
+			col1, op_char, col2, castString.data);
+	}
+	else
+	{
+		/*
+		 * If the attributes have count columns then consider the condition
+		 * where the result becomes NULL.
+		 */
+		char *null_cond = get_null_condition_string(op, arg1, arg2, count_col);
+
+		appendStringInfo(&buf,
+			"(CASE WHEN %s THEN NULL "
+				"WHEN %s IS NULL THEN %s "
+				"WHEN %s IS NULL THEN %s "
+				"ELSE (%s OPERATOR(pg_catalog.%c) %s)%s END)",
+			null_cond,
+			col1, col2,
+			col2, col1,
+			col1, op_char, col2, castString.data
+		);
+	}
+
+	return buf.data;
+}
+
+/*
+ * get_null_condition_string
+ *
+ * Build a predicate string for CASE clause to check if an aggregate value
+ * will became NULL after the given operation is applied.
+ */
+static char *
+get_null_condition_string(IvmOp op, const char *arg1, const char *arg2,
+						  const char* count_col)
+{
+	StringInfoData null_cond;
+	initStringInfo(&null_cond);
+
+	switch (op)
+	{
+		case IVM_ADD:
+			appendStringInfo(&null_cond,
+				"%s OPERATOR(pg_catalog.=) 0 AND %s OPERATOR(pg_catalog.=) 0",
+				quote_qualified_identifier(arg1, count_col),
+				quote_qualified_identifier(arg2, count_col)
+			);
+			break;
+		case IVM_SUB:
+			appendStringInfo(&null_cond,
+				"%s OPERATOR(pg_catalog.=) %s",
+				quote_qualified_identifier(arg1, count_col),
+				quote_qualified_identifier(arg2, count_col)
+			);
+			break;
+		default:
+			elog(ERROR,"unknown operation");
+	}
+
+	return null_cond.data;
+}
+
+
 /*
  * apply_old_delta_with_count
  *
  * Execute a query for applying a delta table given by deltname_old
  * which contains tuples to be deleted from to a materialized view given by
  * matviewname.  This is used when counting is required, that is, the view
- * has aggregate or distinct.
+ * has aggregate or distinct. Also, when a table in EXISTS sub queries
+ * is modified.
+ *
+ * If the view desn't have aggregates or has GROUP BY, this requires a keys
+ * list to identify a tuple in the view. If the view has aggregates, this
+ * requires strings representing resnames of aggregates and SET clause for
+ * updating aggregate values.
+ *
+ * If the view has min or max aggregate, this requires a list of resnames of
+ * min/max aggregates and a list of boolean which represents which entries in
+ * minmax_list is min. These are necessary to check if we need to recalculate
+ * min or max aggregate values. In this case, this query returns TID and keys
+ * of tuples which need to be recalculated.  This result and the number of rows
+ * are stored in tuptables and num_recalc repectedly.
+ *
  */
 static void
 apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
-				List *keys, const char *count_colname)
+				List *keys, StringInfo aggs_list, StringInfo aggs_set,
+				List *minmax_list, List *is_min_list,
+				const char *count_colname,
+				SPITupleTable **tuptable_recalc, uint64 *num_recalc)
 {
 	StringInfoData	querybuf;
 	char   *match_cond;
+	char   *updt_returning = "";
+	char   *select_for_recalc = "SELECT";
+	bool	agg_without_groupby = (list_length(keys) == 0);
+
+	Assert(tuptable_recalc != NULL);
+	Assert(num_recalc != NULL);
 
 	/* build WHERE condition for searching tuples to be deleted */
 	match_cond = get_matching_condition_string(keys);
 
+	/*
+	 * We need a special RETURNING clause and SELECT statement for min/max to
+	 * check which tuple needs re-calculation from base tables.
+	 */
+	if (minmax_list)
+	{
+		updt_returning = get_returning_string(minmax_list, is_min_list, keys);
+		select_for_recalc = get_select_for_recalc_string(keys);
+	}
+
 	/* Search for matching tuples from the view and update or delete if found. */
 	initStringInfo(&querybuf);
 	appendStringInfo(&querybuf,
 					"WITH t AS ("			/* collecting tid of target tuples in the view */
 						"SELECT diff.%s, "			/* count column */
-								"(diff.%s OPERATOR(pg_catalog.=) mv.%s) AS for_dlt, "
+								"(diff.%s OPERATOR(pg_catalog.=) mv.%s AND %s) AS for_dlt, "
 								"mv.ctid "
+								"%s "				/* aggregate columns */
 						"FROM %s AS mv, %s AS diff "
 						"WHERE %s"					/* tuple matching condition */
 					"), updt AS ("			/* update a tuple if this is not to be deleted */
 						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.-) t.%s "
+											"%s"	/* SET clauses for aggregates */
 						"FROM t WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND NOT for_dlt "
-					")"
-					/* delete a tuple if this is to be deleted */
-					"DELETE FROM %s AS mv USING t "
-					"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt",
+						"%s"						/* RETURNING clause for recalc infomation */
+					"), dlt AS ("			/* delete a tuple if this is to be deleted */
+						"DELETE FROM %s AS mv USING t "
+						"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt"
+					") %s",							/* SELECT returning which tuples need to be recalculated */
 					count_colname,
-					count_colname, count_colname,
+					count_colname, count_colname, (agg_without_groupby ? "false" : "true"),
+					(aggs_list != NULL ? aggs_list->data : ""),
 					matviewname, deltaname_old,
 					match_cond,
 					matviewname, count_colname, count_colname, count_colname,
-					matviewname);
+					(aggs_set != NULL ? aggs_set->data : ""),
+					updt_returning,
+					matviewname,
+					select_for_recalc);
 
-	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_SELECT)
 		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+
+
+	/* Return tuples to be recalculated. */
+	if (minmax_list)
+	{
+		*tuptable_recalc = SPI_tuptable;
+		*num_recalc = SPI_processed;
+	}
+	else
+	{
+		*tuptable_recalc = NULL;
+		*num_recalc = 0;
+	}
 }
 
 /*
@@ -2194,10 +2721,15 @@ apply_old_delta(const char *matviewname, const char *deltaname_old,
  * matviewname.  This is used when counting is required, that is, the view
  * has aggregate or distinct. Also, when a table in EXISTS sub queries
  * is modified.
+ *
+ * If the view desn't have aggregates or has GROUP BY, this requires a keys
+ * list to identify a tuple in the view. If the view has aggregates, this
+ * requires strings representing SET clause for updating aggregate values.
  */
 static void
 apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
-				List *keys, StringInfo target_list, const char* count_colname)
+				List *keys, StringInfo aggs_set, StringInfo target_list,
+				const char* count_colname)
 {
 	StringInfoData	querybuf;
 	StringInfoData	returning_keys;
@@ -2228,6 +2760,7 @@ apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
 	appendStringInfo(&querybuf,
 					"WITH updt AS ("		/* update a tuple if this exists in the view */
 						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.+) diff.%s "
+											"%s "	/* SET clauses for aggregates */
 						"FROM %s AS diff "
 						"WHERE %s "					/* tuple matching condition */
 						"RETURNING %s"				/* returning keys of updated tuples */
@@ -2235,6 +2768,7 @@ apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
 						"SELECT %s FROM %s AS diff "
 						"WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE %s);",
 					matviewname, count_colname, count_colname, count_colname,
+					(aggs_set != NULL ? aggs_set->data : ""),
 					deltaname_new,
 					match_cond,
 					returning_keys.data,
@@ -2309,6 +2843,349 @@ get_matching_condition_string(List *keys)
 	return match_cond.data;
 }
 
+/*
+ * get_returning_string
+ *
+ * Build a string for RETURNING clause of UPDATE used in apply_old_delta_with_count.
+ * This clause returns ctid and a boolean value that indicates if we need to
+ * recalculate min or max value, for each updated row.
+ */
+static char *
+get_returning_string(List *minmax_list, List *is_min_list, List *keys)
+{
+	StringInfoData returning;
+	char		*recalc_cond;
+	ListCell	*lc;
+
+	Assert(minmax_list != NIL && is_min_list != NIL);
+	recalc_cond = get_minmax_recalc_condition_string(minmax_list, is_min_list);
+
+	initStringInfo(&returning);
+
+	appendStringInfo(&returning, "RETURNING mv.ctid AS tid, (%s) AS recalc", recalc_cond);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char *resname = NameStr(attr->attname);
+		appendStringInfo(&returning, ", %s", quote_qualified_identifier("mv", resname));
+	}
+
+	return returning.data;
+}
+
+/*
+ * get_minmax_recalc_condition_string
+ *
+ * Build a predicate string for checking if any min/max aggregate
+ * value needs to be recalculated.
+ */
+static char *
+get_minmax_recalc_condition_string(List *minmax_list, List *is_min_list)
+{
+	StringInfoData recalc_cond;
+	ListCell	*lc1, *lc2;
+
+	initStringInfo(&recalc_cond);
+
+	Assert (list_length(minmax_list) == list_length(is_min_list));
+
+	forboth (lc1, minmax_list, lc2, is_min_list)
+	{
+		char   *resname = (char *) lfirst(lc1);
+		bool	is_min = (bool) lfirst_int(lc2);
+		char   *op_str = (is_min ? ">=" : "<=");
+
+		appendStringInfo(&recalc_cond, "%s OPERATOR(pg_catalog.%s) %s",
+			quote_qualified_identifier("mv", resname),
+			op_str,
+			quote_qualified_identifier("t", resname)
+		);
+
+		if (lnext(minmax_list, lc1))
+			appendStringInfo(&recalc_cond, " OR ");
+	}
+
+	return recalc_cond.data;
+}
+
+/*
+ * get_select_for_recalc_string
+ *
+ * Build a query to return tid and keys of tuples which need
+ * recalculation. This is used as the result of the query
+ * built by apply_old_delta.
+ */
+static char *
+get_select_for_recalc_string(List *keys)
+{
+	StringInfoData qry;
+	ListCell	*lc;
+
+	initStringInfo(&qry);
+
+	appendStringInfo(&qry, "SELECT tid");
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		appendStringInfo(&qry, ", %s", NameStr(attr->attname));
+	}
+
+	appendStringInfo(&qry, " FROM updt WHERE recalc");
+
+	return qry.data;
+}
+
+/*
+ * recalc_and_set_values
+ *
+ * Recalculate tuples in a materialized from base tables and update these.
+ * The tuples which needs recalculation are specified by keys, and resnames
+ * of columns to be updated are specified by namelist. TIDs and key values
+ * are given by tuples in tuptable_recalc. Its first attribute must be TID
+ * and key values must be following this.
+ */
+static void
+recalc_and_set_values(SPITupleTable *tuptable_recalc, int64 num_tuples,
+					  List *namelist, List *keys, Relation matviewRel)
+{
+	TupleDesc   tupdesc_recalc = tuptable_recalc->tupdesc;
+	Oid		   *keyTypes = NULL, *types = NULL;
+	char	   *keyNulls = NULL, *nulls = NULL;
+	Datum	   *keyVals = NULL, *vals = NULL;
+	int			num_vals = list_length(namelist);
+	int			num_keys = list_length(keys);
+	uint64      i;
+	Oid			matviewOid;
+	char	   *matviewname;
+
+	matviewOid = RelationGetRelid(matviewRel);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	/* If we have keys, initialize arrays for them. */
+	if (keys)
+	{
+		keyTypes = palloc(sizeof(Oid) * num_keys);
+		keyNulls = palloc(sizeof(char) * num_keys);
+		keyVals = palloc(sizeof(Datum) * num_keys);
+		/* a tuple contains keys to be recalculated and ctid to be updated*/
+		Assert(tupdesc_recalc->natts == num_keys + 1);
+
+		/* Types of key attributes  */
+		for (i = 0; i < num_keys; i++)
+			keyTypes[i] = TupleDescAttr(tupdesc_recalc, i + 1)->atttypid;
+	}
+
+	/* allocate memory for all attribute names and tid */
+	types = palloc(sizeof(Oid) * (num_vals + 1));
+	nulls = palloc(sizeof(char) * (num_vals + 1));
+	vals = palloc(sizeof(Datum) * (num_vals + 1));
+
+	/* For each tuple which needs recalculation */
+	for (i = 0; i < num_tuples; i++)
+	{
+		int j;
+		bool isnull;
+		SPIPlanPtr plan;
+		SPITupleTable *tuptable_newvals;
+		TupleDesc   tupdesc_newvals;
+
+		/* Set group key values as parameters if needed. */
+		if (keys)
+		{
+			for (j = 0; j < num_keys; j++)
+			{
+				keyVals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, j + 2, &isnull);
+				if (isnull)
+					keyNulls[j] = 'n';
+				else
+					keyNulls[j] = ' ';
+			}
+		}
+
+		/*
+		 * Get recalculated values from base tables. The result must be
+		 * only one tuple thich contains the new values for specified keys.
+		 */
+		plan = get_plan_for_recalc(matviewOid, namelist, keys, keyTypes);
+		if (SPI_execute_plan(plan, keyVals, keyNulls, false, 0) != SPI_OK_SELECT)
+			elog(ERROR, "SPI_execute_plan");
+		if (SPI_processed != 1)
+			elog(ERROR, "SPI_execute_plan returned zero or more than one rows");
+
+		tuptable_newvals = SPI_tuptable;
+		tupdesc_newvals = tuptable_newvals->tupdesc;
+
+		Assert(tupdesc_newvals->natts == num_vals);
+
+		/* Set the new values as parameters */
+		for (j = 0; j < tupdesc_newvals->natts; j++)
+		{
+			if (i == 0)
+				types[j] = TupleDescAttr(tupdesc_newvals, j)->atttypid;
+
+			vals[j] = SPI_getbinval(tuptable_newvals->vals[0], tupdesc_newvals, j + 1, &isnull);
+			if (isnull)
+				nulls[j] = 'n';
+			else
+				nulls[j] = ' ';
+		}
+		/* Set TID of the view tuple to be updated as a parameter */
+		types[j] = TIDOID;
+		vals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, 1, &isnull);
+		nulls[j] = ' ';
+
+		/* Update the view tuple to the new values */
+		plan = get_plan_for_set_values(matviewOid, matviewname, namelist, types);
+		if (SPI_execute_plan(plan, vals, nulls, false, 0) != SPI_OK_UPDATE)
+			elog(ERROR, "SPI_execute_plan");
+	}
+}
+
+
+/*
+ * get_plan_for_recalc
+ *
+ * Create or fetch a plan for recalculating value in the view's target list
+ * from base tables using the definition query of materialized view specified
+ * by matviewOid. namelist is a list of resnames of values to be recalculated.
+ *
+ * keys is a list of keys to identify tuples to be recalculated if this is not
+ * empty. KeyTypes is an array of types of keys.
+ */
+static SPIPlanPtr
+get_plan_for_recalc(Oid matviewOid, List *namelist, List *keys, Oid *keyTypes)
+{
+	MV_QueryKey hash_key;
+	SPIPlanPtr	plan;
+
+	/* Fetch or prepare a saved plan for the recalculation */
+	mv_BuildQueryKey(&hash_key, matviewOid, MV_PLAN_RECALC);
+	if ((plan = mv_FetchPreparedPlan(&hash_key)) == NULL)
+	{
+		ListCell	   *lc;
+		StringInfoData	str;
+		char   *viewdef;
+
+		/* get view definition of matview */
+		viewdef = text_to_cstring((text *) DatumGetPointer(
+					DirectFunctionCall1(pg_get_viewdef, ObjectIdGetDatum(matviewOid))));
+		/* get rid of trailing semi-colon */
+		viewdef[strlen(viewdef)-1] = '\0';
+
+		/*
+		 * Build a query string for recalculating values. This is like
+		 *
+		 *  SELECT x1, x2, x3, ... FROM ( ... view definition query ...) mv
+		 *   WHERE (key1, key2, ...) = ($1, $2, ...);
+		 */
+
+		initStringInfo(&str);
+		appendStringInfo(&str, "SELECT ");
+		foreach (lc, namelist)
+		{
+			appendStringInfo(&str, "%s", (char *) lfirst(lc));
+			if (lnext(namelist, lc))
+				appendStringInfoString(&str, ", ");
+		}
+		appendStringInfo(&str, " FROM (%s) mv", viewdef);
+
+		if (keys)
+		{
+			int		i = 1;
+			char	paramname[16];
+
+			appendStringInfo(&str, " WHERE (");
+			foreach (lc, keys)
+			{
+				Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+				char   *resname = NameStr(attr->attname);
+				Oid		typid = attr->atttypid;
+
+				sprintf(paramname, "$%d", i);
+				appendStringInfo(&str, "(");
+				generate_equal(&str, typid, resname, paramname);
+				appendStringInfo(&str, " OR (%s IS NULL AND %s IS NULL))",
+								 resname, paramname);
+
+				if (lnext(keys, lc))
+					appendStringInfoString(&str, " AND ");
+				i++;
+			}
+			appendStringInfo(&str, ")");
+		}
+		else
+			keyTypes = NULL;
+
+		plan = SPI_prepare(str.data, list_length(keys), keyTypes);
+		if (plan == NULL)
+			elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), str.data);
+
+		SPI_keepplan(plan);
+		mv_HashPreparedPlan(&hash_key, plan);
+	}
+
+	return plan;
+}
+
+/*
+ * get_plan_for_set_values
+ *
+ * Create or fetch a plan for applying new values calculated by
+ * get_plan_for_recalc to a materialized view specified by matviewOid.
+ * matviewname is the name of the view.  namelist is a list of resnames
+ * of attributes to be updated, and valTypes is an array of types of the
+ * values.
+ */
+static SPIPlanPtr
+get_plan_for_set_values(Oid matviewOid, char *matviewname, List *namelist,
+						Oid *valTypes)
+{
+	MV_QueryKey	key;
+	SPIPlanPtr	plan;
+
+	/* Fetch or prepare a saved plan for the real check */
+	mv_BuildQueryKey(&key, matviewOid, MV_PLAN_SET_VALUE);
+	if ((plan = mv_FetchPreparedPlan(&key)) == NULL)
+	{
+		ListCell	  *lc;
+		StringInfoData str;
+		int		i;
+
+		/*
+		 * Build a query string for applying min/max values. This is like
+		 *
+		 *  UPDATE matviewname AS mv
+		 *   SET (x1, x2, x3, x4) = ($1, $2, $3, $4)
+		 *   WHERE ctid = $5;
+		 */
+
+		initStringInfo(&str);
+		appendStringInfo(&str, "UPDATE %s AS mv SET (", matviewname);
+		foreach (lc, namelist)
+		{
+			appendStringInfo(&str, "%s", (char *) lfirst(lc));
+			if (lnext(namelist, lc))
+				appendStringInfoString(&str, ", ");
+		}
+		appendStringInfo(&str, ") = ROW(");
+
+		for (i = 1; i <= list_length(namelist); i++)
+			appendStringInfo(&str, "%s$%d", (i==1 ? "" : ", "), i);
+
+		appendStringInfo(&str, ") WHERE ctid OPERATOR(pg_catalog.=) $%d", i);
+
+		plan = SPI_prepare(str.data, list_length(namelist) + 1, valTypes);
+		if (plan == NULL)
+			elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), str.data);
+
+		SPI_keepplan(plan);
+		mv_HashPreparedPlan(&key, plan);
+	}
+
+	return plan;
+}
+
 /*
  * generate_equals
  *
@@ -2342,6 +3219,13 @@ mv_InitHashTables(void)
 {
 	HASHCTL		ctl;
 
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(MV_QueryKey);
+	ctl.entrysize = sizeof(MV_QueryHashEntry);
+	mv_query_cache = hash_create("MV query cache",
+								 MV_INIT_QUERYHASHSIZE,
+								 &ctl, HASH_ELEM | HASH_BLOBS);
+
 	memset(&ctl, 0, sizeof(ctl));
 	ctl.keysize = sizeof(Oid);
 	ctl.entrysize = sizeof(MV_TriggerHashEntry);
@@ -2350,6 +3234,99 @@ mv_InitHashTables(void)
 								 &ctl, HASH_ELEM | HASH_BLOBS);
 }
 
+/*
+ * mv_FetchPreparedPlan
+ */
+static SPIPlanPtr
+mv_FetchPreparedPlan(MV_QueryKey *key)
+{
+	MV_QueryHashEntry *entry;
+	SPIPlanPtr	plan;
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_query_cache)
+		mv_InitHashTables();
+
+	/*
+	 * Lookup for the key
+	 */
+	entry = (MV_QueryHashEntry *) hash_search(mv_query_cache,
+											  (void *) key,
+											  HASH_FIND, NULL);
+	if (entry == NULL)
+		return NULL;
+
+	/*
+	 * Check whether the plan is still valid.  If it isn't, we don't want to
+	 * simply rely on plancache.c to regenerate it; rather we should start
+	 * from scratch and rebuild the query text too.  This is to cover cases
+	 * such as table/column renames.  We depend on the plancache machinery to
+	 * detect possible invalidations, though.
+	 *
+	 * CAUTION: this check is only trustworthy if the caller has already
+	 * locked both materialized views and base tables.
+	 */
+	plan = entry->plan;
+	if (plan && SPI_plan_is_valid(plan))
+		return plan;
+
+	/*
+	 * Otherwise we might as well flush the cached plan now, to free a little
+	 * memory space before we make a new one.
+	 */
+	entry->plan = NULL;
+	if (plan)
+		SPI_freeplan(plan);
+
+	return NULL;
+}
+
+/*
+ * mv_HashPreparedPlan
+ *
+ * Add another plan to our private SPI query plan hashtable.
+ */
+static void
+mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan)
+{
+	MV_QueryHashEntry *entry;
+	bool		found;
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_query_cache)
+		mv_InitHashTables();
+
+	/*
+	 * Add the new plan.  We might be overwriting an entry previously found
+	 * invalid by mv_FetchPreparedPlan.
+	 */
+	entry = (MV_QueryHashEntry *) hash_search(mv_query_cache,
+											  (void *) key,
+											  HASH_ENTER, &found);
+	Assert(!found || entry->plan == NULL);
+	entry->plan = plan;
+}
+
+/*
+ * mv_BuildQueryKey
+ *
+ * Construct a hashtable key for a prepared SPI plan for IVM.
+ */
+static void
+mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type)
+{
+	/*
+	 * We assume struct MV_QueryKey contains no padding bytes, else we'd need
+	 * to use memset to clear them.
+	 */
+	key->matview_id = matview_id;
+	key->query_type = query_type;
+}
+
 /*
  * AtAbort_IVM
  *
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index c369b3ba5e..abcc31023c 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -30,6 +30,7 @@ extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_cr
 extern void CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create);
 
 extern Query *rewriteQueryForIMMV(Query *query, List *colNames);
+extern void makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber *next_resno, List **aggs);
 
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
 
-- 
2.17.1

v27-0008-Add-regression-tests-for-Incremental-View-Mainte.patchtext/x-diff; name=v27-0008-Add-regression-tests-for-Incremental-View-Mainte.patchDownload
From 7b6b26597727a8c97783721aabbc92afc941a946 Mon Sep 17 00:00:00 2001
From: Takuma Hoshiai <takuma.hoshiai@gmail.com>
Date: Wed, 10 Mar 2021 11:11:13 +0900
Subject: [PATCH v27 8/9] Add regression tests for Incremental View Maintenance

---
 .../regress/expected/incremental_matview.out  | 840 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/incremental_matview.sql  | 424 +++++++++
 3 files changed, 1265 insertions(+), 1 deletion(-)
 create mode 100644 src/test/regress/expected/incremental_matview.out
 create mode 100644 src/test/regress/sql/incremental_matview.sql

diff --git a/src/test/regress/expected/incremental_matview.out b/src/test/regress/expected/incremental_matview.out
new file mode 100644
index 0000000000..201af5f087
--- /dev/null
+++ b/src/test/regress/expected/incremental_matview.out
@@ -0,0 +1,840 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) WITH NO DATA;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ERROR:  materialized view "mv_ivm_1" has not been populated
+HINT:  Use the REFRESH MATERIALIZED VIEW command.
+REFRESH MATERIALIZED VIEW mv_ivm_1;
+NOTICE:  could not create an index on materialized view "mv_ivm_1" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- REFRESH WITH NO DATA
+BEGIN;
+CREATE FUNCTION dummy_ivm_trigger_func() RETURNS TRIGGER AS $$
+  BEGIN
+    RETURN NULL;
+  END
+$$ language plpgsql;
+CREATE CONSTRAINT TRIGGER dummy_ivm_trigger AFTER INSERT
+ON mv_base_a FROM mv_ivm_1 FOR EACH ROW
+EXECUTE PROCEDURE dummy_ivm_trigger_func();
+SELECT COUNT(*)
+FROM pg_depend pd INNER JOIN pg_trigger pt ON pd.objid = pt.oid
+WHERE pd.classid = 'pg_trigger'::regclass AND pd.refobjid = 'mv_ivm_1'::regclass;
+ count 
+-------
+    13
+(1 row)
+
+REFRESH MATERIALIZED VIEW mv_ivm_1 WITH NO DATA;
+SELECT COUNT(*)
+FROM pg_depend pd INNER JOIN pg_trigger pt ON pd.objid = pt.oid
+WHERE pd.classid = 'pg_trigger'::regclass AND pd.refobjid = 'mv_ivm_1'::regclass;
+ count 
+-------
+     1
+(1 row)
+
+ROLLBACK;
+-- immediate maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- rename of IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_rename AS SELECT DISTINCT * FROM mv_base_a;
+NOTICE:  created index "mv_ivm_rename_index" on materialized view "mv_ivm_rename"
+ALTER MATERIALIZED VIEW mv_ivm_rename RENAME COLUMN __ivm_count__ TO xxx;
+ERROR:  IVM column can not be renamed
+DROP MATERIALIZED VIEW mv_ivm_rename;
+-- unique index on IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_unique AS SELECT DISTINCT * FROM mv_base_a;
+NOTICE:  created index "mv_ivm_unique_index" on materialized view "mv_ivm_unique"
+CREATE UNIQUE INDEX ON mv_ivm_unique(__ivm_count__);
+ERROR:  unique index creation on IVM columns is not supported
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__));
+ERROR:  unique index creation on IVM columns is not supported
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__ + 1));
+ERROR:  unique index creation on IVM columns is not supported
+DROP MATERIALIZED VIEW mv_ivm_unique;
+-- some query syntax
+BEGIN;
+CREATE FUNCTION ivm_func() RETURNS int LANGUAGE 'sql'
+       AS 'SELECT 1' IMMUTABLE;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_func AS SELECT * FROM ivm_func();
+NOTICE:  could not create an index on materialized view "mv_ivm_func" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_no_tbl AS SELECT 1;
+NOTICE:  could not create an index on materialized view "mv_ivm_no_tbl" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+ROLLBACK;
+-- result of materialized view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_duplicate" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+NOTICE:  created index "mv_ivm_distinct_index" on materialized view "mv_ivm_distinct"
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 20
+ 30
+ 40
+ 50
+(6 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+ROLLBACK;
+-- support SUM(), COUNT() and AVG() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i), AVG(j) FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_agg_index" on materialized view "mv_ivm_agg"
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 | 120 |     2 | 60.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg          
+---+-----+-------+----------------------
+ 1 |  10 |     1 |  10.0000000000000000
+ 2 | 220 |     2 | 110.0000000000000000
+ 3 |  30 |     1 |  30.0000000000000000
+ 4 |  40 |     1 |  40.0000000000000000
+ 5 |  50 |     1 |  50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support COUNT(*) aggregate function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(*) FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_agg_index" on materialized view "mv_ivm_agg"
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 |  20 |     1
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 | 120 |     2
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+ROLLBACK;
+-- support aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j), COUNT(j), AVG(j) FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_group" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count |         avg         
+-----+-------+---------------------
+ 150 |     5 | 30.0000000000000000
+(1 row)
+
+INSERT INTO mv_base_a VALUES(6,60);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count |         avg         
+-----+-------+---------------------
+ 210 |     6 | 35.0000000000000000
+(1 row)
+
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count | avg 
+-----+-------+-----
+     |     0 |    
+(1 row)
+
+ROLLBACK;
+-- resolved issue: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+NOTICE:  created index "mv_ivm_avg_bug_index" on materialized view "mv_ivm_avg_bug"
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     3 |  3.3333333333333333
+ 2 |  80 |     3 | 26.6666666666666667
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support MIN(), MAX() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT i, MIN(j), MAX(j)  FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_min_max_index" on materialized view "mv_ivm_min_max"
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  10 |  10
+ 2 |  20 |  20
+ 3 |  30 |  30
+ 4 |  40 |  40
+ 5 |  50 |  50
+(5 rows)
+
+INSERT INTO mv_base_a VALUES
+  (1,11), (1,12),
+  (2,21), (2,22),
+  (3,31), (3,32),
+  (4,41), (4,42),
+  (5,51), (5,52);
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  10 |  12
+ 2 |  20 |  22
+ 3 |  30 |  32
+ 4 |  40 |  42
+ 5 |  50 |  52
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  11 |  12
+ 2 |  20 |  22
+ 3 |  30 |  31
+ 4 |  40 |  42
+ 5 |  50 |  52
+(5 rows)
+
+ROLLBACK;
+-- support MIN(), MAX() aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT MIN(j), MAX(j)  FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_min_max" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+  10 |  50
+(1 row)
+
+INSERT INTO mv_base_a VALUES
+  (0,0), (6,60), (7,70);
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+   0 |  70
+(1 row)
+
+DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+  10 |  60
+(1 row)
+
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+     |    
+(1 row)
+
+ROLLBACK;
+-- support self join view and multiple change on the same table
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1, 10), (2, 20), (3, 30);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_self(v1, v2) AS
+ SELECT t1.v, t2.v FROM base_t AS t1 JOIN base_t AS t2 ON t1.i = t2.i;
+NOTICE:  could not create an index on materialized view "mv_self" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_self ORDER BY v1;
+ v1 | v2 
+----+----
+ 10 | 10
+ 20 | 20
+ 30 | 30
+(3 rows)
+
+INSERT INTO base_t VALUES (4,40);
+DELETE FROM base_t WHERE i = 1;
+UPDATE base_t SET v = v*10 WHERE i=2;
+SELECT * FROM mv_self ORDER BY v1;
+ v1  | v2  
+-----+-----
+  30 |  30
+  40 |  40
+ 200 | 200
+(3 rows)
+
+WITH
+ ins_t1 AS (INSERT INTO base_t VALUES (5,50) RETURNING 1),
+ ins_t2 AS (INSERT INTO base_t VALUES (6,60) RETURNING 1),
+ upd_t AS (UPDATE base_t SET v = v + 100  RETURNING 1),
+ dlt_t AS (DELETE FROM base_t WHERE i IN (4,5)  RETURNING 1)
+SELECT NULL;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT * FROM mv_self ORDER BY v1;
+ v1  | v2  
+-----+-----
+  50 |  50
+  60 |  60
+ 130 | 130
+ 300 | 300
+(4 rows)
+
+ROLLBACK;
+-- support simultaneous table changes
+BEGIN;
+CREATE TABLE base_r (i int, v int);
+CREATE TABLE base_s (i int, v int);
+INSERT INTO base_r VALUES (1, 10), (2, 20), (3, 30);
+INSERT INTO base_s VALUES (1, 100), (2, 200), (3, 300);
+CREATE INCREMENTAL MATERIALIZED VIEW mv(v1, v2) AS
+ SELECT r.v, s.v FROM base_r AS r JOIN base_s AS s USING(i);
+NOTICE:  could not create an index on materialized view "mv" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY v1;
+ v1 | v2  
+----+-----
+ 10 | 100
+ 20 | 200
+ 30 | 300
+(3 rows)
+
+WITH
+ ins_r AS (INSERT INTO base_r VALUES (1,11) RETURNING 1),
+ ins_r2 AS (INSERT INTO base_r VALUES (3,33) RETURNING 1),
+ ins_s AS (INSERT INTO base_s VALUES (2,222) RETURNING 1),
+ upd_r AS (UPDATE base_r SET v = v + 1000 WHERE i = 2 RETURNING 1),
+ dlt_s AS (DELETE FROM base_s WHERE i = 3 RETURNING 1)
+SELECT NULL;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT * FROM mv ORDER BY v1;
+  v1  | v2  
+------+-----
+   10 | 100
+   11 | 100
+ 1020 | 200
+ 1020 | 222
+(4 rows)
+
+ROLLBACK;
+-- support foreign reference constraints
+BEGIN;
+CREATE TABLE ri1 (i int PRIMARY KEY);
+CREATE TABLE ri2 (i int PRIMARY KEY REFERENCES ri1(i) ON UPDATE CASCADE ON DELETE CASCADE, v int);
+INSERT INTO ri1 VALUES (1),(2),(3);
+INSERT INTO ri2 VALUES (1),(2),(3);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ri(i1, i2) AS
+ SELECT ri1.i, ri2.i FROM ri1 JOIN ri2 USING(i);
+NOTICE:  created index "mv_ri_index" on materialized view "mv_ri"
+SELECT * FROM mv_ri ORDER BY i1;
+ i1 | i2 
+----+----
+  1 |  1
+  2 |  2
+  3 |  3
+(3 rows)
+
+UPDATE ri1 SET i=10 where i=1;
+DELETE FROM ri1 WHERE i=2;
+SELECT * FROM mv_ri ORDER BY i2;
+ i1 | i2 
+----+----
+  3 |  3
+ 10 | 10
+(2 rows)
+
+ROLLBACK;
+-- views including NULL
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1,10),(2, NULL);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+NOTICE:  could not create an index on materialized view "mv" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY i;
+ i | v  
+---+----
+ 1 | 10
+ 2 |   
+(2 rows)
+
+UPDATE base_t SET v = 20 WHERE i = 2;
+SELECT * FROM mv ORDER BY i;
+ i | v  
+---+----
+ 1 | 10
+ 2 | 20
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+NOTICE:  could not create an index on materialized view "mv" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY i;
+ i 
+---
+(0 rows)
+
+INSERT INTO base_t VALUES (1),(NULL);
+SELECT * FROM mv ORDER BY i;
+ i 
+---
+ 1
+  
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (1, 10), (1, 20);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, sum(v) FROM base_t GROUP BY i;
+NOTICE:  created index "mv_index" on materialized view "mv"
+SELECT * FROM mv ORDER BY i;
+ i | sum 
+---+-----
+ 1 |  30
+   |   3
+(2 rows)
+
+UPDATE base_t SET v = v * 10;
+SELECT * FROM mv ORDER BY i;
+ i | sum 
+---+-----
+ 1 | 300
+   |  30
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (NULL, 3), (NULL, 4), (NULL, 5);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, min(v), max(v) FROM base_t GROUP BY i;
+NOTICE:  created index "mv_index" on materialized view "mv"
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   1 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 1;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 3;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 5;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   4
+(1 row)
+
+ROLLBACK;
+-- IMMV containing user defined type
+BEGIN;
+CREATE TYPE mytype;
+CREATE FUNCTION mytype_in(cstring)
+ RETURNS mytype AS 'int4in'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+NOTICE:  return type mytype is only a shell
+CREATE FUNCTION mytype_out(mytype)
+ RETURNS cstring AS 'int4out'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+NOTICE:  argument type mytype is only a shell
+CREATE TYPE mytype (
+ LIKE = int4,
+ INPUT = mytype_in,
+ OUTPUT = mytype_out
+);
+CREATE FUNCTION mytype_eq(mytype, mytype)
+ RETURNS bool AS 'int4eq'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_lt(mytype, mytype)
+ RETURNS bool AS 'int4lt'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_cmp(mytype, mytype)
+ RETURNS integer AS 'btint4cmp'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE OPERATOR = (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_eq);
+CREATE OPERATOR < (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_lt);
+CREATE OPERATOR CLASS mytype_ops
+ DEFAULT FOR TYPE mytype USING btree AS
+ OPERATOR        1       <,
+ OPERATOR        3       = ,
+ FUNCTION		1		mytype_cmp(mytype,mytype);
+CREATE TABLE t_mytype (x mytype);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_mytype AS
+ SELECT * FROM t_mytype;
+NOTICE:  could not create an index on materialized view "mv_mytype" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+INSERT INTO t_mytype VALUES ('1'::mytype);
+SELECT * FROM mv_mytype;
+ x 
+---
+ 1
+(1 row)
+
+ROLLBACK;
+-- outer join is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i;
+ERROR:  OUTER JOIN is not supported on incrementally maintainable materialized view
+-- CTE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS
+    WITH b AS ( SELECT * FROM mv_base_b) SELECT a.i,a.j FROM mv_base_a a, b WHERE a.i = b.i;
+ERROR:  CTE is not supported on incrementally maintainable materialized view
+-- contain system column
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm01 AS SELECT i,j,xmin FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT i,j,xmin::text AS x_min FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm06 AS SELECT i,j,xidsend(xmin) AS x_min FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+-- contain subquery
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm03 AS SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT * FROM mv_base_b) b WHERE a.i = b.i;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm05 AS SELECT i,j, (SELECT k FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+-- contain ORDER BY
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm07 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) ORDER BY i,j,k;
+ERROR:  ORDER BY clause is not supported on incrementally maintainable materialized view
+-- contain HAVING
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm08 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) GROUP BY i,j,k HAVING SUM(i) > 5;
+ERROR:   HAVING clause is not supported on incrementally maintainable materialized view
+-- contain view or materialized view
+CREATE VIEW b_view AS SELECT i,k FROM mv_base_b;
+CREATE MATERIALIZED VIEW b_mview AS SELECT i,k FROM mv_base_b;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm07 AS SELECT a.i,a.j FROM mv_base_a a,b_view b WHERE a.i = b.i;
+ERROR:  VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm08 AS SELECT a.i,a.j FROM mv_base_a a,b_mview b WHERE a.i = b.i;
+ERROR:  VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm09 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm10 AS SELECT a.i,a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) OR a.i > 5;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+-- contain mutable functions
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+ERROR:  mutable function is not supported on incrementally maintainable materialized view
+HINT:  functions must be marked IMMUTABLE
+-- LIMIT/OFFSET is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm13 AS SELECT i,j FROM mv_base_a LIMIT 10 OFFSET 5;
+ERROR:  LIMIT/OFFSET clause is not supported on incrementally maintainable materialized view
+-- DISTINCT ON is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm14 AS SELECT DISTINCT ON(i) i, j FROM mv_base_a;
+ERROR:  DISTINCT ON is not supported on incrementally maintainable materialized view
+-- TABLESAMPLE clause is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm15 AS SELECT i, j FROM mv_base_a TABLESAMPLE SYSTEM(50);
+ERROR:  TABLESAMPLE clause is not supported on incrementally maintainable materialized view
+-- window functions are not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm16 AS SELECT *, cume_dist() OVER (ORDER BY i) AS rank FROM mv_base_a;
+ERROR:  window functions are not supported on incrementally maintainable materialized view
+-- aggregate function with some options is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm17 AS SELECT COUNT(*) FILTER(WHERE i < 3) FROM mv_base_a;
+ERROR:  aggregate function with FILTER clause is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm18 AS SELECT COUNT(DISTINCT i)  FROM mv_base_a;
+ERROR:  aggregate function with DISTINCT arguments is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm19 AS SELECT array_agg(j ORDER BY i DESC) FROM mv_base_a;
+ERROR:  aggregate function with ORDER clause is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm20 AS SELECT i,SUM(j) FROM mv_base_a GROUP BY GROUPING SETS((i),());
+ERROR:  GROUPING SETS, ROLLUP, or CUBE clauses is not supported on incrementally maintainable materialized view
+-- inheritance parent is not supported
+BEGIN;
+CREATE TABLE parent (i int, v int);
+CREATE TABLE child_a(options text) INHERITS(parent);
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm21 AS SELECT * FROM parent;
+ERROR:  inheritance parent is not supported on incrementally maintainable materialized view
+ROLLBACK;
+-- UNION statement is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm22 AS SELECT i,j FROM mv_base_a UNION ALL SELECT i,k FROM mv_base_b;;
+ERROR:  UNION/INTERSECT/EXCEPT statements are not supported on incrementally maintainable materialized view
+-- empty target list is not allowed with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm25 AS SELECT FROM mv_base_a;
+ERROR:  empty target list is not supported on incrementally maintainable materialized view
+-- FOR UPDATE/SHARE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm26 AS SELECT i,j FROM mv_base_a FOR UPDATE;
+ERROR:  FOR UPDATE/SHARE clause is not supported on incrementally maintainable materialized view
+-- tartget list cannot contain ivm column that start with '__ivm'
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm28 AS SELECT i AS "__ivm_count__" FROM mv_base_a;
+ERROR:  column name __ivm_count__ is not supported on incrementally maintainable materialized view
+-- expressions specified in GROUP BY must appear in the target list.
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm29 AS SELECT COUNT(i) FROM mv_base_a GROUP BY i;
+ERROR:  GROUP BY expression not appearing in select list is not supported on incrementally maintainable materialized view
+-- experssions containing an aggregate is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm30 AS SELECT sum(i)*0.5 FROM mv_base_a;
+ERROR:  expression containing an aggregate in it is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm31 AS SELECT sum(i)/sum(j) FROM mv_base_a;
+ERROR:  expression containing an aggregate in it is not supported on incrementally maintainable materialized view
+-- VALUES is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_only_values1 AS values(1);
+ERROR:  VALUES is not supported on incrementally maintainable materialized view
+-- base table which has row level security
+DROP USER IF EXISTS ivm_admin;
+NOTICE:  role "ivm_admin" does not exist, skipping
+DROP USER IF EXISTS ivm_user;
+NOTICE:  role "ivm_user" does not exist, skipping
+CREATE USER ivm_admin;
+CREATE USER ivm_user;
+SET SESSION AUTHORIZATION ivm_admin;
+CREATE TABLE rls_tbl(id int, data text, owner name);
+INSERT INTO rls_tbl VALUES
+  (1,'foo','ivm_user'),
+  (2,'bar','postgres');
+CREATE TABLE num_tbl(id int, num text);
+INSERT INTO num_tbl VALUES
+  (1,'one'),
+  (2,'two'),
+  (3,'three'),
+  (4,'four');
+CREATE POLICY rls_tbl_policy ON rls_tbl FOR SELECT TO PUBLIC USING(owner = current_user);
+CREATE POLICY rls_tbl_policy2 ON rls_tbl FOR INSERT TO PUBLIC WITH CHECK(current_user LIKE 'ivm_%');
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+GRANT ALL on rls_tbl TO PUBLIC;
+GRANT ALL on num_tbl TO PUBLIC;
+SET SESSION AUTHORIZATION ivm_user;
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls AS SELECT * FROM rls_tbl;
+NOTICE:  could not create an index on materialized view "ivm_rls" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+ id | data |  owner   
+----+------+----------
+  1 | foo  | ivm_user
+(1 row)
+
+INSERT INTO rls_tbl VALUES
+  (3,'baz','ivm_user'),
+  (4,'qux','postgres');
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+ id | data |  owner   
+----+------+----------
+  1 | foo  | ivm_user
+  3 | baz  | ivm_user
+(2 rows)
+
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls2 AS SELECT * FROM rls_tbl JOIN num_tbl USING(id);
+NOTICE:  could not create an index on materialized view "ivm_rls2" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+RESET SESSION AUTHORIZATION;
+WITH
+ x AS (UPDATE rls_tbl SET data = data || '_2' where id in (3,4)),
+ y AS (UPDATE num_tbl SET num = num || '_2' where id in (3,4))
+SELECT;
+--
+(1 row)
+
+SELECT * FROM ivm_rls2 ORDER BY 1,2,3;
+ id | data  |  owner   |   num   
+----+-------+----------+---------
+  1 | foo   | ivm_user | one
+  3 | baz_2 | ivm_user | three_2
+(2 rows)
+
+DROP TABLE rls_tbl CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to materialized view ivm_rls
+drop cascades to materialized view ivm_rls2
+DROP TABLE num_tbl CASCADE;
+DROP USER ivm_user;
+DROP USER ivm_admin;
+DROP TABLE mv_base_b CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to materialized view mv_ivm_1
+drop cascades to view b_view
+drop cascades to materialized view b_mview
+DROP TABLE mv_base_a CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 103e11483d..3027f4e78c 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -86,7 +86,7 @@ test: brin_bloom brin_multi
 # psql depends on create_am
 # amutils depends on geometry, create_index_spgist, hash_index, brin
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort create_role
+test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort create_role incremental_matview
 
 # collate.*.utf8 tests cannot be run in parallel with each other
 test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8
diff --git a/src/test/regress/sql/incremental_matview.sql b/src/test/regress/sql/incremental_matview.sql
new file mode 100644
index 0000000000..a4ce2c741e
--- /dev/null
+++ b/src/test/regress/sql/incremental_matview.sql
@@ -0,0 +1,424 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) WITH NO DATA;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+REFRESH MATERIALIZED VIEW mv_ivm_1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+
+-- REFRESH WITH NO DATA
+BEGIN;
+CREATE FUNCTION dummy_ivm_trigger_func() RETURNS TRIGGER AS $$
+  BEGIN
+    RETURN NULL;
+  END
+$$ language plpgsql;
+
+CREATE CONSTRAINT TRIGGER dummy_ivm_trigger AFTER INSERT
+ON mv_base_a FROM mv_ivm_1 FOR EACH ROW
+EXECUTE PROCEDURE dummy_ivm_trigger_func();
+
+SELECT COUNT(*)
+FROM pg_depend pd INNER JOIN pg_trigger pt ON pd.objid = pt.oid
+WHERE pd.classid = 'pg_trigger'::regclass AND pd.refobjid = 'mv_ivm_1'::regclass;
+
+REFRESH MATERIALIZED VIEW mv_ivm_1 WITH NO DATA;
+
+SELECT COUNT(*)
+FROM pg_depend pd INNER JOIN pg_trigger pt ON pd.objid = pt.oid
+WHERE pd.classid = 'pg_trigger'::regclass AND pd.refobjid = 'mv_ivm_1'::regclass;
+ROLLBACK;
+
+-- immediate maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+
+-- rename of IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_rename AS SELECT DISTINCT * FROM mv_base_a;
+ALTER MATERIALIZED VIEW mv_ivm_rename RENAME COLUMN __ivm_count__ TO xxx;
+DROP MATERIALIZED VIEW mv_ivm_rename;
+
+-- unique index on IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_unique AS SELECT DISTINCT * FROM mv_base_a;
+CREATE UNIQUE INDEX ON mv_ivm_unique(__ivm_count__);
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__));
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__ + 1));
+DROP MATERIALIZED VIEW mv_ivm_unique;
+
+-- some query syntax
+BEGIN;
+CREATE FUNCTION ivm_func() RETURNS int LANGUAGE 'sql'
+       AS 'SELECT 1' IMMUTABLE;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_func AS SELECT * FROM ivm_func();
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_no_tbl AS SELECT 1;
+ROLLBACK;
+
+-- result of materialized view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ROLLBACK;
+
+-- support SUM(), COUNT() and AVG() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i), AVG(j) FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ROLLBACK;
+
+-- support COUNT(*) aggregate function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(*) FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j), COUNT(j), AVG(j) FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+INSERT INTO mv_base_a VALUES(6,60);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ROLLBACK;
+
+-- resolved issue: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support MIN(), MAX() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT i, MIN(j), MAX(j)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES
+  (1,11), (1,12),
+  (2,21), (2,22),
+  (3,31), (3,32),
+  (4,41), (4,42),
+  (5,51), (5,52);
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support MIN(), MAX() aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT MIN(j), MAX(j)  FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+INSERT INTO mv_base_a VALUES
+  (0,0), (6,60), (7,70);
+SELECT * FROM mv_ivm_min_max;
+DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
+SELECT * FROM mv_ivm_min_max;
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+ROLLBACK;
+
+-- support self join view and multiple change on the same table
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1, 10), (2, 20), (3, 30);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_self(v1, v2) AS
+ SELECT t1.v, t2.v FROM base_t AS t1 JOIN base_t AS t2 ON t1.i = t2.i;
+SELECT * FROM mv_self ORDER BY v1;
+INSERT INTO base_t VALUES (4,40);
+DELETE FROM base_t WHERE i = 1;
+UPDATE base_t SET v = v*10 WHERE i=2;
+SELECT * FROM mv_self ORDER BY v1;
+WITH
+ ins_t1 AS (INSERT INTO base_t VALUES (5,50) RETURNING 1),
+ ins_t2 AS (INSERT INTO base_t VALUES (6,60) RETURNING 1),
+ upd_t AS (UPDATE base_t SET v = v + 100  RETURNING 1),
+ dlt_t AS (DELETE FROM base_t WHERE i IN (4,5)  RETURNING 1)
+SELECT NULL;
+SELECT * FROM mv_self ORDER BY v1;
+ROLLBACK;
+
+-- support simultaneous table changes
+BEGIN;
+CREATE TABLE base_r (i int, v int);
+CREATE TABLE base_s (i int, v int);
+INSERT INTO base_r VALUES (1, 10), (2, 20), (3, 30);
+INSERT INTO base_s VALUES (1, 100), (2, 200), (3, 300);
+CREATE INCREMENTAL MATERIALIZED VIEW mv(v1, v2) AS
+ SELECT r.v, s.v FROM base_r AS r JOIN base_s AS s USING(i);
+SELECT * FROM mv ORDER BY v1;
+WITH
+ ins_r AS (INSERT INTO base_r VALUES (1,11) RETURNING 1),
+ ins_r2 AS (INSERT INTO base_r VALUES (3,33) RETURNING 1),
+ ins_s AS (INSERT INTO base_s VALUES (2,222) RETURNING 1),
+ upd_r AS (UPDATE base_r SET v = v + 1000 WHERE i = 2 RETURNING 1),
+ dlt_s AS (DELETE FROM base_s WHERE i = 3 RETURNING 1)
+SELECT NULL;
+SELECT * FROM mv ORDER BY v1;
+ROLLBACK;
+
+-- support foreign reference constraints
+BEGIN;
+CREATE TABLE ri1 (i int PRIMARY KEY);
+CREATE TABLE ri2 (i int PRIMARY KEY REFERENCES ri1(i) ON UPDATE CASCADE ON DELETE CASCADE, v int);
+INSERT INTO ri1 VALUES (1),(2),(3);
+INSERT INTO ri2 VALUES (1),(2),(3);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ri(i1, i2) AS
+ SELECT ri1.i, ri2.i FROM ri1 JOIN ri2 USING(i);
+SELECT * FROM mv_ri ORDER BY i1;
+UPDATE ri1 SET i=10 where i=1;
+DELETE FROM ri1 WHERE i=2;
+SELECT * FROM mv_ri ORDER BY i2;
+ROLLBACK;
+
+-- views including NULL
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1,10),(2, NULL);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+SELECT * FROM mv ORDER BY i;
+UPDATE base_t SET v = 20 WHERE i = 2;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+SELECT * FROM mv ORDER BY i;
+INSERT INTO base_t VALUES (1),(NULL);
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (1, 10), (1, 20);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, sum(v) FROM base_t GROUP BY i;
+SELECT * FROM mv ORDER BY i;
+UPDATE base_t SET v = v * 10;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (NULL, 3), (NULL, 4), (NULL, 5);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, min(v), max(v) FROM base_t GROUP BY i;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 1;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 3;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 5;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+-- IMMV containing user defined type
+BEGIN;
+
+CREATE TYPE mytype;
+CREATE FUNCTION mytype_in(cstring)
+ RETURNS mytype AS 'int4in'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_out(mytype)
+ RETURNS cstring AS 'int4out'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE TYPE mytype (
+ LIKE = int4,
+ INPUT = mytype_in,
+ OUTPUT = mytype_out
+);
+
+CREATE FUNCTION mytype_eq(mytype, mytype)
+ RETURNS bool AS 'int4eq'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_lt(mytype, mytype)
+ RETURNS bool AS 'int4lt'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_cmp(mytype, mytype)
+ RETURNS integer AS 'btint4cmp'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+
+CREATE OPERATOR = (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_eq);
+CREATE OPERATOR < (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_lt);
+
+CREATE OPERATOR CLASS mytype_ops
+ DEFAULT FOR TYPE mytype USING btree AS
+ OPERATOR        1       <,
+ OPERATOR        3       = ,
+ FUNCTION		1		mytype_cmp(mytype,mytype);
+
+CREATE TABLE t_mytype (x mytype);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_mytype AS
+ SELECT * FROM t_mytype;
+INSERT INTO t_mytype VALUES ('1'::mytype);
+SELECT * FROM mv_mytype;
+
+ROLLBACK;
+
+-- outer join is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i;
+-- CTE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS
+    WITH b AS ( SELECT * FROM mv_base_b) SELECT a.i,a.j FROM mv_base_a a, b WHERE a.i = b.i;
+-- contain system column
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm01 AS SELECT i,j,xmin FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT i,j,xmin::text AS x_min FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm06 AS SELECT i,j,xidsend(xmin) AS x_min FROM mv_base_a;
+-- contain subquery
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm03 AS SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT * FROM mv_base_b) b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm05 AS SELECT i,j, (SELECT k FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+-- contain ORDER BY
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm07 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) ORDER BY i,j,k;
+-- contain HAVING
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm08 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) GROUP BY i,j,k HAVING SUM(i) > 5;
+
+-- contain view or materialized view
+CREATE VIEW b_view AS SELECT i,k FROM mv_base_b;
+CREATE MATERIALIZED VIEW b_mview AS SELECT i,k FROM mv_base_b;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm07 AS SELECT a.i,a.j FROM mv_base_a a,b_view b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm08 AS SELECT a.i,a.j FROM mv_base_a a,b_mview b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm09 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm10 AS SELECT a.i,a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) OR a.i > 5;
+
+-- contain mutable functions
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+
+-- LIMIT/OFFSET is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm13 AS SELECT i,j FROM mv_base_a LIMIT 10 OFFSET 5;
+
+-- DISTINCT ON is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm14 AS SELECT DISTINCT ON(i) i, j FROM mv_base_a;
+
+-- TABLESAMPLE clause is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm15 AS SELECT i, j FROM mv_base_a TABLESAMPLE SYSTEM(50);
+
+-- window functions are not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm16 AS SELECT *, cume_dist() OVER (ORDER BY i) AS rank FROM mv_base_a;
+
+-- aggregate function with some options is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm17 AS SELECT COUNT(*) FILTER(WHERE i < 3) FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm18 AS SELECT COUNT(DISTINCT i)  FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm19 AS SELECT array_agg(j ORDER BY i DESC) FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm20 AS SELECT i,SUM(j) FROM mv_base_a GROUP BY GROUPING SETS((i),());
+
+-- inheritance parent is not supported
+BEGIN;
+CREATE TABLE parent (i int, v int);
+CREATE TABLE child_a(options text) INHERITS(parent);
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm21 AS SELECT * FROM parent;
+ROLLBACK;
+
+-- UNION statement is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm22 AS SELECT i,j FROM mv_base_a UNION ALL SELECT i,k FROM mv_base_b;;
+
+-- empty target list is not allowed with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm25 AS SELECT FROM mv_base_a;
+
+-- FOR UPDATE/SHARE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm26 AS SELECT i,j FROM mv_base_a FOR UPDATE;
+
+-- tartget list cannot contain ivm column that start with '__ivm'
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm28 AS SELECT i AS "__ivm_count__" FROM mv_base_a;
+
+-- expressions specified in GROUP BY must appear in the target list.
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm29 AS SELECT COUNT(i) FROM mv_base_a GROUP BY i;
+
+-- experssions containing an aggregate is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm30 AS SELECT sum(i)*0.5 FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm31 AS SELECT sum(i)/sum(j) FROM mv_base_a;
+
+-- VALUES is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_only_values1 AS values(1);
+
+-- base table which has row level security
+DROP USER IF EXISTS ivm_admin;
+DROP USER IF EXISTS ivm_user;
+CREATE USER ivm_admin;
+CREATE USER ivm_user;
+SET SESSION AUTHORIZATION ivm_admin;
+
+CREATE TABLE rls_tbl(id int, data text, owner name);
+INSERT INTO rls_tbl VALUES
+  (1,'foo','ivm_user'),
+  (2,'bar','postgres');
+CREATE TABLE num_tbl(id int, num text);
+INSERT INTO num_tbl VALUES
+  (1,'one'),
+  (2,'two'),
+  (3,'three'),
+  (4,'four');
+CREATE POLICY rls_tbl_policy ON rls_tbl FOR SELECT TO PUBLIC USING(owner = current_user);
+CREATE POLICY rls_tbl_policy2 ON rls_tbl FOR INSERT TO PUBLIC WITH CHECK(current_user LIKE 'ivm_%');
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+GRANT ALL on rls_tbl TO PUBLIC;
+GRANT ALL on num_tbl TO PUBLIC;
+
+SET SESSION AUTHORIZATION ivm_user;
+
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls AS SELECT * FROM rls_tbl;
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+INSERT INTO rls_tbl VALUES
+  (3,'baz','ivm_user'),
+  (4,'qux','postgres');
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls2 AS SELECT * FROM rls_tbl JOIN num_tbl USING(id);
+
+RESET SESSION AUTHORIZATION;
+
+WITH
+ x AS (UPDATE rls_tbl SET data = data || '_2' where id in (3,4)),
+ y AS (UPDATE num_tbl SET num = num || '_2' where id in (3,4))
+SELECT;
+SELECT * FROM ivm_rls2 ORDER BY 1,2,3;
+
+DROP TABLE rls_tbl CASCADE;
+DROP TABLE num_tbl CASCADE;
+DROP USER ivm_user;
+DROP USER ivm_admin;
+
+DROP TABLE mv_base_b CASCADE;
+DROP TABLE mv_base_a CASCADE;
-- 
2.17.1

v27-0009-Add-documentations-about-Incremental-View-Mainte.patchtext/x-diff; name=v27-0009-Add-documentations-about-Incremental-View-Mainte.patchDownload
From 25dcb137648c51e98471beada93788e95a869323 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:25:34 +0900
Subject: [PATCH v27 9/9] Add documentations about Incremental View Maintenance

---
 doc/src/sgml/catalogs.sgml                    |  24 +-
 .../sgml/ref/create_materialized_view.sgml    | 131 +++++-
 .../sgml/ref/refresh_materialized_view.sgml   |   8 +-
 doc/src/sgml/rules.sgml                       | 443 ++++++++++++++++++
 4 files changed, 601 insertions(+), 5 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index a533a2153e..2561faa163 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2193,6 +2193,15 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>relisivm</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if materialized view enables incremental view maintenance
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>relrewrite</structfield> <type>oid</type>
@@ -3508,6 +3517,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para>
      </listitem>
     </varlistentry>
+
+    <varlistentry>
+     <term><symbol>DEPENDENCY_IMMV</symbol> (<literal>m</literal>)</term>
+     <listitem>
+      <para>
+       The dependent object was created as part of creation of the Materialized
+       View with Incremental View Maintenance reference, and is really just a 
+       part of its internal implementation. The dependent object must not be
+       dropped unless the materialized view is also dropped.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
 
    Other dependency flavors might be needed in future.
@@ -3891,7 +3912,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <structfield>extrelocatable</structfield> <type>bool</type>
       </para>
       <para>
-       True if extension can be relocated to another schema
+      True for materialized views which are enabled for incremental
+      view maintenance (IVM).
       </para></entry>
      </row>
 
diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml
index 0d2fea2b97..52a4d206b1 100644
--- a/doc/src/sgml/ref/create_materialized_view.sgml
+++ b/doc/src/sgml/ref/create_materialized_view.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
+CREATE [ INCREMENTAL ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
     [ (<replaceable>column_name</replaceable> [, ...] ) ]
     [ USING <replaceable class="parameter">method</replaceable> ]
     [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
@@ -60,6 +60,132 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
   <title>Parameters</title>
 
   <variablelist>
+   <varlistentry>
+    <term><literal>INCREMENTAL</literal></term>
+    <listitem>
+     <para>
+      If specified, some triggers are automatically created so that the rows
+      of the materialized view are immediately updated when base tables of the
+      materialized view are updated. In general, this allows faster update of
+      the materialized view at a price of slower update of the base tables
+      because the triggers will be invoked. We call this form of materialized
+      view as "Incrementally Maintainable Materialized View" (IMMV).
+     </para>
+     <para>
+      When <acronym>IMMV</acronym> is defined without using <command>WITH NO DATA</command>,
+      a unique index is created on the view automatically if possible.  If the view
+      definition query has a GROUP BY clause, a unique index is created on the columns
+      of GROUP BY expressions.  Also, if the view has DISTINCT clause, a unique index
+      is created on all columns in the target list.  Otherwise, if the view contains all
+      primary key attritubes of its base tables in the target list, a unique index is
+      created on these attritubes.  In other cases, no index is created.
+     </para>
+     <para>
+      There are restrictions of query definitions allowed to use this
+      option. The following are supported in query definitions for IMMV:
+      <itemizedlist>
+
+       <listitem>
+        <para>
+         Inner joins (including self-joins).
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Some built-in aggregate functions (count, sum, avg, min, max) without a HAVING
+         clause. 
+        </para>
+        </listitem>
+      </itemizedlist>
+
+      Unsupported queries with this option include the following:
+
+      <itemizedlist>
+       <listitem>
+        <para>
+         Outer joins.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Sub-queries.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Aggregate functions other than built-in count, sum, avg, min and max.
+        </para>
+       </listitem>
+       <listitem>
+        <para>
+         Aggregate functions with a HAVING clause.
+        </para>
+       </listitem>
+       <listitem>
+        <para>
+         DISTINCT ON, WINDOW, VALUES, LIMIT and OFFSET clause.
+        </para>
+       </listitem>
+      </itemizedlist>
+
+      Other restrictions include:
+      <itemizedlist>
+
+       <listitem>
+        <para>
+         IMMVs must be based on simple base tables. It's not supported to
+         create them on top of views or materialized views.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         When the TRUNCATE command is executed on a base table,
+         no changes are made to the materialized view.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         It is not supported to include system columns in an IMMV.
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+ERROR:  system column is not supported with IVM
+         </programlisting>
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Non-immutable functions are not supported.
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+ERROR:  functions in IMMV must be marked IMMUTABLE
+         </programlisting>
+        </para>
+        </listitem>
+
+       <listitem>
+        <para>
+         IMMVs do not support expressions that contains aggregates
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Logical replication does not support IMMVs.
+        </para>
+       </listitem>
+
+      </itemizedlist>
+
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>IF NOT EXISTS</literal></term>
     <listitem>
@@ -155,7 +281,8 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
       This clause specifies whether or not the materialized view should be
       populated at creation time.  If not, the materialized view will be
       flagged as unscannable and cannot be queried until <command>REFRESH
-      MATERIALIZED VIEW</command> is used.
+      MATERIALIZED VIEW</command> is used.  Also, if the view is IMMV,
+      triggers for maintaining the view are not created.
      </para>
     </listitem>
    </varlistentry>
diff --git a/doc/src/sgml/ref/refresh_materialized_view.sgml b/doc/src/sgml/ref/refresh_materialized_view.sgml
index 675d6090f3..c29cfc19b6 100644
--- a/doc/src/sgml/ref/refresh_materialized_view.sgml
+++ b/doc/src/sgml/ref/refresh_materialized_view.sgml
@@ -35,9 +35,13 @@ REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] <replaceable class="parameter">name</
    owner of the materialized view.  The old contents are discarded.  If
    <literal>WITH DATA</literal> is specified (or defaults) the backing query
    is executed to provide the new data, and the materialized view is left in a
-   scannable state.  If <literal>WITH NO DATA</literal> is specified no new
+   scannable state.  If the view is an incrementally maintainable materialized
+   view (IMMV) and was unpopulated, triggers for maintaining the view are
+   created. Also, a unique index is created for IMMV if it is possible and the
+   view doesn't have that yet.
+   If <literal>WITH NO DATA</literal> is specified no new
    data is generated and the materialized view is left in an unscannable
-   state.
+   state.  If the view is IMMV, the triggers are dropped.
   </para>
   <para>
    <literal>CONCURRENTLY</literal> and <literal>WITH NO DATA</literal> may not
diff --git a/doc/src/sgml/rules.sgml b/doc/src/sgml/rules.sgml
index 4b2ba5a4e6..71e10ef5a1 100644
--- a/doc/src/sgml/rules.sgml
+++ b/doc/src/sgml/rules.sgml
@@ -1090,6 +1090,449 @@ SELECT word FROM words ORDER BY word &lt;-&gt; 'caterpiler' LIMIT 10;
 
 </sect1>
 
+<sect1 id="rules-ivm">
+<title>Incremental View Maintenance</title>
+
+<indexterm zone="rules-ivm">
+ <primary>incremental view maintenance</primary>
+</indexterm>
+
+<indexterm zone="rules-ivm">
+ <primary>materialized view</primary>
+ <secondary>incremental view maintenance</secondary>
+</indexterm>
+
+<indexterm zone="rules-ivm">
+ <primary>view</primary>
+ <secondary>incremental view maintenance</secondary>
+</indexterm>
+
+<sect2 id="rules-ivm-overview">
+<title>Overview</title>
+
+<para>
+    Incremental View Maintenance (<acronym>IVM</acronym>) is a way to make
+    materialized views up-to-date in which only incremental changes are computed
+    and applied on views rather than recomputing the contents from scratch as
+    <command>REFRESH MATERIALIZED VIEW</command> does.  <acronym>IVM</acronym>
+    can update materialized views more efficiently than recomputation when only
+    small parts of the view are changed.
+</para>
+
+<para>
+    There are two approaches with regard to timing of view maintenance:
+    immediate and deferred.  In immediate maintenance, views are updated in the
+    same transaction that its base table is modified.  In deferred maintenance,
+    views are updated after the transaction is committed, for example, when the
+    view is accessed, as a response to user command like <command>REFRESH
+    MATERIALIZED VIEW</command>, or periodically in background, and so on.
+    <productname>PostgreSQL</productname> currently implements only a kind of
+    immediate maintenance, in which materialized views are updated immediately
+    in AFTER triggers when a base table is modified.
+</para>
+
+<para>
+    To create materialized views supporting <acronym>IVM</acronym>, use the
+    <command>CREATE INCREMENTAL MATERIALIZED VIEW</command>, for example:
+<programlisting>
+CREATE <emphasis>INCREMENTAL</emphasis> MATERIALIZED VIEW mymatview AS SELECT * FROM mytab;
+</programlisting>
+    When a materialized view is created with the <literal>INCREMENTAL</literal>
+    keyword, some triggers are automatically created so that the view's contents are
+    immediately updated when its base tables are modified. We call this form
+    of materialized view an Incrementally Maintainable Materialized View
+    (<acronym>IMMV</acronym>).
+<programlisting>
+postgres=# CREATE INCREMENTAL MATERIALIZED VIEW m AS SELECT * FROM t0;
+NOTICE:  could not create an index on materialized view "m" automatically
+HINT:  Create an index on the materialized view for effcient incremental maintenance.
+SELECT 3
+postgres=# SELECT * FROM m;
+ i
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+postgres=# INSERT INTO t0 VALUES (4);
+INSERT 0 1
+postgres=# SELECT * FROM m; -- automatically updated
+ i
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+</programlisting>
+</para>
+
+<para>
+    Some <acronym>IMMV</acronym>s have hidden columns which are added
+    automatically when a materialized view is created. Their name starts
+    with <literal>__ivm_</literal> and they contain information required
+    for maintaining the <acronym>IMMV</acronym>. Such columns are not visible
+    when the <acronym>IMMV</acronym> is accessed by <literal>SELECT *</literal>
+    but are visible if the column name is explicitly specified in the target
+    list. We can also see the hidden columns in <literal>\d</literal>
+    meta-commands of <command>psql</command> commands.
+</para>
+
+<para>
+    In general, <acronym>IMMV</acronym>s allow faster updates of materialized
+    views at the price of slower updates to their base tables. Updates of
+    <acronym>IMMV</acronym> is slower because triggers will be invoked and the
+    view is updated in triggers per modification statement.
+</para>
+
+<para>
+    For example, suppose a normal materialized view defined as below:
+
+<programlisting>
+test=# CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm AS
+        SELECT a.aid, b.bid, a.abalance, b.bbalance
+        FROM pgbench_accounts a JOIN pgbench_branches b USING(bid);
+SELECT 10000000
+
+</programlisting>
+
+    Updating a tuple in a base table of this materialized view is rapid but the
+   <command>REFRESH MATERIALIZED VIEW</command> command on this view takes a long time:
+
+<programlisting>
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 0.990 ms
+
+test=# REFRESH MATERIALIZED VIEW mv_normal ;
+REFRESH MATERIALIZED VIEW
+Time: 33533.952 ms (00:33.534)
+</programlisting>
+</para>
+
+<para>
+    On the other hand, after creating <acronym>IMMV</acronym> with the same view
+    definition as below:
+
+<programlisting>
+test=# CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm AS
+        SELECT a.aid, b.bid, a.abalance, b.bbalance
+        FROM pgbench_accounts a JOIN pgbench_branches b USING(bid);
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+NOTICE:  created index "mv_ivm_index" on materialized view "mv_ivm"
+</programlisting>
+
+    updating a tuple in a base table takes more than the normal view,
+    but its content is updated automatically and this is faster than the
+    <command>REFRESH MATERIALIZED VIEW</command> command.
+
+<programlisting>
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 13.068 ms
+</programlisting>
+
+</para>
+
+<para>
+    Appropriate indexes on <acronym>IMMV</acronym>s are necessary for
+    efficient <acronym>IVM</acronym> because it looks for tuples to be
+    updated in <acronym>IMMV</acronym>.  If there are no indexes, it
+    will take a long time.
+</para>
+
+<para>
+    Therefore, when <acronym>IMMV</acronym> is defined, a unique index is created on the view
+    automatically if possible.  If the view definition query has a GROUP BY clause, a unique
+    index is created on the columns of GROUP BY expressions.  Also, if the view has DISTINCT
+    clause, a unique index is created on all columns in the target list. Otherwise, if the
+    view contains all primary key attritubes of its base tables in the target list, a unique
+    index is created on these attritubes.  In other cases, no index is created.
+</para>
+
+<para>
+    In the previous example, a unique index "mv_ivm_index" is created on aid and bid
+    columns of materialized view "mv_ivm", and this enables the rapid update of the view.
+    Dropping this index make updating the view take a loger time.
+<programlisting>
+test=# DROP INDEX mv_ivm_index;
+DROP INDEX
+Time: 67.081 ms
+
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 16386.245 ms (00:16.386)
+</programlisting>
+
+</para>
+
+<para>
+    <acronym>IVM</acronym> is effective when we want to keep a materialized
+    view up-to-date and small fraction of a base table is modified
+    infrequently.  Due to the overhead of immediate maintenance, <acronym>IVM</acronym>
+    is not effective when a base table is modified frequently.  Also, when a
+    large part of a base table is modified or large data is inserted into a
+    base table, <acronym>IVM</acronym> is not effective and the cost of
+    maintenance can be larger than the <command>REFRESH MATERIALIZED VIEW</command>
+    command. In such situation, we can use <command>REFRESH MATERIALIZED VIEW</command>
+    and specify <literal>WITH NO DATA</literal> to disable immediate
+    maintenance before modifying a base table. After a base table modification,
+    execute the <command>REFRESH MATERIALIZED VIEW</command> (with <literal>WITH DATA</literal>)
+    command to refresh the view data and enable immediate maintenance.
+</para>
+
+</sect2>
+
+<sect2>
+<title>Supported View Definitions and Restrictions</title>
+
+<para>
+    Currently, we can create <acronym>IMMV</acronym>s using inner joins, and some
+    aggregates. However, several restrictions apply to the definition of IMMV.
+</para>
+
+<sect3>
+<title>Joins</title>
+<para>
+    Inner joins including self-join are supported. Outer joins are not supported.
+</para>
+</sect3>
+
+<sect3>
+<title>Aggregates</title>
+<para>
+    Supported aggregate functions are <function>count</function>, <function>sum</function>,
+    <function>avg</function>, <function>min</function>, and <function>max</function>.
+    Currently, only built-in aggregate functions are supported and user defined
+    aggregates cannot be used.  When a base table is modified, the new aggregated
+    values are incrementally calculated using the old aggregated values and values
+    of related hidden columns stored in <acronym>IMMV</acronym>.
+</para>
+
+<para>
+     Note that for <function>min</function> or <function>max</function>, the new values
+     could be re-calculated from base tables with regard to the affected groups when a
+     tuple containing the current minimal or maximal values are deleted from a base table.
+     Therefore, it can takes a long time to update an <acronym>IMMV</acronym> containing
+     these functions.
+</para>
+
+<para>
+    Also note that using <function>sum</function> or <function>avg</function> on
+    <type>real</type> (<type>float4</type>) type or <type>double precision</type>
+    (<type>float8</type>) type in <acronym>IMMV</acronym> is unsafe. This is
+    because aggregated values in <acronym>IMMV</acronym> can become different from
+    results calculated from base tables due to the limited precision of these types.
+    To avoid this problem, use the <type>numeric</type> type instead.
+</para>
+
+    <sect4>
+    <title>Restrictions on Aggregates</title>
+    <para>
+        There are the following restrictions:
+    <itemizedlist>
+        <listitem>
+        <para>
+            If we have a <literal>GROUP BY</literal> clause, expressions specified in
+               <literal>GROUP BY</literal> must appear in the target list.  This is
+               how tuples to be updated in the <acronym>IMMV</acronym> are identified.
+               These attributes are used as scan keys for searching tuples in the
+               <acronym>IMMV</acronym>, so indexes on them are required for efficient
+               <acronym>IVM</acronym>.
+        </para>
+        </listitem>
+
+        <listitem>
+        <para>
+            <literal>HAVING</literal> clause cannot be used.
+        </para>
+        </listitem>
+    </itemizedlist>
+    </para>
+    </sect4>
+</sect3>
+
+<sect3>
+<title>Other General Restrictions</title>
+<para>
+    There are other restrictions which generally apply to <acronym>IMMV</acronym>:
+    <itemizedlist>
+        <listitem>
+          <para>
+           Sub-queries cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+           CTEs cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+           Window functions cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s must be based on simple base tables.  It's not
+               supported to create them on top of views, materialized views, foreign tables, inhe.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            LIMIT and OFFSET clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s cannot contain system columns.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s cannot contain non-immutable functions.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            UNION/INTERSECT/EXCEPT clauses cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            DISTINCT ON clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            TABLESAMPLE parameter cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            inheritance parent tables cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            VALUES clause cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <literal>GROUPING SETS</literal> and <literal>FILTER</literal> clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            FOR UPDATE/SHARE cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            targetlist cannot contain columns whose name start with <literal>__ivm_</literal>.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            targetlist cannot contain expressions which contain an aggregate in it.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+              Logical replication is not supported, that is, even when a base table
+               at a publisher node is modified, <acronym>IMMV</acronym>s at subscriber
+               nodes are not updated.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            When the <literal>TRUNCATE</literal> command is executed on a base table,
+               nothing is changed on the <acronym>IMMV</acronym>.
+          </para>
+        </listitem>
+
+    </itemizedlist>
+</para>
+</sect3>
+
+</sect2>
+
+<sect2>
+<title><literal>DISTINCT</literal></title>
+
+<para>
+    <productname>PostgreSQL</productname> supports <acronym>IMMV</acronym> with
+    <literal>DISTINCT</literal>.  For example, suppose a <acronym>IMMV</acronym>
+    defined with <literal>DISTINCT</literal> on a base table containing duplicate
+    tuples.  When tuples are deleted from the base table, a tuple in the view is
+    deleted if and only if the multiplicity of the tuple becomes zero.  Moreover,
+    when tuples are inserted into the base table, a tuple is inserted into the
+    view only if the same tuple doesn't already exist in it.
+</para>
+
+<para>
+    Physically, an <acronym>IMMV</acronym> defined with <literal>DISTINCT</literal>
+    contains tuples after eliminating duplicates, and the multiplicity of each tuple
+    is stored in a hidden column named <literal>__ivm_count__</literal>.
+</para>
+</sect2>
+
+<sect2>
+<title>Concurrent Transactions</title>
+<para>
+    Suppose an <acronym>IMMV</acronym> is defined on two base tables and each
+    table was modified in different a concurrent transaction simultaneously.
+    In the transaction which was committed first, <acronym>IMMV</acronym> can
+    be updated considering only the change which happened in this transaction.
+    On the other hand, in order to update the view correctly in the transaction
+    which was committed later, we need to know the changes occurred in
+    both transactions.  For this reason, <literal>ExclusiveLock</literal>
+    is held on an <acronym>IMMV</acronym> immediately after a base table is
+    modified in <literal>READ COMMITTED</literal> mode to make sure that
+    the <acronym>IMMV</acronym> is updated in the latter transaction after
+    the former transaction is committed.  In <literal>REPEATABLE READ</literal>
+    or <literal>SERIALIZABLE</literal> mode, an error is raised immediately
+    if lock acquisition fails because any changes which occurred in
+    other transactions are not be visible in these modes and
+    <acronym>IMMV</acronym> cannot be updated correctly in such situations.
+    However, as an exception if the view has only one base table, 
+    the lock held on thew view is <literal>RowExclusiveLock</literal>.
+</para>
+</sect2>
+
+<sect2>
+<title>Row Level Security</title>
+<para>
+    If some base tables have row level security policy, rows that are not visible
+    to the materialized view's owner are excluded from the result.  In addition, such
+    rows are excluded as well when views are incrementally maintained.  However, if a
+    new policy is defined or policies are changed after the materialized view was created,
+    the new policy will not be applied to the view contents.  To apply the new policy,
+    you need to refresh materialized views.
+</para>
+</sect2>
+
+</sect1>
+
 <sect1 id="rules-update">
 <title>Rules on <command>INSERT</command>, <command>UPDATE</command>, and <command>DELETE</command></title>
 
-- 
2.17.1

#220Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Yugo NAGATA (#219)
9 attachment(s)
Re: Implementing Incremental View Maintenance

On Fri, 22 Apr 2022 11:29:39 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi,

On Fri, 1 Apr 2022 11:09:16 -0400
Greg Stark <stark@mit.edu> wrote:

This patch has bitrotted due to some other patch affecting trigger.c.

Could you post a rebase?

This is the last week of the CF before feature freeze so time is of the essence.

I attached a rebased patch-set.

Also, I made the folowing changes from the previous.

1. Fix to not use a new deptye

In the previous patch, we introduced a new deptye 'm' into pg_depend.
This deptype was used for looking for IVM triggers to be removed at
REFRESH WITH NO DATA. However, we decided to not use it for reducing
unnecessary change in the core code. Currently, the trigger name and
dependent objclass are used at that time instead of it.

As a result, the number of patches are reduced to nine from ten.

2. Bump the version numbers in psql and pg_dump

This feature's target is PG 16 now.

Sorry, I revert this change. It was too early to bump up the
version number.

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

v27-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patchtext/x-diff; name=v27-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patchDownload
From 5437965959d6dc512328ffee8ff9dea6b22e48f8 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:05:02 +0900
Subject: [PATCH v27 1/9] Add a syntax to create Incrementally Maintainable
 Materialized Views

Allow to create Incrementally Maintainable Materialized View (IMMV)
by using INCREMENTAL option in CREATE MATERIALIZED VIEW command
as follow:

     CREATE [INCREMANTAL] MATERIALIZED VIEW xxxxx AS SELECT ....;
---
 src/backend/nodes/copyfuncs.c  |  1 +
 src/backend/nodes/equalfuncs.c |  1 +
 src/backend/nodes/outfuncs.c   |  1 +
 src/backend/nodes/readfuncs.c  |  1 +
 src/backend/parser/gram.y      | 32 +++++++++++++++++++++-----------
 src/include/nodes/primnodes.h  |  1 +
 src/include/parser/kwlist.h    |  1 +
 7 files changed, 27 insertions(+), 11 deletions(-)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 836f427ea8..0490bce664 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1434,6 +1434,7 @@ _copyIntoClause(const IntoClause *from)
 	COPY_STRING_FIELD(tableSpaceName);
 	COPY_NODE_FIELD(viewQuery);
 	COPY_SCALAR_FIELD(skipData);
+	COPY_SCALAR_FIELD(ivm);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e013c1bbfe..55f41263ee 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -211,6 +211,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
 	COMPARE_STRING_FIELD(tableSpaceName);
 	COMPARE_NODE_FIELD(viewQuery);
 	COMPARE_SCALAR_FIELD(skipData);
+	COMPARE_SCALAR_FIELD(ivm);
 
 	return true;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6a02f81ad5..cfd3ce68b4 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1127,6 +1127,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
 	WRITE_STRING_FIELD(tableSpaceName);
 	WRITE_NODE_FIELD(viewQuery);
 	WRITE_BOOL_FIELD(skipData);
+	WRITE_BOOL_FIELD(ivm);
 }
 
 static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ddf76ac778..5165fb3b93 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -605,6 +605,7 @@ _readIntoClause(void)
 	READ_STRING_FIELD(tableSpaceName);
 	READ_NODE_FIELD(viewQuery);
 	READ_BOOL_FIELD(skipData);
+	READ_BOOL_FIELD(ivm);
 
 	READ_DONE();
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c9941d9cb4..aa134eafc9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -468,6 +468,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target create_mv_target
+%type <boolean>	incremental
 
 %type <defelt>	createfunc_opt_item common_func_opt_item dostmt_opt_item
 %type <fun_param> func_arg func_arg_with_default table_func_column aggr_arg
@@ -801,7 +802,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
 	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
-	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+	INCLUDING INCREMENT INCREMENTAL INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
 	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
 	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -4492,30 +4493,32 @@ opt_with_data:
  *****************************************************************************/
 
 CreateMatViewStmt:
-		CREATE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
+		CREATE OptNoLog incremental MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $7;
-					ctas->into = $5;
+					ctas->query = $8;
+					ctas->into = $6;
 					ctas->objtype = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = false;
 					/* cram additional flags into the IntoClause */
-					$5->rel->relpersistence = $2;
-					$5->skipData = !($8);
+					$6->rel->relpersistence = $2;
+					$6->skipData = !($9);
+					$6->ivm = $3;
 					$$ = (Node *) ctas;
 				}
-		| CREATE OptNoLog MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
+		| CREATE OptNoLog incremental MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
 				{
 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
-					ctas->query = $10;
-					ctas->into = $8;
+					ctas->query = $11;
+					ctas->into = $9;
 					ctas->objtype = OBJECT_MATVIEW;
 					ctas->is_select_into = false;
 					ctas->if_not_exists = true;
 					/* cram additional flags into the IntoClause */
-					$8->rel->relpersistence = $2;
-					$8->skipData = !($11);
+					$9->rel->relpersistence = $2;
+					$9->skipData = !($12);
+					$9->ivm = $3;
 					$$ = (Node *) ctas;
 				}
 		;
@@ -4532,9 +4535,14 @@ create_mv_target:
 					$$->tableSpaceName = $5;
 					$$->viewQuery = NULL;		/* filled at analysis time */
 					$$->skipData = false;		/* might get changed later */
+					$$->ivm = false;
 				}
 		;
 
+incremental:	INCREMENTAL				{ $$ = true; }
+				| /*EMPTY*/				{ $$ = false; }
+		;
+
 OptNoLog:	UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
 		;
@@ -17007,6 +17015,7 @@ unreserved_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
@@ -17588,6 +17597,7 @@ bare_label_keyword:
 			| INCLUDE
 			| INCLUDING
 			| INCREMENT
+			| INCREMENTAL
 			| INDEX
 			| INDEXES
 			| INHERIT
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 66d32fc006..acac38b91c 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -126,6 +126,7 @@ typedef struct IntoClause
 	char	   *tableSpaceName; /* table space to use, or NULL */
 	Node	   *viewQuery;		/* materialized view's SELECT query */
 	bool		skipData;		/* true for WITH NO DATA */
+	bool		ivm;			/* true for WITH IVM */
 } IntoClause;
 
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 8a2ab405a2..7cd9b54a01 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -210,6 +210,7 @@ PG_KEYWORD("in", IN_P, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("incremental", INCREMENTAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD, BARE_LABEL)
-- 
2.17.1

v27-0002-Add-relisivm-column-to-pg_class-system-catalog.patchtext/x-diff; name=v27-0002-Add-relisivm-column-to-pg_class-system-catalog.patchDownload
From bd5bcbb73ab7ab535a71021fa80ef6c0a5543d6a Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:07:23 +0900
Subject: [PATCH v27 2/9] Add relisivm column to pg_class system catalog

If this boolean column is true, a relations is Incrementally Maintainable
Materialized View (IMMV). This is set when IMMV is created.
---
 src/backend/catalog/heap.c          |  1 +
 src/backend/catalog/index.c         |  1 +
 src/backend/utils/cache/lsyscache.c | 24 ++++++++++++++++++++++++
 src/backend/utils/cache/relcache.c  |  2 ++
 src/include/catalog/pg_class.h      |  3 +++
 src/include/utils/lsyscache.h       |  1 +
 src/include/utils/rel.h             |  2 ++
 7 files changed, 34 insertions(+)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9b512ccd3c..a9a314fefe 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -920,6 +920,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
+	values[Anum_pg_class_relisivm - 1] = BoolGetDatum(rd_rel->relisivm);
 	if (relacl != (Datum) 0)
 		values[Anum_pg_class_relacl - 1] = relacl;
 	else
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index fd389c28d8..4d1c99a8bd 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -982,6 +982,7 @@ index_create(Relation heapRelation,
 	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
 	indexRelation->rd_rel->relam = accessMethodObjectId;
 	indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid);
+	indexRelation->rd_rel->relisivm = false;
 
 	/*
 	 * store index's pg_class entry
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1b7e11b93e..3275963886 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2023,6 +2023,30 @@ get_rel_relispartition(Oid relid)
 		return false;
 }
 
+/*
+ * get_rel_relisivm
+ *
+ *		Returns the relisivm flag associated with a given relation.
+ */
+bool
+get_rel_relisivm(Oid relid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp);
+		bool		result;
+
+		result = reltup->relisivm;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return false;
+}
+
 /*
  * get_rel_tablespace
  *
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 43f14c233d..a67ba4d8b1 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1923,6 +1923,8 @@ formrdesc(const char *relationName, Oid relationReltype,
 
 	/* ... and they're always populated, too */
 	relation->rd_rel->relispopulated = true;
+	/* ... and they're always no ivm, too */
+	relation->rd_rel->relisivm = false;
 
 	relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
 	relation->rd_rel->relpages = 0;
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 304e8c18d5..fbaa1438d6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -119,6 +119,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
 	/* is relation a partition? */
 	bool		relispartition BKI_DEFAULT(f);
 
+	/* is relation a matview with ivm? */
+	bool		relisivm BKI_DEFAULT(f);
+
 	/* link to original rel during table rewrite; otherwise 0 */
 	Oid			relrewrite BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_class);
 
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index b8dd27d4a9..cc2f635122 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -137,6 +137,7 @@ extern Oid	get_rel_namespace(Oid relid);
 extern Oid	get_rel_type_id(Oid relid);
 extern char get_rel_relkind(Oid relid);
 extern bool get_rel_relispartition(Oid relid);
+extern bool get_rel_relisivm(Oid relid);
 extern Oid	get_rel_tablespace(Oid relid);
 extern char get_rel_persistence(Oid relid);
 extern Oid	get_transform_fromsql(Oid typid, Oid langid, List *trftypes);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index eadbd00904..0e28716e6b 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -661,6 +661,8 @@ RelationGetSmgr(Relation rel)
  */
 #define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated)
 
+#define RelationIsIVM(relation) ((relation)->rd_rel->relisivm)
+
 /*
  * RelationIsAccessibleInLogicalDecoding
  *		True if we need to log enough information to have access via
-- 
2.17.1

v27-0003-Allow-to-prolong-life-span-of-transition-tables-.patchtext/x-diff; name=v27-0003-Allow-to-prolong-life-span-of-transition-tables-.patchDownload
From 2ef59a56b5df4f2449e615af36a7245f1d8a9880 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:09:45 +0900
Subject: [PATCH v27 3/9] Allow to prolong life span of transition tables until
 transaction end

Originally, tuplestores of AFTER trigger's transition tables were
freed for each query depth. For our IVM implementation, we would like
to prolong life of the tuplestores because we have to preserve them
for a whole query assuming that some base tables might be changed
in some trigger functions.
---
 src/backend/commands/trigger.c | 83 ++++++++++++++++++++++++++++++++--
 src/include/commands/trigger.h |  2 +
 2 files changed, 81 insertions(+), 4 deletions(-)

diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 13cb516752..aa1da7b279 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3730,6 +3730,10 @@ typedef struct AfterTriggerEventList
  * end of the list, so it is relatively easy to discard them.  The event
  * list chunks themselves are stored in event_cxt.
  *
+ * prolonged_tuplestored is a list of transition table tuplestores whose
+ * life are prolonged to the end of the outmost query instead of each nested
+ * query.
+ *
  * query_depth is the current depth of nested AfterTriggerBeginQuery calls
  * (-1 when the stack is empty).
  *
@@ -3795,6 +3799,7 @@ typedef struct AfterTriggersData
 	SetConstraintState state;	/* the active S C state */
 	AfterTriggerEventList events;	/* deferred-event list */
 	MemoryContext event_cxt;	/* memory context for events, if any */
+	List   *prolonged_tuplestores;	/* list of prolonged tuplestores */
 
 	/* per-query-level data: */
 	AfterTriggersQueryData *query_stack;	/* array of structs shown below */
@@ -3830,6 +3835,7 @@ struct AfterTriggersTableData
 	bool		closed;			/* true when no longer OK to add tuples */
 	bool		before_trig_done;	/* did we already queue BS triggers? */
 	bool		after_trig_done;	/* did we already queue AS triggers? */
+	bool		prolonged;			/* are transition tables prolonged? */
 	AfterTriggerEventList after_trig_events;	/* if so, saved list pointer */
 
 	/*
@@ -3879,6 +3885,7 @@ static void TransitionTableAddTuple(EState *estate,
 									TupleTableSlot *original_insert_tuple,
 									Tuplestorestate *tuplestore);
 static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs);
+static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged);
 static SetConstraintState SetConstraintStateCreate(int numalloc);
 static SetConstraintState SetConstraintStateCopy(SetConstraintState state);
 static SetConstraintState SetConstraintStateAddItem(SetConstraintState state,
@@ -4725,6 +4732,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
 }
 
 
+/*
+ * SetTransitionTablePreserved
+ *
+ * Prolong lifespan of transition tables corresponding specified relid and
+ * command type to the end of the outmost query instead of each nested query.
+ * This enables to use nested AFTER trigger's transition tables from outer
+ * query's triggers.  Currently, only immediate incremental view maintenance
+ * uses this.
+ */
+void
+SetTransitionTablePreserved(Oid relid, CmdType cmdType)
+{
+	AfterTriggersTableData *table;
+	AfterTriggersQueryData *qs;
+	bool		found = false;
+	ListCell   *lc;
+
+	/* Check state, like AfterTriggerSaveEvent. */
+	if (afterTriggers.query_depth < 0)
+		elog(ERROR, "SetTransitionTablePreserved() called outside of query");
+
+	qs = &afterTriggers.query_stack[afterTriggers.query_depth];
+
+	foreach(lc, qs->tables)
+	{
+		table = (AfterTriggersTableData *) lfirst(lc);
+		if (table->relid == relid && table->cmdType == cmdType &&
+			table->closed)
+		{
+			table->prolonged = true;
+			found = true;
+		}
+	}
+
+	if (!found)
+		elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType);
+}
+
+
 /*
  * GetAfterTriggersTableData
  *
@@ -4933,6 +4979,7 @@ AfterTriggerBeginXact(void)
 	 */
 	afterTriggers.firing_counter = (CommandId) 1;	/* mustn't be 0 */
 	afterTriggers.query_depth = -1;
+	afterTriggers.prolonged_tuplestores = NIL;
 
 	/*
 	 * Verify that there is no leftover state remaining.  If these assertions
@@ -5093,19 +5140,19 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
 		ts = table->old_upd_tuplestore;
 		table->old_upd_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+			release_or_prolong_tuplestore(ts, table->prolonged);
 		ts = table->new_upd_tuplestore;
 		table->new_upd_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+			release_or_prolong_tuplestore(ts, table->prolonged);
 		ts = table->old_del_tuplestore;
 		table->old_del_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+			release_or_prolong_tuplestore(ts, table->prolonged);
 		ts = table->new_ins_tuplestore;
 		table->new_ins_tuplestore = NULL;
 		if (ts)
-			tuplestore_end(ts);
+			release_or_prolong_tuplestore(ts, table->prolonged);
 		if (table->storeslot)
 			ExecDropSingleTupleTableSlot(table->storeslot);
 	}
@@ -5117,6 +5164,34 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
 	 */
 	qs->tables = NIL;
 	list_free_deep(tables);
+
+	/* Release prolonged tuplestores at the end of the outmost query */
+	if (afterTriggers.query_depth == 0)
+	{
+		foreach(lc, afterTriggers.prolonged_tuplestores)
+		{
+			ts = (Tuplestorestate *) lfirst(lc);
+			if (ts)
+				tuplestore_end(ts);
+		}
+		afterTriggers.prolonged_tuplestores = NIL;
+	}
+}
+
+/*
+ * Release the tuplestore, or append it to the prolonged tuplestores list.
+ */
+static void
+release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged)
+{
+	if (prolonged && afterTriggers.query_depth > 0)
+	{
+		MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext);
+		afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts);
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else
+		tuplestore_end(ts);
 }
 
 
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 194e8d5bc1..2e9f1ac8c7 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -261,6 +261,8 @@ extern void AfterTriggerEndSubXact(bool isCommit);
 extern void AfterTriggerSetState(ConstraintsSetStmt *stmt);
 extern bool AfterTriggerPendingOnRel(Oid relid);
 
+extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType);
+
 
 /*
  * in utils/adt/ri_triggers.c
-- 
2.17.1

v27-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patchtext/x-diff; name=v27-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patchDownload
From b5d9e69e2287fe47a2c4118ce82b913c84d0b70c Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Wed, 11 Nov 2020 17:01:25 +0900
Subject: [PATCH v27 4/9] Add Incremental View Maintenance support to pg_dump

Support CREATE INCREMENTAL MATERIALIZED VIEW syntax.
---
 src/bin/pg_dump/pg_dump.c        | 18 +++++++++++++++---
 src/bin/pg_dump/pg_dump.h        |  1 +
 src/bin/pg_dump/t/002_pg_dump.pl | 15 +++++++++++++++
 3 files changed, 31 insertions(+), 3 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 969e2a7a46..6340b3e2b7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6089,6 +6089,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_relacl;
 	int			i_acldefault;
 	int			i_ispartition;
+	int			i_isivm;
 
 	/*
 	 * Find all the tables and table-like objects.
@@ -6191,10 +6192,17 @@ getTables(Archive *fout, int *numTables)
 
 	if (fout->remoteVersion >= 100000)
 		appendPQExpBufferStr(query,
-							 "c.relispartition AS ispartition ");
+							 "c.relispartition AS ispartition, ");
 	else
 		appendPQExpBufferStr(query,
-							 "false AS ispartition ");
+							 "false AS ispartition, ");
+
+	if (fout->remoteVersion >= 150000)
+		appendPQExpBufferStr(query,
+							 "c.relisivm AS isivm ");
+	else
+		appendPQExpBufferStr(query,
+							 "false AS isivm ");
 
 	/*
 	 * Left join to pg_depend to pick up dependency info linking sequences to
@@ -6303,6 +6311,7 @@ getTables(Archive *fout, int *numTables)
 	i_relacl = PQfnumber(res, "relacl");
 	i_acldefault = PQfnumber(res, "acldefault");
 	i_ispartition = PQfnumber(res, "ispartition");
+	i_isivm = PQfnumber(res, "isivm");
 
 	if (dopt->lockWaitTimeout)
 	{
@@ -6380,6 +6389,7 @@ getTables(Archive *fout, int *numTables)
 			tblinfo[i].amname = pg_strdup(PQgetvalue(res, i, i_amname));
 		tblinfo[i].is_identity_sequence = (strcmp(PQgetvalue(res, i, i_is_identity_sequence), "t") == 0);
 		tblinfo[i].ispartition = (strcmp(PQgetvalue(res, i, i_ispartition), "t") == 0);
+		tblinfo[i].isivm = (strcmp(PQgetvalue(res, i, i_isivm), "t") == 0);
 
 		/* other fields were zeroed above */
 
@@ -15115,9 +15125,11 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
-		appendPQExpBuffer(q, "CREATE %s%s %s",
+		appendPQExpBuffer(q, "CREATE %s%s%s %s",
 						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
 						  "UNLOGGED " : "",
+						  tbinfo->relkind == RELKIND_MATVIEW && tbinfo->isivm ?
+						  "INCREMENTAL " : "",
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 1d21c2906f..0b798ad5de 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -318,6 +318,7 @@ typedef struct _tableInfo
 	bool		dummy_view;		/* view's real definition must be postponed */
 	bool		postponed_def;	/* matview must be postponed into post-data */
 	bool		ispartition;	/* is table a partition? */
+	bool		isivm;			/* is incrementally maintainable materialized view? */
 
 	/*
 	 * These fields are computed only if we decide the table is interesting
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c65c92bfb0..3655727472 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2295,6 +2295,21 @@ my %tests = (
 		  { exclude_dump_test_schema => 1, no_toast_compression => 1, },
 	},
 
+	'CREATE MATERIALIZED VIEW matview_ivm' => {
+		create_order => 21,
+		create_sql   => 'CREATE INCREMENTAL MATERIALIZED VIEW dump_test.matview_ivm (col1) AS
+					   SELECT col1 FROM dump_test.test_table;',
+		regexp => qr/^
+			\QCREATE INCREMENTAL MATERIALIZED VIEW dump_test.matview_ivm AS\E
+			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QFROM dump_test.test_table\E
+			\n\s+\QWITH NO DATA;\E
+			/xm,
+		like =>
+		  { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, },
+	},
+
 	'CREATE POLICY p1 ON test_table' => {
 		create_order => 22,
 		create_sql   => 'CREATE POLICY p1 ON dump_test.test_table
-- 
2.17.1

v27-0005-Add-Incremental-View-Maintenance-support-to-psql.patchtext/x-diff; name=v27-0005-Add-Incremental-View-Maintenance-support-to-psql.patchDownload
From cd33838bc380b99a4b9704f80384ad28d53a9998 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:21:54 +0900
Subject: [PATCH v27 5/9] Add Incremental View Maintenance support to psql

Add tab completion and meta-command output for IVM.
---
 src/bin/psql/describe.c     | 32 +++++++++++++++++++++++++++++++-
 src/bin/psql/tab-complete.c | 14 +++++++++-----
 2 files changed, 40 insertions(+), 6 deletions(-)

diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 583817b0cc..8cdcb3f048 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1492,6 +1492,7 @@ describeOneTableDetails(const char *schemaname,
 		char		relpersistence;
 		char		relreplident;
 		char	   *relam;
+		bool		isivm;
 	}			tableinfo;
 	bool		show_column_details = false;
 
@@ -1504,7 +1505,26 @@ describeOneTableDetails(const char *schemaname,
 	initPQExpBuffer(&tmpbuf);
 
 	/* Get general table info */
-	if (pset.sversion >= 120000)
+	if (pset.sversion >= 150000)
+	{
+		printfPQExpBuffer(&buf,
+						  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+						  "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, "
+						  "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
+						  "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
+						  "c.relpersistence, c.relreplident, am.amname, "
+						  "c.relisivm\n"
+						  "FROM pg_catalog.pg_class c\n "
+						  "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+						  "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
+						  "WHERE c.oid = '%s';",
+						  (verbose ?
+						   "pg_catalog.array_to_string(c.reloptions || "
+						   "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+						   : "''"),
+						  oid);
+	}
+	else if (pset.sversion >= 120000)
 	{
 		printfPQExpBuffer(&buf,
 						  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
@@ -1624,6 +1644,10 @@ describeOneTableDetails(const char *schemaname,
 			(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
 	else
 		tableinfo.relam = NULL;
+	if (pset.sversion >= 150000)
+		tableinfo.isivm = strcmp(PQgetvalue(res, 0, 15), "t") == 0;
+	else
+		tableinfo.isivm = false;
 	PQclear(res);
 	res = NULL;
 
@@ -3424,6 +3448,12 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
 			printTableAddFooter(&cont, buf.data);
 		}
+
+		/* Incremental view maintance info */
+		if (verbose && tableinfo.relkind == RELKIND_MATVIEW && tableinfo.isivm)
+		{
+			printTableAddFooter(&cont, _("Incremental view maintenance: yes"));
+		}
 	}
 
 	/* reloptions, if verbose */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 588c0841fe..87b5d3e107 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1221,6 +1221,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"INCREMENTAL MATERIALIZED VIEW", NULL, NULL, &Query_for_list_of_matviews, NULL, THING_NO_DROP | THING_NO_ALTER},
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -3074,7 +3075,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
-		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
+		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW", "INCREMENTAL MATERIALIZED VIEW");
 	/* Complete PARTITION BY with RANGE ( or LIST ( or ... */
 	else if (TailMatches("PARTITION", "BY"))
 		COMPLETE_WITH("RANGE (", "LIST (", "HASH (");
@@ -3390,13 +3391,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches("CREATE", "MATERIALIZED"))
+	else if (Matches("CREATE", "MATERIALIZED") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED"))
 		COMPLETE_WITH("VIEW");
-	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	/* Complete CREATE MATERIALIZED VIEW <name> with AS  */
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny) ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS") ||
+			 Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH("SELECT");
 
 /* CREATE EVENT TRIGGER */
-- 
2.17.1

v27-0006-Add-Incremental-View-Maintenance-support.patchtext/x-diff; name=v27-0006-Add-Incremental-View-Maintenance-support.patchDownload
From a267c2873b72dc886f76b459ef35b5923823691f Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Thu, 21 Apr 2022 11:58:53 +0900
Subject: [PATCH v27 6/9] Add Incremental View Maintenance support

In this implementation, AFTER triggers are used to collect
tuplestores containing transition table contents. When multiple tables
are changed, multiple AFTER triggers are invoked, then the final AFTER
trigger performs actual update of the matview. In addition, BEFORE
triggers are also used to handle global information for view
maintenance. To calculate view deltas, we need both pre-state and
post-state of base tables. Post-update states are available in AFTER
trigger, and pre-update states can be calculated by filtering inserted
tuples using cmin/xmin system columns, and append deleted tuples which
are contained in an old transition table.

This patch also allows self-join, simultaneous updates of more than
one base table, and multiple updates of the same base table.

Incrementally Maintainable Materialized Views (IMMV) can contain
duplicated tuples. Also, DISTINCT clause is supported. When IMMV is
created with DISTINCT, multiplicity of tuples is counted and stored
in  "__ivm_count__" column, which is a hidden column of IMMV.
The value in __ivm_count__ is updated when IMMV is maintained
incrementally. A tuple in IMMV can be removed if and only if the
count becomes zero.
---
 src/backend/access/transam/xact.c   |    5 +
 src/backend/commands/createas.c     |  751 +++++++++++++
 src/backend/commands/indexcmds.c    |   40 +
 src/backend/commands/matview.c      | 1577 ++++++++++++++++++++++++++-
 src/backend/commands/tablecmds.c    |    9 +
 src/backend/nodes/copyfuncs.c       |    1 +
 src/backend/nodes/equalfuncs.c      |    1 +
 src/backend/nodes/outfuncs.c        |    1 +
 src/backend/nodes/readfuncs.c       |    1 +
 src/backend/parser/parse_relation.c |   18 +-
 src/backend/rewrite/rewriteDefine.c |    3 +-
 src/include/catalog/pg_proc.dat     |    8 +
 src/include/commands/createas.h     |    6 +
 src/include/commands/matview.h      |    8 +
 src/include/nodes/parsenodes.h      |    2 +
 15 files changed, 2391 insertions(+), 40 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 53f3e7fd1a..0377f5a88b 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -36,6 +36,7 @@
 #include "catalog/pg_enum.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/matview.h"
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "common/pg_prng.h"
@@ -2792,6 +2793,7 @@ AbortTransaction(void)
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
 	AtAbort_Twophase();
+	AtAbort_IVM();
 
 	/*
 	 * Advertise the fact that we aborted in pg_xact (assuming that we got as
@@ -5032,6 +5034,9 @@ AbortSubTransaction(void)
 	AbortBufferIO();
 	UnlockBuffers();
 
+	/* Clean up hash entries for incremental view maintenance */
+	AtAbort_IVM();
+
 	/* Reset WAL record construction state */
 	XLogResetInsertion();
 
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 9abbb6b555..1224a3b075 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -32,24 +32,41 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
 #include "catalog/toasting.h"
 #include "commands/createas.h"
+#include "commands/defrem.h"
 #include "commands/matview.h"
 #include "commands/prepare.h"
 #include "commands/tablecmds.h"
+#include "commands/tablespace.h"
+#include "commands/trigger.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "optimizer/clauses.h"
+#include "optimizer/optimizer.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_func.h"
+#include "parser/parse_type.h"
 #include "rewrite/rewriteHandler.h"
+#include "rewrite/rewriteManip.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
+#include "utils/syscache.h"
 
 typedef struct
 {
@@ -73,6 +90,13 @@ static bool intorel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void intorel_shutdown(DestReceiver *self);
 static void intorel_destroy(DestReceiver *self);
 
+static void CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid,
+									 Relids *relids, bool ex_lock);
+static void CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock);
+static void check_ivm_restriction(Node *node);
+static bool check_ivm_restriction_walker(Node *node, void *context);
+static Bitmapset *get_primary_key_attnos_from_query(Query *qry, List **constraintList, bool is_create);
+static Bitmapset *get_primary_key_attnos_from_query(Query *query, List **constraintList, bool is_create);
 
 /*
  * create_ctas_internal
@@ -108,6 +132,8 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	create->oncommit = into->onCommit;
 	create->tablespacename = into->tableSpaceName;
 	create->if_not_exists = false;
+	/* Using Materialized view only */
+	create->ivm = into->ivm;
 	create->accessMethod = into->accessMethod;
 
 	/*
@@ -282,6 +308,21 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 		save_nestlevel = NewGUCNestLevel();
 	}
 
+	if (is_matview && into->ivm)
+	{
+		/* check if the query is supported in IMMV definition */
+		if (contain_mutable_functions((Node *) query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("mutable function is not supported on incrementally maintainable materialized view"),
+					 errhint("functions must be marked IMMUTABLE")));
+
+		check_ivm_restriction((Node *) query);
+
+		/* For IMMV, we need to rewrite matview query */
+		query = rewriteQueryForIMMV(query, into->colNames);
+	}
+
 	if (into->skipData)
 	{
 		/*
@@ -358,11 +399,74 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 
 		/* Restore userid and security context */
 		SetUserIdAndSecContext(save_userid, save_sec_context);
+
+		if (into->ivm)
+		{
+			Oid matviewOid = address.objectId;
+			Relation matviewRel = table_open(matviewOid, NoLock);
+
+			/*
+			 * Mark relisivm field, if it's a matview and into->ivm is true.
+			 */
+			SetMatViewIVMState(matviewRel, true);
+
+			if (!into->skipData)
+			{
+				/* Create an index on incremental maintainable materialized view, if possible */
+				CreateIndexOnIMMV((Query *) into->viewQuery, matviewRel, true);
+
+				/* Create triggers on incremental maintainable materialized view */
+				CreateIvmTriggersOnBaseTables((Query *) into->viewQuery, matviewOid, true);
+			}
+			table_close(matviewRel, NoLock);
+		}
 	}
 
 	return address;
 }
 
+/*
+ * rewriteQueryForIMMV -- rewrite view definition query for IMMV
+ *
+ * count(*) is added for counting distinct tuples in views.
+ */
+Query *
+rewriteQueryForIMMV(Query *query, List *colNames)
+{
+	Query *rewritten;
+
+	TargetEntry *tle;
+	Node *node;
+	ParseState *pstate = make_parsestate(NULL);
+	FuncCall *fn;
+
+	rewritten = copyObject(query);
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	/*
+	 * Convert DISTINCT to GROUP BY and add count(*) for counting distinct
+	 * tuples in views.
+	 */
+	if (rewritten->distinctClause)
+	{
+		rewritten->groupClause = transformDistinctClause(NULL, &rewritten->targetList, rewritten->sortClause, false);
+
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+		fn->agg_star = true;
+
+		node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+		tle = makeTargetEntry((Expr *) node,
+								list_length(rewritten->targetList) + 1,
+								pstrdup("__ivm_count__"),
+								false);
+		rewritten->targetList = lappend(rewritten->targetList, tle);
+		rewritten->hasAggs = true;
+	}
+
+	return rewritten;
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -623,3 +727,650 @@ intorel_destroy(DestReceiver *self)
 {
 	pfree(self);
 }
+
+/*
+ * CreateIvmTriggersOnBaseTables -- create IVM triggers on all base tables
+ */
+void
+CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create)
+{
+	Relids	relids = NULL;
+	bool	ex_lock = false;
+	Index	first_rtindex = is_create ? 1 : PRS2_NEW_VARNO + 1;
+	RangeTblEntry *rte;
+
+	/* Immediately return if we don't have any base tables. */
+	if (list_length(qry->rtable) < first_rtindex)
+		return;
+
+	/*
+	 * If the view has more than one base tables, we need an exclusive lock
+	 * on the view so that the view would be maintained serially to avoid
+	 * the inconsistency that occurs when two base tables are modified in
+	 * concurrent transactions. However, if the view has only one table,
+	 * we can use a weaker lock.
+	 *
+	 * The type of lock should be determined here, because if we check the
+	 * view definition at maintenance time, we need to acquire a weaker lock,
+	 * and upgrading the lock level after this increases probability of
+	 * deadlock.
+	 */
+
+	rte = list_nth(qry->rtable, first_rtindex - 1);
+	if (list_length(qry->rtable) > first_rtindex ||
+		rte->rtekind != RTE_RELATION)
+		ex_lock = true;
+
+	CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *)qry, matviewOid, &relids, ex_lock);
+
+	bms_free(relids);
+}
+
+static void
+CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid,
+									 Relids *relids, bool ex_lock)
+{
+	if (node == NULL)
+		return;
+
+	/* This can recurse, so check for excessive recursion */
+	check_stack_depth();
+
+	switch (nodeTag(node))
+	{
+		case T_Query:
+			{
+				Query *query = (Query *) node;
+
+				CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *)query->jointree, matviewOid, relids, ex_lock);
+			}
+			break;
+
+		case T_RangeTblRef:
+			{
+				int			rti = ((RangeTblRef *) node)->rtindex;
+				RangeTblEntry *rte = rt_fetch(rti, qry->rtable);
+
+				if (rte->rtekind == RTE_RELATION && !bms_is_member(rte->relid, *relids))
+				{
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_BEFORE, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_BEFORE, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_BEFORE, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_AFTER, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_AFTER, ex_lock);
+					CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_AFTER, ex_lock);
+
+					*relids = bms_add_member(*relids, rte->relid);
+				}
+			}
+			break;
+
+		case T_FromExpr:
+			{
+				FromExpr   *f = (FromExpr *) node;
+				ListCell   *l;
+
+				foreach(l, f->fromlist)
+					CreateIvmTriggersOnBaseTablesRecurse(qry, lfirst(l), matviewOid, relids, ex_lock);
+			}
+			break;
+
+		case T_JoinExpr:
+			{
+				JoinExpr   *j = (JoinExpr *) node;
+
+				CreateIvmTriggersOnBaseTablesRecurse(qry, j->larg, matviewOid, relids, ex_lock);
+				CreateIvmTriggersOnBaseTablesRecurse(qry, j->rarg, matviewOid, relids, ex_lock);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
+	}
+}
+
+/*
+ * CreateIvmTrigger -- create IVM trigger on a base table
+ */
+static void
+CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock)
+{
+	ObjectAddress	refaddr;
+	ObjectAddress	address;
+	CreateTrigStmt *ivm_trigger;
+	List *transitionRels = NIL;
+
+	Assert(timing == TRIGGER_TYPE_BEFORE || timing == TRIGGER_TYPE_AFTER);
+
+	refaddr.classId = RelationRelationId;
+	refaddr.objectId = viewOid;
+	refaddr.objectSubId = 0;
+
+	ivm_trigger = makeNode(CreateTrigStmt);
+	ivm_trigger->relation = NULL;
+	ivm_trigger->row = false;
+
+	ivm_trigger->timing = timing;
+	ivm_trigger->events = type;
+
+	switch (type)
+	{
+		case TRIGGER_TYPE_INSERT:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_ins_before" : "IVM_trigger_ins_after");
+			break;
+		case TRIGGER_TYPE_DELETE:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_del_before" : "IVM_trigger_del_after");
+			break;
+		case TRIGGER_TYPE_UPDATE:
+			ivm_trigger->trigname = (timing == TRIGGER_TYPE_BEFORE ? "IVM_trigger_upd_before" : "IVM_trigger_upd_after");
+			break;
+		default:
+			elog(ERROR, "unsupported trigger type");
+	}
+
+	if (timing == TRIGGER_TYPE_AFTER)
+	{
+		if (type == TRIGGER_TYPE_INSERT || type == TRIGGER_TYPE_UPDATE)
+		{
+			TriggerTransition *n = makeNode(TriggerTransition);
+			n->name = "__ivm_newtable";
+			n->isNew = true;
+			n->isTable = true;
+
+			transitionRels = lappend(transitionRels, n);
+		}
+		if (type == TRIGGER_TYPE_DELETE || type == TRIGGER_TYPE_UPDATE)
+		{
+			TriggerTransition *n = makeNode(TriggerTransition);
+			n->name = "__ivm_oldtable";
+			n->isNew = false;
+			n->isTable = true;
+
+			transitionRels = lappend(transitionRels, n);
+		}
+	}
+
+	ivm_trigger->funcname =
+		(timing == TRIGGER_TYPE_BEFORE ? SystemFuncName("IVM_immediate_before") : SystemFuncName("IVM_immediate_maintenance"));
+
+	ivm_trigger->columns = NIL;
+	ivm_trigger->transitionRels = transitionRels;
+	ivm_trigger->whenClause = NULL;
+	ivm_trigger->isconstraint = false;
+	ivm_trigger->deferrable = false;
+	ivm_trigger->initdeferred = false;
+	ivm_trigger->constrrel = NULL;
+	ivm_trigger->args = list_make2(
+		makeString(DatumGetPointer(DirectFunctionCall1(oidout, ObjectIdGetDatum(viewOid)))),
+		makeString(DatumGetPointer(DirectFunctionCall1(boolout, BoolGetDatum(ex_lock))))
+		);
+
+	address = CreateTrigger(ivm_trigger, NULL, relOid, InvalidOid, InvalidOid,
+						 InvalidOid, InvalidOid, InvalidOid, NULL, true, false);
+
+	recordDependencyOn(&address, &refaddr, DEPENDENCY_AUTO);
+
+	/* Make changes-so-far visible */
+	CommandCounterIncrement();
+}
+
+/*
+ * check_ivm_restriction --- look for specify nodes in the query tree
+ */
+static void
+check_ivm_restriction(Node *node)
+{
+	check_ivm_restriction_walker(node, NULL);
+}
+
+static bool
+check_ivm_restriction_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	/*
+	 * We currently don't support Sub-Query.
+	 */
+	if (IsA(node, SubPlan) || IsA(node, SubLink))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("subquery is not supported on incrementally maintainable materialized view")));
+
+	/* This can recurse, so check for excessive recursion */
+	check_stack_depth();
+
+	switch (nodeTag(node))
+	{
+		case T_Query:
+			{
+				Query *qry = (Query *)node;
+				ListCell   *lc;
+				List       *vars;
+
+				/* if contained CTE, return error */
+				if (qry->cteList != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("CTE is not supported on incrementally maintainable materialized view")));
+				if (qry->havingQual != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg(" HAVING clause is not supported on incrementally maintainable materialized view")));
+				if (qry->sortClause != NIL)	/* There is a possibility that we don't need to return an error */
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("ORDER BY clause is not supported on incrementally maintainable materialized view")));
+				if (qry->limitOffset != NULL || qry->limitCount != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("LIMIT/OFFSET clause is not supported on incrementally maintainable materialized view")));
+				if (qry->hasDistinctOn)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DISTINCT ON is not supported on incrementally maintainable materialized view")));
+				if (qry->hasWindowFuncs)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("window functions are not supported on incrementally maintainable materialized view")));
+				if (qry->groupingSets != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("GROUPING SETS, ROLLUP, or CUBE clauses is not supported on incrementally maintainable materialized view")));
+				if (qry->setOperations != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("UNION/INTERSECT/EXCEPT statements are not supported on incrementally maintainable materialized view")));
+				if (list_length(qry->targetList) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("empty target list is not supported on incrementally maintainable materialized view")));
+				if (qry->rowMarks != NIL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("FOR UPDATE/SHARE clause is not supported on incrementally maintainable materialized view")));
+
+				/* system column restrictions */
+				vars = pull_vars_of_level((Node *) qry, 0);
+				foreach(lc, vars)
+				{
+					if (IsA(lfirst(lc), Var))
+					{
+						Var *var = (Var *) lfirst(lc);
+						/* if system column, return error */
+						if (var->varattno < 0)
+							ereport(ERROR,
+									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									 errmsg("system column is not supported on incrementally maintainable materialized view")));
+					}
+				}
+
+				/* restrictions for rtable */
+				foreach(lc, qry->rtable)
+				{
+					RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+					if (rte->subquery)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("subquery is not supported on incrementally maintainable materialized view")));
+
+					if (rte->tablesample != NULL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("TABLESAMPLE clause is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("partitioned table is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_RELATION && has_superclass(rte->relid))
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("partitions is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_RELATION && find_inheritance_children(rte->relid, NoLock) != NIL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("inheritance parent is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_FOREIGN_TABLE)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("foreign table is not supported on incrementally maintainable materialized view")));
+
+					if (rte->relkind == RELKIND_VIEW ||
+						rte->relkind == RELKIND_MATVIEW)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view")));
+
+					if (rte->rtekind == RTE_VALUES)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("VALUES is not supported on incrementally maintainable materialized view")));
+
+				}
+
+				query_tree_walker(qry, check_ivm_restriction_walker, NULL, QTW_IGNORE_RANGE_TABLE);
+
+				break;
+			}
+		case T_TargetEntry:
+			{
+				TargetEntry *tle = (TargetEntry *)node;
+				if (isIvmName(tle->resname))
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("column name %s is not supported on incrementally maintainable materialized view", tle->resname)));
+
+				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				break;
+			}
+		case T_JoinExpr:
+			{
+				JoinExpr *joinexpr = (JoinExpr *)node;
+
+				if (joinexpr->jointype > JOIN_INNER)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("OUTER JOIN is not supported on incrementally maintainable materialized view")));
+
+				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+			}
+			break;
+		case T_Aggref:
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("aggregate function is not supported on incrementally maintainable materialized view")));
+			break;
+		default:
+			expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
+			break;
+	}
+	return false;
+}
+
+/*
+ * CreateIndexOnIMMV
+ *
+ * Create a unique index on incremental maintainable materialized view.
+ * If the view definition query has a GROUP BY clause, the index is created
+ * on the columns of GROUP BY expressions. Otherwise, if the view contains
+ * all primary key attritubes of its base tables in the target list, the index
+ * is created on these attritubes. In other cases, no index is created.
+ */
+void
+CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create)
+{
+	ListCell *lc;
+	IndexStmt  *index;
+	ObjectAddress address;
+	List *constraintList = NIL;
+	char		idxname[NAMEDATALEN];
+	List	   *indexoidlist = RelationGetIndexList(matviewRel);
+	ListCell   *indexoidscan;
+
+	snprintf(idxname, sizeof(idxname), "%s_index", RelationGetRelationName(matviewRel));
+
+	index = makeNode(IndexStmt);
+
+	/*
+	 * We consider null values not distinct to make sure that views with DISTINCT
+	 * or GROUP BY don't contain multiple NULL rows when NULL is inserted to
+	 * a base table concurrently.
+	 */
+	index->nulls_not_distinct = true;
+
+	index->unique = true;
+	index->primary = false;
+	index->isconstraint = false;
+	index->deferrable = false;
+	index->initdeferred = false;
+	index->idxname = idxname;
+	index->relation =
+		makeRangeVar(get_namespace_name(RelationGetNamespace(matviewRel)),
+					 pstrdup(RelationGetRelationName(matviewRel)),
+					 -1);
+	index->accessMethod = DEFAULT_INDEX_TYPE;
+	index->options = NIL;
+	index->tableSpace = get_tablespace_name(matviewRel->rd_rel->reltablespace);
+	index->whereClause = NULL;
+	index->indexParams = NIL;
+	index->indexIncludingParams = NIL;
+	index->excludeOpNames = NIL;
+	index->idxcomment = NULL;
+	index->indexOid = InvalidOid;
+	index->oldNode = InvalidOid;
+	index->oldCreateSubid = InvalidSubTransactionId;
+	index->oldFirstRelfilenodeSubid = InvalidSubTransactionId;
+	index->transformed = true;
+	index->concurrent = false;
+	index->if_not_exists = false;
+
+	if (query->distinctClause)
+	{
+		/* create unique constraint on all columns */
+		foreach(lc, query->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+			IndexElem  *iparam;
+
+			iparam = makeNode(IndexElem);
+			iparam->name = pstrdup(NameStr(attr->attname));
+			iparam->expr = NULL;
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->opclassopts = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+		}
+	}
+	else
+	{
+		Bitmapset *key_attnos;
+
+		/* create index on the base tables' primary key columns */
+		key_attnos = get_primary_key_attnos_from_query(query, &constraintList, is_create);
+		if (key_attnos)
+		{
+			foreach(lc, query->targetList)
+			{
+				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+				Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+
+				if (bms_is_member(tle->resno - FirstLowInvalidHeapAttributeNumber, key_attnos))
+				{
+					IndexElem  *iparam;
+
+					iparam = makeNode(IndexElem);
+					iparam->name = pstrdup(NameStr(attr->attname));
+					iparam->expr = NULL;
+					iparam->indexcolname = NULL;
+					iparam->collation = NIL;
+					iparam->opclass = NIL;
+					iparam->opclassopts = NIL;
+					iparam->ordering = SORTBY_DEFAULT;
+					iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+					index->indexParams = lappend(index->indexParams, iparam);
+				}
+			}
+		}
+		else
+		{
+			/* create no index, just notice that an appropriate index is necessary for efficient IVM */
+			ereport(NOTICE,
+					(errmsg("could not create an index on materialized view \"%s\" automatically",
+							RelationGetRelationName(matviewRel)),
+					 errdetail("This target list does not have all the primary key columns, "
+							   "or this view does not contain DISTINCT clause."),
+					 errhint("Create an index on the materialized view for efficient incremental maintenance.")));
+			return;
+		}
+	}
+
+	/* If we have a compatible index, we don't need to create another. */
+	foreach(indexoidscan, indexoidlist)
+	{
+		Oid			indexoid = lfirst_oid(indexoidscan);
+		Relation	indexRel;
+		bool		hasCompatibleIndex = false;
+
+		indexRel = index_open(indexoid, AccessShareLock);
+
+		if (CheckIndexCompatible(indexRel->rd_id,
+								index->accessMethod,
+								index->indexParams,
+								index->excludeOpNames))
+			hasCompatibleIndex = true;
+
+		index_close(indexRel, AccessShareLock);
+
+		if (hasCompatibleIndex)
+			return;
+	}
+
+	address = DefineIndex(RelationGetRelid(matviewRel),
+						  index,
+						  InvalidOid,
+						  InvalidOid,
+						  InvalidOid,
+						  false, true, false, false, true);
+
+	ereport(NOTICE,
+			(errmsg("created index \"%s\" on materialized view \"%s\"",
+					idxname, RelationGetRelationName(matviewRel))));
+
+	/*
+	 * Make dependencies so that the index is dropped if any base tables's
+	 * primary key is dropped.
+	 */
+	foreach(lc, constraintList)
+	{
+		Oid constraintOid = lfirst_oid(lc);
+		ObjectAddress	refaddr;
+
+		refaddr.classId = ConstraintRelationId;
+		refaddr.objectId = constraintOid;
+		refaddr.objectSubId = 0;
+
+		recordDependencyOn(&address, &refaddr, DEPENDENCY_NORMAL);
+	}
+}
+
+
+/*
+ * get_primary_key_attnos_from_query
+ *
+ * Identify the columns in base tables' primary keys in the target list.
+ *
+ * Returns a Bitmapset of the column attnos of the primary key's columns of
+ * tables that used in the query.  The attnos are offset by
+ * FirstLowInvalidHeapAttributeNumber as same as get_primary_key_attnos.
+ *
+ * If any table has no primary key or any primary key's columns is not in
+ * the target list, return NULL.  We also return NULL if any pkey constraint
+ * is deferrable.
+ *
+ * constraintList is set to a list of the OIDs of the pkey constraints.
+ */
+static Bitmapset *
+get_primary_key_attnos_from_query(Query *query, List **constraintList, bool is_create)
+{
+	List *key_attnos_list = NIL;
+	ListCell *lc;
+	int i;
+	Bitmapset *keys = NULL;
+	Relids	rels_in_from;
+	PlannerInfo root;
+
+
+	/*
+	 * Collect primary key attributes from all tables used in query. The key attributes
+	 * sets for each table are stored in key_attnos_list in order by RTE index.
+	 */
+	i = 1;
+	foreach(lc, query->rtable)
+	{
+		RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+		Bitmapset *key_attnos;
+		bool	has_pkey = true;
+		Index	first_rtindex = is_create ? 1 : PRS2_NEW_VARNO + 1;
+
+		/* skip NEW/OLD entries */
+		if (i >= first_rtindex)
+		{
+			/* for tables, call get_primary_key_attnos */
+			if (r->rtekind == RTE_RELATION)
+			{
+				Oid constraintOid;
+				key_attnos = get_primary_key_attnos(r->relid, false, &constraintOid);
+				*constraintList = lappend_oid(*constraintList, constraintOid);
+				has_pkey = (key_attnos != NULL);
+			}
+			/* for other RTEs, store NULL into key_attnos_list */
+			else
+				key_attnos = NULL;
+		}
+		else
+			key_attnos = NULL;
+
+		/*
+		 * If any table or subquery has no primary key or its pkey constraint is deferrable,
+		 * we cannot get key attributes for this query, so return NULL.
+		 */
+		if (!has_pkey)
+			return NULL;
+
+		key_attnos_list = lappend(key_attnos_list, key_attnos);
+		i++;
+	}
+
+	/* Collect key attributes appearing in the target list */
+	i = 1;
+	foreach(lc, query->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) flatten_join_alias_vars(query, lfirst(lc));
+
+		if (IsA(tle->expr, Var))
+		{
+			Var *var = (Var*) tle->expr;
+			Bitmapset *attnos = list_nth(key_attnos_list, var->varno - 1);
+
+			/* check if this attribute is from a base table's primary key */
+			if (bms_is_member(var->varattno - FirstLowInvalidHeapAttributeNumber, attnos))
+			{
+				/*
+				 * Remove found key attributes from key_attnos_list, and add this
+				 * to the result list.
+				 */
+				bms_del_member(attnos, var->varattno - FirstLowInvalidHeapAttributeNumber);
+				keys = bms_add_member(keys, i - FirstLowInvalidHeapAttributeNumber);
+			}
+		}
+		i++;
+	}
+
+	/* Collect relations appearing in the FROM clause */
+	rels_in_from = pull_varnos_of_level(&root, (Node *)query->jointree, 0);
+
+	/*
+	 * Check if all key attributes of relations in FROM are appearing in the target
+	 * list.  If an attribute remains in key_attnos_list in spite of the table is used
+	 * in FROM clause, the target is missing this key attribute, so we return NULL.
+	 */
+	i = 1;
+	foreach(lc, key_attnos_list)
+	{
+		Bitmapset *bms = (Bitmapset *)lfirst(lc);
+		if (!bms_is_empty(bms) && bms_is_member(i, rels_in_from))
+			return NULL;
+		i++;
+	}
+
+	return keys;
+}
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index cd30f15eba..a94b8ffd7d 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -36,6 +36,7 @@
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
+#include "commands/matview.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
@@ -1055,6 +1056,45 @@ DefineIndex(Oid relationId,
 	safe_index = indexInfo->ii_Expressions == NIL &&
 		indexInfo->ii_Predicate == NIL;
 
+	/*
+	 * We disallow unique indexes on IVM columns of IMMVs.
+	 */
+	if (RelationIsIVM(rel) && stmt->unique)
+	{
+		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
+		{
+			AttrNumber	attno = indexInfo->ii_IndexAttrNumbers[i];
+			if (attno > 0)
+			{
+				char *name = NameStr(TupleDescAttr(rel->rd_att, attno - 1)->attname);
+				if (name && isIvmName(name))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique index creation on IVM columns is not supported")));
+			}
+		}
+
+		if (indexInfo->ii_Expressions)
+		{
+			Bitmapset  *indexattrs = NULL;
+			int			varno = -1;
+
+			pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
+
+			while ((varno = bms_next_member(indexattrs, varno)) >= 0)
+			{
+				int attno = varno + FirstLowInvalidHeapAttributeNumber;
+				char *name = NameStr(TupleDescAttr(rel->rd_att, attno - 1)->attname);
+				if (name && isIvmName(name))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique index creation on IVM columns is not supported")));
+			}
+
+		}
+	}
+
+
 	/*
 	 * Report index creation if appropriate (delay this till after most of the
 	 * error checks)
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 9ab248d25e..cb713328a0 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -25,26 +25,48 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "commands/cluster.h"
+#include "commands/defrem.h"
 #include "commands/matview.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
+#include "commands/createas.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "executor/tstoreReceiver.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
+#include "parser/parser.h"
+#include "parser/parsetree.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_func.h"
 #include "parser/parse_relation.h"
+#include "parser/parse_type.h"
 #include "pgstat.h"
 #include "rewrite/rewriteHandler.h"
+#include "rewrite/rewriteManip.h"
+#include "rewrite/rowsecurity.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
 
 
 typedef struct
@@ -58,6 +80,50 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_transientrel;
 
+#define MV_INIT_QUERYHASHSIZE	16
+
+/*
+ * MV_TriggerHashEntry
+ *
+ * Hash entry for base tables on which IVM trigger is invoked
+ */
+typedef struct MV_TriggerHashEntry
+{
+	Oid	matview_id;			/* OID of the materialized view */
+	int	before_trig_count;	/* count of before triggers invoked */
+	int	after_trig_count;	/* count of after triggers invoked */
+
+	TransactionId	xid;	/* Transaction id before the first table is modified*/
+	CommandId		cid;	/* Command id before the first table is modified */
+
+	List   *tables;		/* List of MV_TriggerTable */
+	bool	has_old;	/* tuples are deleted from any table? */
+	bool	has_new;	/* tuples are inserted into any table? */
+} MV_TriggerHashEntry;
+
+/*
+ * MV_TriggerTable
+ *
+ * IVM related data for tables on which the trigger is invoked.
+ */
+typedef struct MV_TriggerTable
+{
+	Oid		table_id;			/* OID of the modified table */
+	List   *old_tuplestores;	/* tuplestores for deleted tuples */
+	List   *new_tuplestores;	/* tuplestores for inserted tuples */
+	List   *old_rtes;			/* RTEs of ENRs for old_tuplestores*/
+	List   *new_rtes;			/* RTEs of ENRs for new_tuplestores */
+
+	List   *rte_indexes;		/* List of RTE index of the modified table */
+	RangeTblEntry *original_rte;	/* the original RTE saved before rewriting query */
+} MV_TriggerTable;
+
+static HTAB *mv_trigger_info = NULL;
+
+/* ENR name for materialized view delta */
+#define NEW_DELTA_ENRNAME "new_delta"
+#define OLD_DELTA_ENRNAME "old_delta"
+
 static int	matview_maintenance_depth = 0;
 
 static void transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
@@ -65,7 +131,9 @@ static bool transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void transientrel_shutdown(DestReceiver *self);
 static void transientrel_destroy(DestReceiver *self);
 static uint64 refresh_matview_datafill(DestReceiver *dest, Query *query,
-									   const char *queryString);
+						 QueryEnvironment *queryEnv,
+						 TupleDesc *resultTupleDesc,
+						 const char *queryString);
 static char *make_temptable_name_n(char *tempname, int n);
 static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 								   int save_sec_context);
@@ -73,6 +141,45 @@ static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersist
 static bool is_usable_unique_index(Relation indexRel);
 static void OpenMatViewIncrementalMaintenance(void);
 static void CloseMatViewIncrementalMaintenance(void);
+static Query *get_matview_query(Relation matviewRel);
+
+static Query *rewrite_query_for_preupdate_state(Query *query, List *tables,
+								  TransactionId xid, CommandId cid,
+								  ParseState *pstate);
+static void register_delta_ENRs(ParseState *pstate, Query *query, List *tables);
+static char *make_delta_enr_name(const char *prefix, Oid relid, int count);
+static RangeTblEntry *get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
+				 TransactionId xid, CommandId cid,
+				 QueryEnvironment *queryEnv);
+static RangeTblEntry *union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
+		   QueryEnvironment *queryEnv);
+static Query *rewrite_query_for_distinct(Query *query, ParseState *pstate);
+
+static void calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
+			DestReceiver *dest_old, DestReceiver *dest_new,
+			TupleDesc *tupdesc_old, TupleDesc *tupdesc_new,
+			QueryEnvironment *queryEnv);
+static Query *rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte_index);
+
+static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
+			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
+			Query *query, bool use_count, char *count_colname);
+static void apply_old_delta(const char *matviewname, const char *deltaname_old,
+				List *keys);
+static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
+				List *keys, const char *count_colname);
+static void apply_new_delta(const char *matviewname, const char *deltaname_new,
+				StringInfo target_list);
+static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
+				List *keys, StringInfo target_list, const char* count_colname);
+static char *get_matching_condition_string(List *keys);
+static void generate_equal(StringInfo querybuf, Oid opttype,
+			   const char *leftop, const char *rightop);
+
+static void mv_InitHashTables(void);
+static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry);
+
+static List *get_securityQuals(Oid relId, int rt_index, Query *query);
 
 /*
  * SetMatViewPopulatedState
@@ -114,6 +221,46 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
 	CommandCounterIncrement();
 }
 
+/*
+ * SetMatViewIVMState
+ *		Mark a materialized view as IVM, or not.
+ *
+ * NOTE: caller must be holding an appropriate lock on the relation.
+ */
+void
+SetMatViewIVMState(Relation relation, bool newstate)
+{
+	Relation	pgrel;
+	HeapTuple	tuple;
+
+	Assert(relation->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Update relation's pg_class entry.  Crucial side-effect: other backends
+	 * (and this one too!) are sent SI message to make them rebuild relcache
+	 * entries.
+	 */
+	pgrel = table_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(relation)));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u",
+			 RelationGetRelid(relation));
+
+	((Form_pg_class) GETSTRUCT(tuple))->relisivm = newstate;
+
+	CatalogTupleUpdate(pgrel, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	table_close(pgrel, RowExclusiveLock);
+
+	/*
+	 * Advance command counter to make the updated pg_class row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+}
+
 /*
  * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command
  *
@@ -140,9 +287,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 {
 	Oid			matviewOid;
 	Relation	matviewRel;
-	RewriteRule *rule;
-	List	   *actions;
 	Query	   *dataQuery;
+	Query	   *viewQuery;
 	Oid			tableSpace;
 	Oid			relowner;
 	Oid			OIDNewHeap;
@@ -155,6 +301,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 	int			save_sec_context;
 	int			save_nestlevel;
 	ObjectAddress address;
+	bool oldPopulated;
 
 	/* Determine strength of lock needed. */
 	concurrent = stmt->concurrent;
@@ -167,6 +314,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										  lockmode, 0,
 										  RangeVarCallbackOwnsTable, NULL);
 	matviewRel = table_open(matviewOid, NoLock);
+	oldPopulated = RelationIsPopulated(matviewRel);
 
 	/* Make sure it is a materialized view. */
 	if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW)
@@ -188,32 +336,14 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 				 errmsg("%s and %s options cannot be used together",
 						"CONCURRENTLY", "WITH NO DATA")));
 
-	/*
-	 * Check that everything is correct for a refresh. Problems at this point
-	 * are internal errors, so elog is sufficient.
-	 */
-	if (matviewRel->rd_rel->relhasrules == false ||
-		matviewRel->rd_rules->numLocks < 1)
-		elog(ERROR,
-			 "materialized view \"%s\" is missing rewrite information",
-			 RelationGetRelationName(matviewRel));
-
-	if (matviewRel->rd_rules->numLocks > 1)
-		elog(ERROR,
-			 "materialized view \"%s\" has too many rules",
-			 RelationGetRelationName(matviewRel));
 
-	rule = matviewRel->rd_rules->rules[0];
-	if (rule->event != CMD_SELECT || !(rule->isInstead))
-		elog(ERROR,
-			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
-			 RelationGetRelationName(matviewRel));
+	viewQuery = get_matview_query(matviewRel);
 
-	actions = rule->actions;
-	if (list_length(actions) != 1)
-		elog(ERROR,
-			 "the rule for materialized view \"%s\" is not a single action",
-			 RelationGetRelationName(matviewRel));
+	/* For IMMV, we need to rewrite matview query */
+	if (!stmt->skipData && RelationIsIVM(matviewRel))
+		dataQuery = rewriteQueryForIMMV(viewQuery,NIL);
+	else
+		dataQuery = viewQuery;
 
 	/*
 	 * Check that there is a unique index with no WHERE clause on one or more
@@ -248,12 +378,6 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 					 errhint("Create a unique index with no WHERE clause on one or more columns of the materialized view.")));
 	}
 
-	/*
-	 * The stored query was rewritten at the time of the MV definition, but
-	 * has not been scribbled on by the planner.
-	 */
-	dataQuery = linitial_node(Query, actions);
-
 	/*
 	 * Check for active uses of the relation in the current transaction, such
 	 * as open scans.
@@ -294,6 +418,74 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 		relpersistence = matviewRel->rd_rel->relpersistence;
 	}
 
+	/* delete IMMV triggers. */
+	if (RelationIsIVM(matviewRel) && stmt->skipData )
+	{
+		Relation	tgRel;
+		Relation	depRel;
+		ScanKeyData key;
+		SysScanDesc scan;
+		HeapTuple	tup;
+		ObjectAddresses *immv_triggers;
+
+		immv_triggers = new_object_addresses();
+
+		tgRel = table_open(TriggerRelationId, RowExclusiveLock);
+		depRel = table_open(DependRelationId, RowExclusiveLock);
+
+		/* search triggers that depends on IMMV. */
+		ScanKeyInit(&key,
+					Anum_pg_depend_refobjid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(matviewOid));
+		scan = systable_beginscan(depRel, DependReferenceIndexId, true,
+								  NULL, 1, &key);
+		while ((tup = systable_getnext(scan)) != NULL)
+		{
+			ObjectAddress obj;
+			Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
+
+			if (foundDep->classid == TriggerRelationId)
+			{
+				HeapTuple	tgtup;
+				ScanKeyData tgkey[1];
+				SysScanDesc tgscan;
+				Form_pg_trigger tgform;
+
+				/* Find the trigger name. */
+				ScanKeyInit(&tgkey[0],
+							Anum_pg_trigger_oid,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(foundDep->objid));
+
+				tgscan = systable_beginscan(tgRel, TriggerOidIndexId, true,
+											NULL, 1, tgkey);
+				tgtup = systable_getnext(tgscan);
+				if (!HeapTupleIsValid(tgtup))
+					elog(ERROR, "could not find tuple for immv trigger %u", foundDep->objid);
+
+				tgform = (Form_pg_trigger) GETSTRUCT(tgtup);
+
+				/* If trigger is created by IMMV, delete it. */
+				if (strncmp(NameStr(tgform->tgname), "IVM_trigger_", 12) == 0)
+				{
+					obj.classId = foundDep->classid;
+					obj.objectId = foundDep->objid;
+					obj.objectSubId = foundDep->refobjsubid;
+					add_exact_object_address(&obj, immv_triggers);
+				}
+				systable_endscan(tgscan);
+			}
+		}
+		systable_endscan(scan);
+
+		performMultipleDeletions(immv_triggers, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
+
+		table_close(depRel, RowExclusiveLock);
+		table_close(tgRel, RowExclusiveLock);
+		free_object_addresses(immv_triggers);
+	}
+
 	/*
 	 * Create the transient table that will receive the regenerated data. Lock
 	 * it against access by any other process until commit (by which time it
@@ -313,7 +505,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 
 	/* Generate the data, if wanted. */
 	if (!stmt->skipData)
-		processed = refresh_matview_datafill(dest, dataQuery, queryString);
+		processed = refresh_matview_datafill(dest, dataQuery, NULL, NULL, queryString);
 
 	/* Make the matview match the newly generated data. */
 	if (concurrent)
@@ -348,6 +540,12 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 			pgstat_count_heap_insert(matviewRel, processed);
 	}
 
+	if (!stmt->skipData && RelationIsIVM(matviewRel) && !oldPopulated)
+	{
+		CreateIndexOnIMMV(viewQuery, matviewRel, false);
+		CreateIvmTriggersOnBaseTables(viewQuery, matviewOid, false);
+	}
+
 	table_close(matviewRel, NoLock);
 
 	/* Roll back any GUC changes */
@@ -382,6 +580,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  */
 static uint64
 refresh_matview_datafill(DestReceiver *dest, Query *query,
+						 QueryEnvironment *queryEnv,
+						 TupleDesc *resultTupleDesc,
 						 const char *queryString)
 {
 	List	   *rewritten;
@@ -418,7 +618,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 	/* Create a QueryDesc, redirecting output to our tuple receiver */
 	queryDesc = CreateQueryDesc(plan, queryString,
 								GetActiveSnapshot(), InvalidSnapshot,
-								dest, NULL, NULL, 0);
+								dest, NULL, queryEnv ? queryEnv: NULL, 0);
 
 	/* call ExecutorStart to prepare the plan for execution */
 	ExecutorStart(queryDesc, 0);
@@ -428,6 +628,9 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 
 	processed = queryDesc->estate->es_processed;
 
+	if (resultTupleDesc)
+		*resultTupleDesc = CreateTupleDescCopy(queryDesc->tupDesc);
+
 	/* and clean up */
 	ExecutorFinish(queryDesc);
 	ExecutorEnd(queryDesc);
@@ -942,3 +1145,1307 @@ CloseMatViewIncrementalMaintenance(void)
 	matview_maintenance_depth--;
 	Assert(matview_maintenance_depth >= 0);
 }
+
+/*
+ * get_matview_query - get the Query from a matview's _RETURN rule.
+ */
+static Query *
+get_matview_query(Relation matviewRel)
+{
+	RewriteRule *rule;
+	List * actions;
+
+	/*
+	 * Check that everything is correct for a refresh. Problems at this point
+	 * are internal errors, so elog is sufficient.
+	 */
+	if (matviewRel->rd_rel->relhasrules == false ||
+		matviewRel->rd_rules->numLocks < 1)
+		elog(ERROR,
+			 "materialized view \"%s\" is missing rewrite information",
+			 RelationGetRelationName(matviewRel));
+
+	if (matviewRel->rd_rules->numLocks > 1)
+		elog(ERROR,
+			 "materialized view \"%s\" has too many rules",
+			 RelationGetRelationName(matviewRel));
+
+	rule = matviewRel->rd_rules->rules[0];
+	if (rule->event != CMD_SELECT || !(rule->isInstead))
+		elog(ERROR,
+			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
+			 RelationGetRelationName(matviewRel));
+
+	actions = rule->actions;
+	if (list_length(actions) != 1)
+		elog(ERROR,
+			 "the rule for materialized view \"%s\" is not a single action",
+			 RelationGetRelationName(matviewRel));
+
+	/*
+	 * The stored query was rewritten at the time of the MV definition, but
+	 * has not been scribbled on by the planner.
+	 */
+	return linitial_node(Query, actions);
+}
+
+
+/* ----------------------------------------------------
+ *		Incremental View Maintenance routines
+ * ---------------------------------------------------
+ */
+
+/*
+ * IVM_immediate_before
+ *
+ * IVM trigger function invoked before base table is modified. If this is
+ * invoked firstly in the same statement, we save the transaction id and the
+ * command id at that time.
+ */
+Datum
+IVM_immediate_before(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	char	   *matviewOid_text = trigdata->tg_trigger->tgargs[0];
+	char	   *ex_lock_text = trigdata->tg_trigger->tgargs[1];
+	Oid			matviewOid;
+	MV_TriggerHashEntry *entry;
+	bool	found;
+	bool	ex_lock;
+
+	matviewOid = DatumGetObjectId(DirectFunctionCall1(oidin, CStringGetDatum(matviewOid_text)));
+	ex_lock = DatumGetBool(DirectFunctionCall1(boolin, CStringGetDatum(ex_lock_text)));
+
+	/* If the view has more than one tables, we have to use an exclusive lock. */
+	if (ex_lock)
+	{
+		/*
+		 * Wait for concurrent transactions which update this materialized view at
+		 * READ COMMITED. This is needed to see changes committed in other
+		 * transactions. No wait and raise an error at REPEATABLE READ or
+		 * SERIALIZABLE to prevent update anomalies of matviews.
+		 * XXX: dead-lock is possible here.
+		 */
+		if (!IsolationUsesXactSnapshot())
+			LockRelationOid(matviewOid, ExclusiveLock);
+		else if (!ConditionalLockRelationOid(matviewOid, ExclusiveLock))
+		{
+			/* try to throw error by name; relation could be deleted... */
+			char	   *relname = get_rel_name(matviewOid);
+
+			if (!relname)
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						errmsg("could not obtain lock on materialized view during incremental maintenance")));
+
+			ereport(ERROR,
+					(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+					errmsg("could not obtain lock on materialized view \"%s\" during incremental maintenance",
+							relname)));
+		}
+	}
+	else
+		LockRelationOid(matviewOid, RowExclusiveLock);
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_trigger_info)
+		mv_InitHashTables();
+
+	entry = (MV_TriggerHashEntry *) hash_search(mv_trigger_info,
+											  (void *) &matviewOid,
+											  HASH_ENTER, &found);
+
+	/* On the first BEFORE to update the view, initialize trigger data */
+	if (!found)
+	{
+		Snapshot snapshot = GetActiveSnapshot();
+
+		entry->matview_id = matviewOid;
+		entry->before_trig_count = 0;
+		entry->after_trig_count = 0;
+		entry->xid = GetCurrentTransactionId();
+		entry->cid = snapshot->curcid;
+		entry->tables = NIL;
+		entry->has_old = false;
+		entry->has_new = false;
+	}
+
+	entry->before_trig_count++;
+
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * IVM_immediate_before
+ *
+ * IVM trigger function invoked after base table is modified.
+ * For each table, tuplestores of transition tables are collected.
+ * and after the last modification
+ */
+Datum
+IVM_immediate_maintenance(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	Relation	rel;
+	Oid			relid;
+	Oid			matviewOid;
+	Query	   *query;
+	Query	   *rewritten = NULL;
+	char	   *matviewOid_text = trigdata->tg_trigger->tgargs[0];
+	Relation	matviewRel;
+	int old_depth = matview_maintenance_depth;
+
+	Oid			relowner;
+	Tuplestorestate *old_tuplestore = NULL;
+	Tuplestorestate *new_tuplestore = NULL;
+	DestReceiver *dest_new = NULL, *dest_old = NULL;
+	Oid			save_userid;
+	int			save_sec_context;
+	int			save_nestlevel;
+
+	MV_TriggerHashEntry *entry;
+	MV_TriggerTable		*table;
+	bool	found;
+
+	ParseState		 *pstate;
+	QueryEnvironment *queryEnv = create_queryEnv();
+	MemoryContext	oldcxt;
+	ListCell   *lc;
+	int			i;
+
+
+	/* Create a ParseState for rewriting the view definition query */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	rel = trigdata->tg_relation;
+	relid = rel->rd_id;
+
+	matviewOid = DatumGetObjectId(DirectFunctionCall1(oidin, CStringGetDatum(matviewOid_text)));
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_trigger_info)
+		mv_InitHashTables();
+
+	/* get the entry for this materialized view */
+	entry = (MV_TriggerHashEntry *) hash_search(mv_trigger_info,
+											  (void *) &matviewOid,
+											  HASH_FIND, &found);
+	Assert (found && entry != NULL);
+	entry->after_trig_count++;
+
+	/* search the entry for the modified table and create new entry if not found */
+	found = false;
+	foreach(lc, entry->tables)
+	{
+		table = (MV_TriggerTable *) lfirst(lc);
+		if (table->table_id == relid)
+		{
+			found = true;
+			break;
+		}
+	}
+	if (!found)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		table = (MV_TriggerTable *) palloc0(sizeof(MV_TriggerTable));
+		table->table_id = relid;
+		table->old_tuplestores = NIL;
+		table->new_tuplestores = NIL;
+		table->old_rtes = NIL;
+		table->new_rtes = NIL;
+		table->rte_indexes = NIL;
+		entry->tables = lappend(entry->tables, table);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* Save the transition tables and make a request to not free immediately */
+	if (trigdata->tg_oldtable)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+		table->old_tuplestores = lappend(table->old_tuplestores, trigdata->tg_oldtable);
+		entry->has_old = true;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (trigdata->tg_newtable)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+		table->new_tuplestores = lappend(table->new_tuplestores, trigdata->tg_newtable);
+		entry->has_new = true;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (entry->has_new || entry->has_old)
+	{
+		CmdType cmd;
+
+		if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+			cmd = CMD_INSERT;
+		else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+			cmd = CMD_DELETE;
+		else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+			cmd = CMD_UPDATE;
+		else
+			elog(ERROR,"unsupported trigger type");
+
+		/* Prolong lifespan of transition tables to the end of the last AFTER trigger */
+		SetTransitionTablePreserved(relid, cmd);
+	}
+
+
+	/* If this is not the last AFTER trigger call, immediately exit. */
+	Assert (entry->before_trig_count >= entry->after_trig_count);
+	if (entry->before_trig_count != entry->after_trig_count)
+		return PointerGetDatum(NULL);
+
+	/*
+	 * If this is the last AFTER trigger call, continue and update the view.
+	 */
+
+	/*
+	 * Advance command counter to make the updated base table row locally
+	 * visible.
+	 */
+	CommandCounterIncrement();
+
+	matviewRel = table_open(matviewOid, NoLock);
+
+	/* get view query*/
+	query = get_matview_query(matviewRel);
+
+	/* Make sure it is a materialized view. */
+	Assert(matviewRel->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Get and push the latast snapshot to see any changes which is committed
+	 * during waiting in other transactions at READ COMMITTED level.
+	 */
+	PushActiveSnapshot(GetTransactionSnapshot());
+
+	/*
+	 * Check for active uses of the relation in the current transaction, such
+	 * as open scans.
+	 *
+	 * NB: We count on this to protect us against problems with refreshing the
+	 * data using TABLE_INSERT_FROZEN.
+	 */
+	CheckTableNotInUse(matviewRel, "refresh a materialized view incrementally");
+
+	/*
+	 * Switch to the owner's userid, so that any functions are run as that
+	 * user.  Also arrange to make GUC variable changes local to this command.
+	 * We will switch modes when we are about to execute user code.
+	 */
+	relowner = matviewRel->rd_rel->relowner;
+	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+	SetUserIdAndSecContext(relowner,
+						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+	save_nestlevel = NewGUCNestLevel();
+
+	/*
+	 * rewrite query for calculating deltas
+	 */
+
+	rewritten = copyObject(query);
+
+	/* Replace resnames in a target list with materialized view's attnames */
+	i = 0;
+	foreach (lc, rewritten->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char *resname = NameStr(attr->attname);
+
+		tle->resname = pstrdup(resname);
+		i++;
+	}
+
+	/* Set all tables in the query to pre-update state */
+	rewritten = rewrite_query_for_preupdate_state(rewritten, entry->tables,
+												  entry->xid, entry->cid,
+												  pstate);
+	/* Rewrite for DISTINCT clause */
+	rewritten = rewrite_query_for_distinct(rewritten, pstate);
+
+	/* Create tuplestores to store view deltas */
+	if (entry->has_old)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		old_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+		dest_old = CreateDestReceiver(DestTuplestore);
+		SetTuplestoreDestReceiverParams(dest_old,
+									old_tuplestore,
+									TopTransactionContext,
+									false,
+									NULL,
+									NULL);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+	if (entry->has_new)
+	{
+		oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+		new_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+		dest_new = CreateDestReceiver(DestTuplestore);
+		SetTuplestoreDestReceiverParams(dest_new,
+									new_tuplestore,
+									TopTransactionContext,
+									false,
+									NULL,
+									NULL);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* for all modified tables */
+	foreach(lc, entry->tables)
+	{
+		ListCell *lc2;
+
+		table = (MV_TriggerTable *) lfirst(lc);
+
+		/* loop for self-join */
+		foreach(lc2, table->rte_indexes)
+		{
+			int	rte_index = lfirst_int(lc2);
+			TupleDesc		tupdesc_old;
+			TupleDesc		tupdesc_new;
+			bool	use_count = false;
+			char   *count_colname = NULL;
+
+			count_colname = pstrdup("__ivm_count__");
+
+			if (query->distinctClause)
+				use_count = true;
+
+			/* calculate delta tables */
+			calc_delta(table, rte_index, rewritten, dest_old, dest_new,
+					   &tupdesc_old, &tupdesc_new, queryEnv);
+
+			/* Set the table in the query to post-update state */
+			rewritten = rewrite_query_for_postupdate_state(rewritten, table, rte_index);
+
+			PG_TRY();
+			{
+				/* apply the delta tables to the materialized view */
+				apply_delta(matviewOid, old_tuplestore, new_tuplestore,
+							tupdesc_old, tupdesc_new, query, use_count,
+							count_colname);
+			}
+			PG_CATCH();
+			{
+				matview_maintenance_depth = old_depth;
+				PG_RE_THROW();
+			}
+			PG_END_TRY();
+
+			/* clear view delta tuplestores */
+			if (old_tuplestore)
+				tuplestore_clear(old_tuplestore);
+			if (new_tuplestore)
+				tuplestore_clear(new_tuplestore);
+		}
+	}
+
+	/* Clean up hash entry and delete tuplestores */
+	clean_up_IVM_hash_entry(entry);
+	if (old_tuplestore)
+	{
+		dest_old->rDestroy(dest_old);
+		tuplestore_end(old_tuplestore);
+	}
+	if (new_tuplestore)
+	{
+		dest_new->rDestroy(dest_new);
+		tuplestore_end(new_tuplestore);
+	}
+
+	/* Pop the original snapshot. */
+	PopActiveSnapshot();
+
+	table_close(matviewRel, NoLock);
+
+	/* Roll back any GUC changes */
+	AtEOXact_GUC(false, save_nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(save_userid, save_sec_context);
+
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * rewrite_query_for_preupdate_state
+ *
+ * Rewrite the query so that base tables' RTEs will represent "pre-update"
+ * state of tables. This is necessary to calculate view delta after multiple
+ * tables are modified. xid and cid are the transaction id and command id
+ * before the first table was modified.
+ */
+static Query*
+rewrite_query_for_preupdate_state(Query *query, List *tables,
+								  TransactionId xid, CommandId cid,
+								  ParseState *pstate)
+{
+	ListCell *lc;
+	int num_rte = list_length(query->rtable);
+	int i;
+
+
+	/* register delta ENRs */
+	register_delta_ENRs(pstate, query, tables);
+
+	/* XXX: Is necessary? Is this right timing? */
+	AcquireRewriteLocks(query, true, false);
+
+	i = 1;
+	foreach(lc, query->rtable)
+	{
+		RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
+
+		ListCell *lc2;
+		foreach(lc2, tables)
+		{
+			MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc2);
+			/*
+			 * if the modified table is found then replace the original RTE with
+			 * "pre-state" RTE and append its index to the list.
+			 */
+			if (r->relid == table->table_id)
+			{
+				lfirst(lc) = get_prestate_rte(r, table, xid, cid, pstate->p_queryEnv);
+				table->rte_indexes = lappend_int(table->rte_indexes, i);
+				break;
+			}
+		}
+
+		/* finish the loop if we processed all RTE included in the original query */
+		if (i++ >= num_rte)
+			break;
+	}
+
+	return query;
+}
+
+/*
+ * register_delta_ENRs
+ *
+ * For all modified tables, make ENRs for their transition tables
+ * and register them to the queryEnv. ENR's RTEs are also appended
+ * into the list in query tree.
+ */
+static void
+register_delta_ENRs(ParseState *pstate, Query *query, List *tables)
+{
+	QueryEnvironment *queryEnv = pstate->p_queryEnv;
+	ListCell *lc;
+	RangeTblEntry	*rte;
+
+	foreach(lc, tables)
+	{
+		MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc);
+		ListCell *lc2;
+		int count;
+
+		count = 0;
+		foreach(lc2, table->old_tuplestores)
+		{
+			Tuplestorestate *oldtable = (Tuplestorestate *) lfirst(lc2);
+			EphemeralNamedRelation enr =
+				palloc(sizeof(EphemeralNamedRelationData));
+			ParseNamespaceItem *nsitem;
+
+			enr->md.name = make_delta_enr_name("old", table->table_id, count);
+			enr->md.reliddesc = table->table_id;
+			enr->md.tupdesc = NULL;
+			enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+			enr->md.enrtuples = tuplestore_tuple_count(oldtable);
+			enr->reldata = oldtable;
+			register_ENR(queryEnv, enr);
+
+			nsitem = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+			rte = nsitem->p_rte;
+			/* if base table has RLS, set security condition to enr */
+			rte->securityQuals = get_securityQuals(table->table_id, list_length(query->rtable) + 1, query);
+
+			query->rtable = lappend(query->rtable, rte);
+			table->old_rtes = lappend(table->old_rtes, rte);
+
+			count++;
+		}
+
+		count = 0;
+		foreach(lc2, table->new_tuplestores)
+		{
+			Tuplestorestate *newtable = (Tuplestorestate *) lfirst(lc2);
+			EphemeralNamedRelation enr =
+				palloc(sizeof(EphemeralNamedRelationData));
+			ParseNamespaceItem *nsitem;
+
+			enr->md.name = make_delta_enr_name("new", table->table_id, count);
+			enr->md.reliddesc = table->table_id;
+			enr->md.tupdesc = NULL;
+			enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+			enr->md.enrtuples = tuplestore_tuple_count(newtable);
+			enr->reldata = newtable;
+			register_ENR(queryEnv, enr);
+
+			nsitem = addRangeTableEntryForENR(pstate, makeRangeVar(NULL, enr->md.name, -1), true);
+			rte = nsitem->p_rte;
+			/* if base table has RLS, set security condition to enr*/
+			rte->securityQuals = get_securityQuals(table->table_id, list_length(query->rtable) + 1, query);
+
+			query->rtable = lappend(query->rtable, rte);
+			table->new_rtes = lappend(table->new_rtes, rte);
+
+			count++;
+		}
+	}
+}
+
+/*
+ * get_prestate_rte
+ *
+ * Rewrite RTE of the modified table to a subquery which represents
+ * "pre-state" table. The original RTE is saved in table->rte_original.
+ */
+static RangeTblEntry*
+get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
+				 TransactionId xid, CommandId cid,
+				 QueryEnvironment *queryEnv)
+{
+	StringInfoData str;
+	RawStmt *raw;
+	Query *sub;
+	Relation rel;
+	ParseState *pstate;
+	char *relname;
+	int i;
+
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	/*
+	 * We can use NoLock here since AcquireRewriteLocks should
+	 * have locked the rel already.
+	 */
+	rel = table_open(table->table_id, NoLock);
+	relname = quote_qualified_identifier(
+					get_namespace_name(RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+	table_close(rel, NoLock);
+
+	initStringInfo(&str);
+	appendStringInfo(&str,
+		"SELECT t.* FROM %s t"
+		" WHERE (age(t.xmin) - age(%u::text::xid) > 0) OR"
+		" (t.xmin = %u AND t.cmin::text::int < %u)",
+			relname, xid, xid, cid);
+
+	for (i = 0; i < list_length(table->old_tuplestores); i++)
+	{
+		appendStringInfo(&str, " UNION ALL ");
+		appendStringInfo(&str," SELECT * FROM %s",
+			make_delta_enr_name("old", table->table_id, i));
+	}
+
+	raw = (RawStmt*)linitial(raw_parser(str.data, RAW_PARSE_DEFAULT));
+	sub = transformStmt(pstate, raw->stmt);
+
+	/* If this query has setOperations, RTEs in rtables has a subquery which contains ENR */
+	if (sub->setOperations != NULL)
+	{
+		ListCell *lc;
+
+		/* add securityQuals for tuplestores */
+		foreach (lc, sub->rtable)
+		{
+			RangeTblEntry *rte;
+			RangeTblEntry *sub_rte;
+
+			rte = (RangeTblEntry *)lfirst(lc);
+			Assert(rte->subquery != NULL);
+
+			sub_rte = (RangeTblEntry *)linitial(rte->subquery->rtable);
+			if (sub_rte->rtekind == RTE_NAMEDTUPLESTORE)
+				/* rt_index is always 1, bacause subquery has enr_rte only */
+				sub_rte->securityQuals = get_securityQuals(sub_rte->relid, 1, sub);
+		}
+	}
+
+	/* save the original RTE */
+	table->original_rte = copyObject(rte);
+
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = sub;
+	rte->security_barrier = false;
+	/* Clear fields that should not be set in a subquery RTE */
+	rte->relid = InvalidOid;
+	rte->relkind = 0;
+	rte->rellockmode = 0;
+	rte->tablesample = NULL;
+	rte->inh = false;			/* must not be set for a subquery */
+
+	rte->requiredPerms = 0;		/* no permission check on subquery itself */
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->insertedCols = NULL;
+	rte->updatedCols = NULL;
+	rte->extraUpdatedCols = NULL;
+
+	return rte;
+}
+
+/*
+ * make_delta_enr_name
+ *
+ * Make a name for ENR of a transition table from the base table's oid.
+ * prefix will be "new" or "old" depending on its transition table kind..
+ */
+static char*
+make_delta_enr_name(const char *prefix, Oid relid, int count)
+{
+	char buf[NAMEDATALEN];
+	char *name;
+
+	snprintf(buf, NAMEDATALEN, "__ivm_%s_%u_%u", prefix, relid, count);
+	name = pstrdup(buf);
+
+	return name;
+}
+
+/*
+ * union_ENRs
+ *
+ * Make a single table delta by unionning all transition tables of the modified table
+ * whose RTE is specified by
+ */
+static RangeTblEntry*
+union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
+		   QueryEnvironment *queryEnv)
+{
+	StringInfoData str;
+	ParseState	*pstate;
+	RawStmt *raw;
+	Query *sub;
+	int	i;
+	RangeTblEntry *enr_rte;
+
+	/* Create a ParseState for rewriting the view definition query */
+	pstate = make_parsestate(NULL);
+	pstate->p_queryEnv = queryEnv;
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	initStringInfo(&str);
+
+	for (i = 0; i < list_length(enr_rtes); i++)
+	{
+		if (i > 0)
+			appendStringInfo(&str, " UNION ALL ");
+
+		appendStringInfo(&str,
+			" SELECT * FROM %s",
+			make_delta_enr_name(prefix, relid, i));
+	}
+
+	raw = (RawStmt*)linitial(raw_parser(str.data, RAW_PARSE_DEFAULT));
+	sub = transformStmt(pstate, raw->stmt);
+
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = sub;
+	rte->security_barrier = false;
+	/* Clear fields that should not be set in a subquery RTE */
+	rte->relid = InvalidOid;
+	rte->relkind = 0;
+	rte->rellockmode = 0;
+	rte->tablesample = NULL;
+	rte->inh = false;			/* must not be set for a subquery */
+
+	rte->requiredPerms = 0;		/* no permission check on subquery itself */
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->insertedCols = NULL;
+	rte->updatedCols = NULL;
+	rte->extraUpdatedCols = NULL;
+	/* if base table has RLS, set security condition to enr*/
+	enr_rte = (RangeTblEntry *)linitial(sub->rtable);
+	/* rt_index is always 1, bacause subquery has enr_rte only */
+	enr_rte->securityQuals = get_securityQuals(relid, 1, sub);
+
+	return rte;
+}
+
+/*
+ * rewrite_query_for_distinct
+ *
+ * Rewrite query for counting DISTINCT clause.
+ */
+static Query *
+rewrite_query_for_distinct(Query *query, ParseState *pstate)
+{
+	TargetEntry *tle_count;
+	FuncCall *fn;
+	Node *node;
+
+	/* Add count(*) for counting distinct tuples in views */
+	fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+	fn->agg_star = true;
+	if (!query->groupClause && !query->hasAggs)
+		query->groupClause = transformDistinctClause(NULL, &query->targetList, query->sortClause, false);
+
+	node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+	tle_count = makeTargetEntry((Expr *) node,
+								list_length(query->targetList) + 1,
+								pstrdup("__ivm_count__"),
+								false);
+	query->targetList = lappend(query->targetList, tle_count);
+	query->hasAggs = true;
+
+	return query;
+}
+
+/*
+ * calc_delta
+ *
+ * Calculate view deltas generated under the modification of a table specified
+ * by the RTE index.
+ */
+static void
+calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
+			DestReceiver *dest_old, DestReceiver *dest_new,
+			TupleDesc *tupdesc_old, TupleDesc *tupdesc_new,
+			QueryEnvironment *queryEnv)
+{
+	ListCell *lc = list_nth_cell(query->rtable, rte_index - 1);
+	RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+	/* Generate old delta */
+	if (list_length(table->old_rtes) > 0)
+	{
+		/* Replace the modified table with the old delta table and calculate the old view delta. */
+		lfirst(lc) = union_ENRs(rte, table->table_id, table->old_rtes, "old", queryEnv);
+		refresh_matview_datafill(dest_old, query, queryEnv, tupdesc_old, "");
+	}
+
+	/* Generate new delta */
+	if (list_length(table->new_rtes) > 0)
+	{
+		/* Replace the modified table with the new delta table and calculate the new view delta*/
+		lfirst(lc) = union_ENRs(rte, table->table_id, table->new_rtes, "new", queryEnv);
+		refresh_matview_datafill(dest_new, query, queryEnv, tupdesc_new, "");
+	}
+}
+
+/*
+ * rewrite_query_for_postupdate_state
+ *
+ * Rewrite the query so that the specified base table's RTEs will represent
+ * "post-update" state of tables. This is called after the view delta
+ * calculation due to changes on this table finishes.
+ */
+static Query*
+rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte_index)
+{
+	ListCell *lc = list_nth_cell(query->rtable, rte_index - 1);
+
+	/* Retore the original RTE */
+	lfirst(lc) = table->original_rte;
+
+	return query;
+}
+
+/*
+ * apply_delta
+ *
+ * Apply deltas to the materialized view. In outer join cases, this requires
+ * the view maintenance graph.
+ */
+static void
+apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
+			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
+			Query *query, bool use_count, char *count_colname)
+{
+	StringInfoData querybuf;
+	StringInfoData target_list_buf;
+	Relation	matviewRel;
+	char	   *matviewname;
+	ListCell	*lc;
+	int			i;
+	List	   *keys = NIL;
+
+
+	/*
+	 * get names of the materialized view and delta tables
+	 */
+
+	matviewRel = table_open(matviewOid, NoLock);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	/*
+	 * Build parts of the maintenance queries
+	 */
+
+	initStringInfo(&querybuf);
+	initStringInfo(&target_list_buf);
+
+	/* build string of target list */
+	for (i = 0; i < matviewRel->rd_att->natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char   *resname = NameStr(attr->attname);
+
+		if (i != 0)
+			appendStringInfo(&target_list_buf, ", ");
+		appendStringInfo(&target_list_buf, "%s", quote_qualified_identifier(NULL, resname));
+	}
+
+	i = 0;
+	foreach (lc, query->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+
+		i++;
+
+		if (tle->resjunk)
+			continue;
+
+		keys = lappend(keys, attr);
+	}
+
+	/* Start maintaining the materialized view. */
+	OpenMatViewIncrementalMaintenance();
+
+	/* Open SPI context. */
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	/* For tuple deletion */
+	if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
+	{
+		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		int				rc;
+
+		/* convert tuplestores to ENR, and register for SPI */
+		enr->md.name = pstrdup(OLD_DELTA_ENRNAME);
+		enr->md.reliddesc = InvalidOid;
+		enr->md.tupdesc = tupdesc_old;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(old_tuplestores);
+		enr->reldata = old_tuplestores;
+
+		rc = SPI_register_relation(enr);
+		if (rc != SPI_OK_REL_REGISTER)
+			elog(ERROR, "SPI_register failed");
+
+		if (use_count)
+			/* apply old delta and get rows to be recalculated */
+			apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME,
+									   keys, count_colname);
+		else
+			apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys);
+
+	}
+	/* For tuple insertion */
+	if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
+	{
+		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		int rc;
+
+		/* convert tuplestores to ENR, and register for SPI */
+		enr->md.name = pstrdup(NEW_DELTA_ENRNAME);
+		enr->md.reliddesc = InvalidOid;
+		enr->md.tupdesc = tupdesc_new;;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(new_tuplestores);
+		enr->reldata = new_tuplestores;
+
+		rc = SPI_register_relation(enr);
+		if (rc != SPI_OK_REL_REGISTER)
+			elog(ERROR, "SPI_register failed");
+
+		/* apply new delta */
+		if (use_count)
+			apply_new_delta_with_count(matviewname, NEW_DELTA_ENRNAME,
+								keys, &target_list_buf, count_colname);
+		else
+			apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf);
+	}
+
+	/* We're done maintaining the materialized view. */
+	CloseMatViewIncrementalMaintenance();
+
+	table_close(matviewRel, NoLock);
+
+	/* Close SPI context. */
+	if (SPI_finish() != SPI_OK_FINISH)
+		elog(ERROR, "SPI_finish failed");
+}
+
+/*
+ * apply_old_delta_with_count
+ *
+ * Execute a query for applying a delta table given by deltname_old
+ * which contains tuples to be deleted from to a materialized view given by
+ * matviewname.  This is used when counting is required, that is, the view
+ * has aggregate or distinct.
+ */
+static void
+apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
+				List *keys, const char *count_colname)
+{
+	StringInfoData	querybuf;
+	char   *match_cond;
+
+	/* build WHERE condition for searching tuples to be deleted */
+	match_cond = get_matching_condition_string(keys);
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"WITH t AS ("			/* collecting tid of target tuples in the view */
+						"SELECT diff.%s, "			/* count column */
+								"(diff.%s OPERATOR(pg_catalog.=) mv.%s) AS for_dlt, "
+								"mv.ctid "
+						"FROM %s AS mv, %s AS diff "
+						"WHERE %s"					/* tuple matching condition */
+					"), updt AS ("			/* update a tuple if this is not to be deleted */
+						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.-) t.%s "
+						"FROM t WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND NOT for_dlt "
+					")"
+					/* delete a tuple if this is to be deleted */
+					"DELETE FROM %s AS mv USING t "
+					"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt",
+					count_colname,
+					count_colname, count_colname,
+					matviewname, deltaname_old,
+					match_cond,
+					matviewname, count_colname, count_colname, count_colname,
+					matviewname);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_old_delta
+ *
+ * Execute a query for applying a delta table given by deltname_old
+ * which contains tuples to be deleted from to a materialized view given by
+ * matviewname.  This is used when counting is not required.
+ */
+static void
+apply_old_delta(const char *matviewname, const char *deltaname_old,
+				List *keys)
+{
+	StringInfoData	querybuf;
+	StringInfoData	keysbuf;
+	char   *match_cond;
+	ListCell *lc;
+
+	/* build WHERE condition for searching tuples to be deleted */
+	match_cond = get_matching_condition_string(keys);
+
+	/* build string of keys list */
+	initStringInfo(&keysbuf);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char   *resname = NameStr(attr->attname);
+		appendStringInfo(&keysbuf, "%s", quote_qualified_identifier("mv", resname));
+		if (lnext(keys, lc))
+			appendStringInfo(&keysbuf, ", ");
+	}
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+	"DELETE FROM %s WHERE ctid IN ("
+		"SELECT tid FROM (SELECT row_number() over (partition by %s) AS \"__ivm_row_number__\","
+								  "mv.ctid AS tid,"
+								  "diff.\"__ivm_count__\""
+						 "FROM %s AS mv, %s AS diff "
+						 "WHERE %s) v "
+					"WHERE v.\"__ivm_row_number__\" OPERATOR(pg_catalog.<=) v.\"__ivm_count__\")",
+					matviewname,
+					keysbuf.data,
+					matviewname, deltaname_old,
+					match_cond);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_new_delta_with_count
+ *
+ * Execute a query for applying a delta table given by deltname_new
+ * which contains tuples to be inserted into a materialized view given by
+ * matviewname.  This is used when counting is required, that is, the view
+ * has aggregate or distinct. Also, when a table in EXISTS sub queries
+ * is modified.
+ */
+static void
+apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
+				List *keys, StringInfo target_list, const char* count_colname)
+{
+	StringInfoData	querybuf;
+	StringInfoData	returning_keys;
+	ListCell	*lc;
+	char	*match_cond = "";
+
+	/* build WHERE condition for searching tuples to be updated */
+	match_cond = get_matching_condition_string(keys);
+
+	/* build string of keys list */
+	initStringInfo(&returning_keys);
+	if (keys)
+	{
+		foreach (lc, keys)
+		{
+			Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+			char   *resname = NameStr(attr->attname);
+			appendStringInfo(&returning_keys, "%s", quote_qualified_identifier("mv", resname));
+			if (lnext(keys, lc))
+				appendStringInfo(&returning_keys, ", ");
+		}
+	}
+	else
+		appendStringInfo(&returning_keys, "NULL");
+
+	/* Search for matching tuples from the view and update if found or insert if not. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"WITH updt AS ("		/* update a tuple if this exists in the view */
+						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.+) diff.%s "
+						"FROM %s AS diff "
+						"WHERE %s "					/* tuple matching condition */
+						"RETURNING %s"				/* returning keys of updated tuples */
+					") INSERT INTO %s (%s)"	/* insert a new tuple if this doesn't existw */
+						"SELECT %s FROM %s AS diff "
+						"WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE %s);",
+					matviewname, count_colname, count_colname, count_colname,
+					deltaname_new,
+					match_cond,
+					returning_keys.data,
+					matviewname, target_list->data,
+					target_list->data, deltaname_new,
+					match_cond);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * apply_new_delta
+ *
+ * Execute a query for applying a delta table given by deltname_new
+ * which contains tuples to be inserted into a materialized view given by
+ * matviewname.  This is used when counting is not required.
+ */
+static void
+apply_new_delta(const char *matviewname, const char *deltaname_new,
+				StringInfo target_list)
+{
+	StringInfoData	querybuf;
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"INSERT INTO %s (%s) SELECT %s FROM ("
+						"SELECT diff.*, generate_series(1, diff.\"__ivm_count__\") "
+						"FROM %s AS diff) AS v",
+					matviewname, target_list->data, target_list->data,
+					deltaname_new);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
+/*
+ * get_matching_condition_string
+ *
+ * Build a predicate string for looking for a tuple with given keys.
+ */
+static char *
+get_matching_condition_string(List *keys)
+{
+	StringInfoData match_cond;
+	ListCell	*lc;
+
+	/* If there is no key columns, the condition is always true. */
+	if (keys == NIL)
+		return "true";
+
+	initStringInfo(&match_cond);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char   *resname = NameStr(attr->attname);
+		char   *mv_resname = quote_qualified_identifier("mv", resname);
+		char   *diff_resname = quote_qualified_identifier("diff", resname);
+		Oid		typid = attr->atttypid;
+
+		/* Considering NULL values, we can not use simple = operator. */
+		appendStringInfo(&match_cond, "(");
+		generate_equal(&match_cond, typid, mv_resname, diff_resname);
+		appendStringInfo(&match_cond, " OR (%s IS NULL AND %s IS NULL))",
+						 mv_resname, diff_resname);
+
+		if (lnext(keys, lc))
+			appendStringInfo(&match_cond, " AND ");
+	}
+
+	return match_cond.data;
+}
+
+/*
+ * generate_equals
+ *
+ * Generate an equality clause using given operands' default equality
+ * operator.
+ */
+static void
+generate_equal(StringInfo querybuf, Oid opttype,
+			   const char *leftop, const char *rightop)
+{
+	TypeCacheEntry *typentry;
+
+	typentry = lookup_type_cache(opttype, TYPECACHE_EQ_OPR);
+	if (!OidIsValid(typentry->eq_opr))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_FUNCTION),
+				 errmsg("could not identify an equality operator for type %s",
+						format_type_be(opttype))));
+
+	generate_operator_clause(querybuf,
+							 leftop, opttype,
+							 typentry->eq_opr,
+							 rightop, opttype);
+}
+
+/*
+ * mv_InitHashTables
+ */
+static void
+mv_InitHashTables(void)
+{
+	HASHCTL		ctl;
+
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(MV_TriggerHashEntry);
+	mv_trigger_info = hash_create("MV trigger info",
+								 MV_INIT_QUERYHASHSIZE,
+								 &ctl, HASH_ELEM | HASH_BLOBS);
+}
+
+/*
+ * AtAbort_IVM
+ *
+ * Clean up hash entries for all materialized views. This is called at
+ * transaction abort.
+ */
+void
+AtAbort_IVM()
+{
+	HASH_SEQ_STATUS seq;
+	MV_TriggerHashEntry *entry;
+
+	if (mv_trigger_info)
+	{
+		hash_seq_init(&seq, mv_trigger_info);
+		while ((entry = hash_seq_search(&seq)) != NULL)
+			clean_up_IVM_hash_entry(entry);
+	}
+}
+
+/*
+ * clean_up_IVM_hash_entry
+ *
+ * Clean up tuple stores and hash entries for a materialized view after its
+ * maintenance finished.
+ */
+static void
+clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry)
+{
+	bool found;
+	ListCell *lc;
+
+	foreach(lc, entry->tables)
+	{
+		MV_TriggerTable *table = (MV_TriggerTable *) lfirst(lc);
+
+		list_free(table->old_tuplestores);
+		list_free(table->new_tuplestores);
+	}
+	list_free(entry->tables);
+
+	hash_search(mv_trigger_info, (void *) &entry->matview_id, HASH_REMOVE, &found);
+}
+
+/*
+ * isIvmName
+ *
+ * Check if this is a IVM hidden column from the name.
+ */
+bool
+isIvmName(const char *s)
+{
+	if (s)
+		return (strncmp(s, "__ivm_", 6) == 0);
+	return false;
+}
+
+/*
+ * get_securityQuals
+ *
+ * Get row security policy on a relation.
+ * This is used by IVM for copying RLS from base table to enr.
+ */
+static List *
+get_securityQuals(Oid relId, int rt_index, Query *query)
+{
+	ParseState *pstate;
+	Relation rel;
+	ParseNamespaceItem *nsitem;
+	RangeTblEntry *rte;
+	List *securityQuals;
+	List *withCheckOptions;
+	bool  hasRowSecurity;
+	bool  hasSubLinks;
+
+	securityQuals = NIL;
+	pstate = make_parsestate(NULL);
+
+	rel = table_open(relId, NoLock);
+	nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, false);
+	rte = nsitem->p_rte;
+
+	get_row_security_policies(query, rte, rt_index,
+							  &securityQuals, &withCheckOptions,
+							  &hasRowSecurity, &hasSubLinks);
+
+	/*
+	 * Make sure the query is marked correctly if row level security
+	 * applies, or if the new quals had sublinks.
+	 */
+	if (hasRowSecurity)
+		query->hasRowSecurity = true;
+	if (hasSubLinks)
+		query->hasSubLinks = true;
+
+	table_close(rel, NoLock);
+
+	return securityQuals;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2cd8546d47..1c85df888e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
+#include "commands/matview.h"
 #include "commands/event_trigger.h"
 #include "commands/policy.h"
 #include "commands/sequence.h"
@@ -3441,6 +3442,14 @@ renameatt_internal(Oid myrelid,
 	targetrelation = relation_open(myrelid, AccessExclusiveLock);
 	renameatt_check(myrelid, RelationGetForm(targetrelation), recursing);
 
+	/*
+	 * Don't rename IVM columns.
+	 */
+	if (RelationIsIVM(targetrelation) && isIvmName(oldattname))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("IVM column can not be renamed")));
+
 	/*
 	 * if the 'recurse' flag is set then we are supposed to rename this
 	 * attribute in all classes that inherit from 'relname' (as well as in
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 0490bce664..4afc8f5826 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2949,6 +2949,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(relisivm);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 55f41263ee..08d7805b7c 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -3131,6 +3131,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(relisivm);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index cfd3ce68b4..e5a9467e99 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3436,6 +3436,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 5165fb3b93..50cc852c32 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1669,6 +1669,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_BOOL_FIELD(relisivm);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 5448cb01fa..b08c742678 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -36,6 +36,7 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 #include "utils/varlena.h"
+#include "commands/matview.h"
 
 
 /*
@@ -79,7 +80,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							int count, int offset,
 							int rtindex, int sublevels_up,
 							int location, bool include_dropped,
-							List **colnames, List **colvars);
+							List **colnames, List **colvars, bool is_ivm);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
 
@@ -1444,6 +1445,7 @@ addRangeTableEntry(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -1532,6 +1534,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -2688,7 +2691,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 						expandTupleDesc(tupdesc, rte->eref,
 										rtfunc->funccolcount, atts_done,
 										rtindex, sublevels_up, location,
-										include_dropped, colnames, colvars);
+										include_dropped, colnames, colvars, false);
 					}
 					else if (functypclass == TYPEFUNC_SCALAR)
 					{
@@ -2956,7 +2959,7 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
 	expandTupleDesc(rel->rd_att, eref, rel->rd_att->natts, 0,
 					rtindex, sublevels_up,
 					location, include_dropped,
-					colnames, colvars);
+					colnames, colvars, RelationIsIVM(rel));
 	relation_close(rel, AccessShareLock);
 }
 
@@ -2973,7 +2976,7 @@ static void
 expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 				int rtindex, int sublevels_up,
 				int location, bool include_dropped,
-				List **colnames, List **colvars)
+				List **colnames, List **colvars, bool is_ivm)
 {
 	ListCell   *aliascell;
 	int			varattno;
@@ -2986,6 +2989,9 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 	{
 		Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
 
+		if (is_ivm && isIvmName(NameStr(attr->attname)) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		if (attr->attisdropped)
 		{
 			if (include_dropped)
@@ -3140,6 +3146,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 		Var		   *varnode = (Var *) lfirst(var);
 		TargetEntry *te;
 
+		/* if transform * into columnlist with IMMV, remove IVM columns */
+		if (rte->relisivm && isIvmName(label) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		te = makeTargetEntry((Expr *) varnode,
 							 (AttrNumber) pstate->p_next_resno++,
 							 label,
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 185bf5fbff..d8a8b66196 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -776,7 +776,8 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 														attr->atttypmod))));
 	}
 
-	if (i != resultDesc->natts)
+	/* No check for materialized views since this could have special columns for IVM */
+	if ((!isSelect || requireColumnNameMatch) && i != resultDesc->natts)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 isSelect ?
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6d378ff785..7d88a84682 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11882,4 +11882,12 @@
   prorettype => 'bytea', proargtypes => 'pg_brin_minmax_multi_summary',
   prosrc => 'brin_minmax_multi_summary_send' },
 
+# IVM
+{ oid => '786', descr => 'ivm trigger (before)',
+  proname => 'IVM_immediate_before', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_before' },
+{ oid => '787', descr => 'ivm trigger (after)',
+  proname => 'IVM_immediate_maintenance', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'IVM_immediate_maintenance' },
+
 ]
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index 54a38491fb..c369b3ba5e 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -16,6 +16,7 @@
 
 #include "catalog/objectaddress.h"
 #include "nodes/params.h"
+#include "nodes/pathnodes.h"
 #include "parser/parse_node.h"
 #include "tcop/dest.h"
 #include "utils/queryenvironment.h"
@@ -25,6 +26,11 @@ extern ObjectAddress ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *st
 									   ParamListInfo params, QueryEnvironment *queryEnv,
 									   QueryCompletion *qc);
 
+extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create);
+extern void CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create);
+
+extern Query *rewriteQueryForIMMV(Query *query, List *colNames);
+
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
 
 extern DestReceiver *CreateIntoRelDestReceiver(IntoClause *intoClause);
diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h
index a067da39d2..ec479db513 100644
--- a/src/include/commands/matview.h
+++ b/src/include/commands/matview.h
@@ -15,6 +15,7 @@
 #define MATVIEW_H
 
 #include "catalog/objectaddress.h"
+#include "fmgr.h"
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
 #include "tcop/dest.h"
@@ -23,6 +24,8 @@
 
 extern void SetMatViewPopulatedState(Relation relation, bool newstate);
 
+extern void SetMatViewIVMState(Relation relation, bool newstate);
+
 extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 										ParamListInfo params, QueryCompletion *qc);
 
@@ -30,4 +33,9 @@ extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
 
 extern bool MatViewIncrementalMaintenanceIsEnabled(void);
 
+extern Datum IVM_immediate_before(PG_FUNCTION_ARGS);
+extern Datum IVM_immediate_maintenance(PG_FUNCTION_ARGS);
+extern void AtAbort_IVM(void);
+extern bool isIvmName(const char *s);
+
 #endif							/* MATVIEW_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index da02658c81..d1a754936b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1042,6 +1042,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		relisivm;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -2545,6 +2546,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		ivm;			/* incremental view maintenance is used by materialized view */
 } CreateStmt;
 
 /* ----------
-- 
2.17.1

v27-0007-Add-aggregates-support-in-IVM.patchtext/x-diff; name=v27-0007-Add-aggregates-support-in-IVM.patchDownload
From 28daff58ecd0d979d4fffdd35dfeac2ef469dbf6 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Mon, 2 Aug 2021 14:59:27 +0900
Subject: [PATCH v27 7/9] Add aggregates support in IVM
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Currently, count, sum, avg, min and max are supported.

As a restriction, expressions specified in GROUP BY must appear in
the target list because tuples to be updated in IMMV are identified
by using this group key. However, in the case of aggregates without
GROUP BY, there is only one tuple in the view, so keys are not uses
to identify tuples.

When creating a IMMV, in addition to __ivm_count column, some hidden
columns for each aggregate are added to the target list. For example,
names of these hidden columns are ivm_count_avg and ivm_sum_avg for
the average function, and so on.

In the case of views without aggregate functions, only the number of
tuple multiplicities in __ivm_count__ column are updated at incremental
maintenance. On the other hand, in the case of view with aggregates,
the aggregated values and related hidden columns are also updated. The
way of update depends the kind of aggregate function. Specifically,
sum and count are updated by simply adding or subtracting delta value
calculated from delta tables. avg is updated by using values of sum
and count stored in views as hidden columns and deltas calculated
from delta tables.

In min or max cases, it becomes more complicated. For an example of min,
when tuples are inserted, the smaller value between the current min value
in the view and the value calculated from the new delta table is used.
When tuples are deleted, if the current min value in the view equals to
the min in the old delta table, we need re-computation the latest min
value from base tables. Otherwise, the current value in the view remains.

As to sum, avg, min, and max (any aggregate functions except count),
NULL in input values is ignored, and this returns a null value when no
rows are selected. To support this specification, the number of not-NULL
input values is counted and stored in views as a hidden column. In the
case of count(), count(x) returns zero when no rows are selected, and
count(*) doesn't ignore NULL input. These specification are also supported.
---
 src/backend/commands/createas.c |  299 ++++++++-
 src/backend/commands/matview.c  | 1023 ++++++++++++++++++++++++++++++-
 src/include/commands/createas.h |    1 +
 3 files changed, 1281 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 1224a3b075..d2ae50d5ee 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/regproc.h"
 #include "utils/rel.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
@@ -80,6 +81,11 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_intorel;
 
+typedef struct
+{
+	bool	has_agg;
+} check_ivm_restriction_context;
+
 /* utility functions for CTAS definition creation */
 static ObjectAddress create_ctas_internal(List *attrList, IntoClause *into);
 static ObjectAddress create_ctas_nodata(List *tlist, IntoClause *into);
@@ -94,9 +100,9 @@ static void CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid mat
 									 Relids *relids, bool ex_lock);
 static void CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock);
 static void check_ivm_restriction(Node *node);
-static bool check_ivm_restriction_walker(Node *node, void *context);
-static Bitmapset *get_primary_key_attnos_from_query(Query *qry, List **constraintList, bool is_create);
+static bool check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context);
 static Bitmapset *get_primary_key_attnos_from_query(Query *query, List **constraintList, bool is_create);
+static bool check_aggregate_supports_ivm(Oid aggfnoid);
 
 /*
  * create_ctas_internal
@@ -429,6 +435,7 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
  * rewriteQueryForIMMV -- rewrite view definition query for IMMV
  *
  * count(*) is added for counting distinct tuples in views.
+ * Also, additional hidden columns are added for aggregate values.
  */
 Query *
 rewriteQueryForIMMV(Query *query, List *colNames)
@@ -443,14 +450,46 @@ rewriteQueryForIMMV(Query *query, List *colNames)
 	rewritten = copyObject(query);
 	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
 
-	/*
-	 * Convert DISTINCT to GROUP BY and add count(*) for counting distinct
-	 * tuples in views.
-	 */
-	if (rewritten->distinctClause)
+	/* group keys must be in targetlist */
+	if (rewritten->groupClause)
 	{
+		ListCell *lc;
+		foreach(lc, rewritten->groupClause)
+		{
+			SortGroupClause *scl = (SortGroupClause *) lfirst(lc);
+			TargetEntry *tle = get_sortgroupclause_tle(scl, rewritten->targetList);
+
+			if (tle->resjunk)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("GROUP BY expression not appearing in select list is not supported on incrementally maintainable materialized view")));
+		}
+	}
+	/* Convert DISTINCT to GROUP BY.  count(*) will be added afterward. */
+	else if (!rewritten->hasAggs && rewritten->distinctClause)
 		rewritten->groupClause = transformDistinctClause(NULL, &rewritten->targetList, rewritten->sortClause, false);
 
+	/* Add additional columns for aggregate values */
+	if (rewritten->hasAggs)
+	{
+		ListCell *lc;
+		List *aggs = NIL;
+		AttrNumber next_resno = list_length(rewritten->targetList) + 1;
+
+		foreach(lc, rewritten->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+			char *resname = (colNames == NIL ? tle->resname : strVal(list_nth(colNames, tle->resno - 1)));
+
+			if (IsA(tle->expr, Aggref))
+				makeIvmAggColumn(pstate, (Aggref *)tle->expr, resname, &next_resno, &aggs);
+		}
+		rewritten->targetList = list_concat(rewritten->targetList, aggs);
+	}
+
+	/* Add count(*) for counting distinct tuples in views */
+	if (rewritten->distinctClause || rewritten->hasAggs)
+	{
 		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
 		fn->agg_star = true;
 
@@ -467,6 +506,91 @@ rewriteQueryForIMMV(Query *query, List *colNames)
 	return rewritten;
 }
 
+/*
+ * makeIvmAggColumn -- make additional aggregate columns for IVM
+ *
+ * For an aggregate column specified by aggref, additional aggregate columns
+ * are added, which are used to calculate the new aggregate value in IMMV.
+ * An additional aggregate columns has a name based on resname
+ * (ex. ivm_count_resname), and resno specified by next_resno. The created
+ * columns are returned to aggs, and the resno for the next column is also
+ * returned to next_resno.
+ *
+ * Currently, an additional count() is created for aggref other than count.
+ * In addition, sum() is created for avg aggregate column.
+ */
+void
+makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber *next_resno, List **aggs)
+{
+	TargetEntry *tle_count;
+	Node *node;
+	FuncCall *fn;
+	Const	*dmy_arg = makeConst(INT4OID,
+								 -1,
+								 InvalidOid,
+								 sizeof(int32),
+								 Int32GetDatum(1),
+								 false,
+								 true); /* pass by value */
+	const char *aggname = get_func_name(aggref->aggfnoid);
+
+	/*
+	 * For aggregate functions except count, add count() func with the same arg parameters.
+	 * This count result is used for determining if the aggregate value should be NULL or not.
+	 * Also, add sum() func for avg because we need to calculate an average value as sum/count.
+	 *
+	 * XXX: If there are same expressions explicitly in the target list, we can use this instead
+	 * of adding new duplicated one.
+	 */
+	if (strcmp(aggname, "count") != 0)
+	{
+		fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
+
+		/* Make a Func with a dummy arg, and then override this by the original agg's args. */
+		node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1);
+		((Aggref *)node)->args = aggref->args;
+
+		tle_count = makeTargetEntry((Expr *) node,
+									*next_resno,
+									pstrdup(makeObjectName("__ivm_count",resname, "_")),
+									false);
+		*aggs = lappend(*aggs, tle_count);
+		(*next_resno)++;
+	}
+	if (strcmp(aggname, "avg") == 0)
+	{
+		List *dmy_args = NIL;
+		ListCell *lc;
+		foreach(lc, aggref->aggargtypes)
+		{
+			Oid		typeid = lfirst_oid(lc);
+			Type	type = typeidType(typeid);
+
+			Const *con = makeConst(typeid,
+								   -1,
+								   typeTypeCollation(type),
+								   typeLen(type),
+								   (Datum) 0,
+								   true,
+								   typeByVal(type));
+			dmy_args = lappend(dmy_args, con);
+			ReleaseSysCache(type);
+		}
+		fn = makeFuncCall(list_make1(makeString("sum")), NIL, COERCE_EXPLICIT_CALL, -1);
+
+		/* Make a Func with dummy args, and then override this by the original agg's args. */
+		node = ParseFuncOrColumn(pstate, fn->funcname, dmy_args, NULL, fn, false, -1);
+		((Aggref *)node)->args = aggref->args;
+
+		tle_count = makeTargetEntry((Expr *) node,
+									*next_resno,
+									pstrdup(makeObjectName("__ivm_sum",resname, "_")),
+									false);
+		*aggs = lappend(*aggs, tle_count);
+		(*next_resno)++;
+	}
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -920,11 +1044,13 @@ CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock
 static void
 check_ivm_restriction(Node *node)
 {
-	check_ivm_restriction_walker(node, NULL);
+	check_ivm_restriction_context context = {false};
+
+	check_ivm_restriction_walker(node, &context);
 }
 
 static bool
-check_ivm_restriction_walker(Node *node, void *context)
+check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context)
 {
 	if (node == NULL)
 		return false;
@@ -1005,6 +1131,8 @@ check_ivm_restriction_walker(Node *node, void *context)
 					}
 				}
 
+				context->has_agg |= qry->hasAggs;
+
 				/* restrictions for rtable */
 				foreach(lc, qry->rtable)
 				{
@@ -1053,7 +1181,7 @@ check_ivm_restriction_walker(Node *node, void *context)
 
 				}
 
-				query_tree_walker(qry, check_ivm_restriction_walker, NULL, QTW_IGNORE_RANGE_TABLE);
+				query_tree_walker(qry, check_ivm_restriction_walker, (void *) context, QTW_IGNORE_RANGE_TABLE);
 
 				break;
 			}
@@ -1064,8 +1192,12 @@ check_ivm_restriction_walker(Node *node, void *context)
 						ereport(ERROR,
 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								 errmsg("column name %s is not supported on incrementally maintainable materialized view", tle->resname)));
+				if (context->has_agg && !IsA(tle->expr, Aggref) && contain_aggs_of_level((Node *) tle->expr, 0))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("expression containing an aggregate in it is not supported on incrementally maintainable materialized view")));
 
-				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
 				break;
 			}
 		case T_JoinExpr:
@@ -1077,14 +1209,36 @@ check_ivm_restriction_walker(Node *node, void *context)
 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								 errmsg("OUTER JOIN is not supported on incrementally maintainable materialized view")));
 
-				expression_tree_walker(node, check_ivm_restriction_walker, NULL);
+				expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
+				break;
 			}
-			break;
 		case T_Aggref:
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("aggregate function is not supported on incrementally maintainable materialized view")));
-			break;
+			{
+				/* Check if this supports IVM */
+				Aggref *aggref = (Aggref *) node;
+				const char *aggname = format_procedure(aggref->aggfnoid);
+
+				if (aggref->aggfilter != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with FILTER clause is not supported on incrementally maintainable materialized view")));
+
+				if (aggref->aggdistinct != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with DISTINCT arguments is not supported on incrementally maintainable materialized view")));
+
+				if (aggref->aggorder != NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function with ORDER clause is not supported on incrementally maintainable materialized view")));
+
+				if (!check_aggregate_supports_ivm(aggref->aggfnoid))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("aggregate function %s is not supported on incrementally maintainable materialized view", aggname)));
+				break;
+			}
 		default:
 			expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
 			break;
@@ -1092,6 +1246,91 @@ check_ivm_restriction_walker(Node *node, void *context)
 	return false;
 }
 
+/*
+ * check_aggregate_supports_ivm
+ *
+ * Check if the given aggregate function is supporting IVM
+ */
+static bool
+check_aggregate_supports_ivm(Oid aggfnoid)
+{
+	switch (aggfnoid)
+	{
+		/* count */
+		case F_COUNT_ANY:
+		case F_COUNT_:
+
+		/* sum */
+		case F_SUM_INT8:
+		case F_SUM_INT4:
+		case F_SUM_INT2:
+		case F_SUM_FLOAT4:
+		case F_SUM_FLOAT8:
+		case F_SUM_MONEY:
+		case F_SUM_INTERVAL:
+		case F_SUM_NUMERIC:
+
+		/* avg */
+		case F_AVG_INT8:
+		case F_AVG_INT4:
+		case F_AVG_INT2:
+		case F_AVG_NUMERIC:
+		case F_AVG_FLOAT4:
+		case F_AVG_FLOAT8:
+		case F_AVG_INTERVAL:
+
+		/* min */
+		case F_MIN_ANYARRAY:
+		case F_MIN_INT8:
+		case F_MIN_INT4:
+		case F_MIN_INT2:
+		case F_MIN_OID:
+		case F_MIN_FLOAT4:
+		case F_MIN_FLOAT8:
+		case F_MIN_DATE:
+		case F_MIN_TIME:
+		case F_MIN_TIMETZ:
+		case F_MIN_MONEY:
+		case F_MIN_TIMESTAMP:
+		case F_MIN_TIMESTAMPTZ:
+		case F_MIN_INTERVAL:
+		case F_MIN_TEXT:
+		case F_MIN_NUMERIC:
+		case F_MIN_BPCHAR:
+		case F_MIN_TID:
+		case F_MIN_ANYENUM:
+		case F_MIN_INET:
+		case F_MIN_PG_LSN:
+
+		/* max */
+		case F_MAX_ANYARRAY:
+		case F_MAX_INT8:
+		case F_MAX_INT4:
+		case F_MAX_INT2:
+		case F_MAX_OID:
+		case F_MAX_FLOAT4:
+		case F_MAX_FLOAT8:
+		case F_MAX_DATE:
+		case F_MAX_TIME:
+		case F_MAX_TIMETZ:
+		case F_MAX_MONEY:
+		case F_MAX_TIMESTAMP:
+		case F_MAX_TIMESTAMPTZ:
+		case F_MAX_INTERVAL:
+		case F_MAX_TEXT:
+		case F_MAX_NUMERIC:
+		case F_MAX_BPCHAR:
+		case F_MAX_TID:
+		case F_MAX_ANYENUM:
+		case F_MAX_INET:
+		case F_MAX_PG_LSN:
+			return true;
+
+		default:
+			return false;
+	}
+}
+
 /*
  * CreateIndexOnIMMV
  *
@@ -1149,7 +1388,29 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create)
 	index->concurrent = false;
 	index->if_not_exists = false;
 
-	if (query->distinctClause)
+	if (query->groupClause)
+	{
+		/* create unique constraint on GROUP BY expression columns */
+		foreach(lc, query->groupClause)
+		{
+			SortGroupClause *scl = (SortGroupClause *) lfirst(lc);
+			TargetEntry *tle = get_sortgroupclause_tle(scl, query->targetList);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+			IndexElem  *iparam;
+
+			iparam = makeNode(IndexElem);
+			iparam->name = pstrdup(NameStr(attr->attname));
+			iparam->expr = NULL;
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->opclassopts = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+		}
+	}
+	else if (query->distinctClause)
 	{
 		/* create unique constraint on all columns */
 		foreach(lc, query->targetList)
@@ -1207,7 +1468,7 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create)
 					(errmsg("could not create an index on materialized view \"%s\" automatically",
 							RelationGetRelationName(matviewRel)),
 					 errdetail("This target list does not have all the primary key columns, "
-							   "or this view does not contain DISTINCT clause."),
+							   "or this view does not contain GROUP BY or DISTINCT clause."),
 					 errhint("Create an index on the materialized view for efficient incremental maintenance.")));
 			return;
 		}
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index cb713328a0..721d91c009 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -82,6 +82,32 @@ typedef struct
 
 #define MV_INIT_QUERYHASHSIZE	16
 
+/* MV query type codes */
+#define MV_PLAN_RECALC			1
+#define MV_PLAN_SET_VALUE		2
+
+/*
+ * MI_QueryKey
+ *
+ * The key identifying a prepared SPI plan in our query hashtable
+ */
+typedef struct MV_QueryKey
+{
+	Oid			matview_id;	/* OID of materialized view */
+	int32		query_type;	/* query type ID, see MV_PLAN_XXX above */
+} MV_QueryKey;
+
+/*
+ * MV_QueryHashEntry
+ *
+ * Hash entry for cached plans used to maintain materialized views.
+ */
+typedef struct MV_QueryHashEntry
+{
+	MV_QueryKey key;
+	SPIPlanPtr	plan;
+} MV_QueryHashEntry;
+
 /*
  * MV_TriggerHashEntry
  *
@@ -118,8 +144,16 @@ typedef struct MV_TriggerTable
 	RangeTblEntry *original_rte;	/* the original RTE saved before rewriting query */
 } MV_TriggerTable;
 
+static HTAB *mv_query_cache = NULL;
 static HTAB *mv_trigger_info = NULL;
 
+/* kind of IVM operation for the view */
+typedef enum
+{
+	IVM_ADD,
+	IVM_SUB
+} IvmOp;
+
 /* ENR name for materialized view delta */
 #define NEW_DELTA_ENRNAME "new_delta"
 #define OLD_DELTA_ENRNAME "old_delta"
@@ -153,7 +187,7 @@ static RangeTblEntry *get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *tabl
 				 QueryEnvironment *queryEnv);
 static RangeTblEntry *union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
 		   QueryEnvironment *queryEnv);
-static Query *rewrite_query_for_distinct(Query *query, ParseState *pstate);
+static Query *rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate);
 
 static void calc_delta(MV_TriggerTable *table, int rte_index, Query *query,
 			DestReceiver *dest_old, DestReceiver *dest_new,
@@ -164,19 +198,48 @@ static Query *rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *
 static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
 			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
 			Query *query, bool use_count, char *count_colname);
+static void append_set_clause_for_count(const char *resname, StringInfo buf_old,
+							StringInfo buf_new,StringInfo aggs_list);
+static void append_set_clause_for_sum(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list);
+static void append_set_clause_for_avg(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list,
+						  const char *aggtype);
+static void append_set_clause_for_minmax(const char *resname, StringInfo buf_old,
+							 StringInfo buf_new, StringInfo aggs_list,
+							 bool is_min);
+static char *get_operation_string(IvmOp op, const char *col, const char *arg1, const char *arg2,
+					 const char* count_col, const char *castType);
+static char *get_null_condition_string(IvmOp op, const char *arg1, const char *arg2,
+						  const char* count_col);
 static void apply_old_delta(const char *matviewname, const char *deltaname_old,
 				List *keys);
 static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
-				List *keys, const char *count_colname);
+				List *keys, StringInfo aggs_list, StringInfo aggs_set,
+				List *minmax_list, List *is_min_list,
+				const char *count_colname,
+				SPITupleTable **tuptable_recalc, uint64 *num_recalc);
 static void apply_new_delta(const char *matviewname, const char *deltaname_new,
 				StringInfo target_list);
 static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
-				List *keys, StringInfo target_list, const char* count_colname);
+				List *keys, StringInfo target_list, StringInfo aggs_set,
+				const char* count_colname);
 static char *get_matching_condition_string(List *keys);
+static char *get_returning_string(List *minmax_list, List *is_min_list, List *keys);
+static char *get_minmax_recalc_condition_string(List *minmax_list, List *is_min_list);
+static char *get_select_for_recalc_string(List *keys);
+static void recalc_and_set_values(SPITupleTable *tuptable_recalc, int64 num_tuples,
+					  List *namelist, List *keys, Relation matviewRel);
+static SPIPlanPtr get_plan_for_recalc(Oid matviewOid, List *namelist, List *keys, Oid *keyTypes);
+static SPIPlanPtr get_plan_for_set_values(Oid matviewOid, char *matviewname, List *namelist,
+						Oid *valTypes);
 static void generate_equal(StringInfo querybuf, Oid opttype,
 			   const char *leftop, const char *rightop);
 
 static void mv_InitHashTables(void);
+static SPIPlanPtr mv_FetchPreparedPlan(MV_QueryKey *key);
+static void mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan);
+static void mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type);
 static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry);
 
 static List *get_securityQuals(Oid relId, int rt_index, Query *query);
@@ -1470,8 +1533,8 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
 	rewritten = rewrite_query_for_preupdate_state(rewritten, entry->tables,
 												  entry->xid, entry->cid,
 												  pstate);
-	/* Rewrite for DISTINCT clause */
-	rewritten = rewrite_query_for_distinct(rewritten, pstate);
+	/* Rewrite for DISTINCT clause and aggregates functions */
+	rewritten = rewrite_query_for_distinct_and_aggregates(rewritten, pstate);
 
 	/* Create tuplestores to store view deltas */
 	if (entry->has_old)
@@ -1522,7 +1585,7 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
 
 			count_colname = pstrdup("__ivm_count__");
 
-			if (query->distinctClause)
+			if (query->hasAggs || query->distinctClause)
 				use_count = true;
 
 			/* calculate delta tables */
@@ -1884,17 +1947,34 @@ union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_rtes, const char *prefix,
 }
 
 /*
- * rewrite_query_for_distinct
+ * rewrite_query_for_distinct_and_aggregates
  *
- * Rewrite query for counting DISTINCT clause.
+ * Rewrite query for counting DISTINCT clause and aggregate functions.
  */
 static Query *
-rewrite_query_for_distinct(Query *query, ParseState *pstate)
+rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate)
 {
 	TargetEntry *tle_count;
 	FuncCall *fn;
 	Node *node;
 
+	/* For aggregate views */
+	if (query->hasAggs)
+	{
+		ListCell *lc;
+		List *aggs = NIL;
+		AttrNumber next_resno = list_length(query->targetList) + 1;
+
+		foreach(lc, query->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+			if (IsA(tle->expr, Aggref))
+				makeIvmAggColumn(pstate, (Aggref *)tle->expr, tle->resname, &next_resno, &aggs);
+		}
+		query->targetList = list_concat(query->targetList, aggs);
+	}
+
 	/* Add count(*) for counting distinct tuples in views */
 	fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1);
 	fn->agg_star = true;
@@ -1963,6 +2043,8 @@ rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte
 	return query;
 }
 
+#define IVM_colname(type, col) makeObjectName("__ivm_" type, col, "_")
+
 /*
  * apply_delta
  *
@@ -1976,11 +2058,16 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 {
 	StringInfoData querybuf;
 	StringInfoData target_list_buf;
+	StringInfo	aggs_list_buf = NULL;
+	StringInfo	aggs_set_old = NULL;
+	StringInfo	aggs_set_new = NULL;
 	Relation	matviewRel;
 	char	   *matviewname;
 	ListCell	*lc;
 	int			i;
 	List	   *keys = NIL;
+	List	   *minmax_list = NIL;
+	List	   *is_min_list = NIL;
 
 
 	/*
@@ -1998,6 +2085,15 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 	initStringInfo(&querybuf);
 	initStringInfo(&target_list_buf);
 
+	if (query->hasAggs)
+	{
+		if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
+			aggs_set_old = makeStringInfo();
+		if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
+			aggs_set_new = makeStringInfo();
+		aggs_list_buf = makeStringInfo();
+	}
+
 	/* build string of target list */
 	for (i = 0; i < matviewRel->rd_att->natts; i++)
 	{
@@ -2014,13 +2110,72 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 	{
 		TargetEntry *tle = (TargetEntry *) lfirst(lc);
 		Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, i);
+		char *resname = NameStr(attr->attname);
 
 		i++;
 
 		if (tle->resjunk)
 			continue;
 
-		keys = lappend(keys, attr);
+		/*
+		 * For views without aggregates, all attributes are used as keys to identify a
+		 * tuple in a view.
+		 */
+		if (!query->hasAggs)
+			keys = lappend(keys, attr);
+
+		/* For views with aggregates, we need to build SET clause for updating aggregate
+		 * values. */
+		if (query->hasAggs && IsA(tle->expr, Aggref))
+		{
+			Aggref *aggref = (Aggref *) tle->expr;
+			const char *aggname = get_func_name(aggref->aggfnoid);
+
+			/*
+			 * We can use function names here because it is already checked if these
+			 * can be used in IMMV by its OID at the definition time.
+			 */
+
+			/* count */
+			if (!strcmp(aggname, "count"))
+				append_set_clause_for_count(resname, aggs_set_old, aggs_set_new, aggs_list_buf);
+
+			/* sum */
+			else if (!strcmp(aggname, "sum"))
+				append_set_clause_for_sum(resname, aggs_set_old, aggs_set_new, aggs_list_buf);
+
+			/* avg */
+			else if (!strcmp(aggname, "avg"))
+				append_set_clause_for_avg(resname, aggs_set_old, aggs_set_new, aggs_list_buf,
+										  format_type_be(aggref->aggtype));
+
+			/* min/max */
+			else if (!strcmp(aggname, "min") || !strcmp(aggname, "max"))
+			{
+				bool	is_min = (!strcmp(aggname, "min"));
+
+				append_set_clause_for_minmax(resname, aggs_set_old, aggs_set_new, aggs_list_buf, is_min);
+
+				/* make a resname list of min and max aggregates */
+				minmax_list = lappend(minmax_list, resname);
+				is_min_list = lappend_int(is_min_list, is_min);
+			}
+			else
+				elog(ERROR, "unsupported aggregate function: %s", aggname);
+		}
+	}
+
+	/* If we have GROUP BY clause, we use its entries as keys. */
+	if (query->hasAggs && query->groupClause)
+	{
+		foreach (lc, query->groupClause)
+		{
+			SortGroupClause *sgcl = (SortGroupClause *) lfirst(lc);
+			TargetEntry		*tle = get_sortgroupclause_tle(sgcl, query->targetList);
+			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+
+			keys = lappend(keys, attr);
+		}
 	}
 
 	/* Start maintaining the materialized view. */
@@ -2034,6 +2189,8 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 	if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
 	{
 		EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
+		SPITupleTable  *tuptable_recalc = NULL;
+		uint64			num_recalc;
 		int				rc;
 
 		/* convert tuplestores to ENR, and register for SPI */
@@ -2051,10 +2208,19 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		if (use_count)
 			/* apply old delta and get rows to be recalculated */
 			apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME,
-									   keys, count_colname);
+									   keys, aggs_list_buf, aggs_set_old,
+									   minmax_list, is_min_list,
+									   count_colname, &tuptable_recalc, &num_recalc);
 		else
 			apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys);
 
+		/*
+		 * If we have min or max, we might have to recalculate aggregate values from base tables
+		 * on some tuples. TIDs and keys such tuples are returned as a result of the above query.
+		 */
+		if (minmax_list && tuptable_recalc)
+			recalc_and_set_values(tuptable_recalc, num_recalc, minmax_list, keys, matviewRel);
+
 	}
 	/* For tuple insertion */
 	if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
@@ -2077,7 +2243,7 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		/* apply new delta */
 		if (use_count)
 			apply_new_delta_with_count(matviewname, NEW_DELTA_ENRNAME,
-								keys, &target_list_buf, count_colname);
+								keys, aggs_set_new, &target_list_buf, count_colname);
 		else
 			apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf);
 	}
@@ -2092,49 +2258,410 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		elog(ERROR, "SPI_finish failed");
 }
 
+/*
+ * append_set_clause_for_count
+ *
+ * Append SET clause string for count aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_count(const char *resname, StringInfo buf_old,
+							StringInfo buf_new,StringInfo aggs_list)
+{
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* resname = mv.resname - t.resname */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, resname, "mv", "t", NULL, NULL));
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* resname = mv.resname + diff.resname */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, resname, "mv", "diff", NULL, NULL));
+	}
+
+	appendStringInfo(aggs_list, ", %s",
+		quote_qualified_identifier("diff", resname)
+	);
+}
+
+/*
+ * append_set_clause_for_sum
+ *
+ * Append SET clause string for sum aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_sum(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list)
+{
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* sum = mv.sum - t.sum */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, resname, "mv", "t", count_col, NULL)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* sum = mv.sum + diff.sum */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, resname, "mv", "diff", count_col, NULL)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * append_set_clause_for_avg
+ *
+ * Append SET clause string for avg aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ */
+static void
+append_set_clause_for_avg(const char *resname, StringInfo buf_old,
+						  StringInfo buf_new, StringInfo aggs_list,
+						  const char *aggtype)
+{
+	char *sum_col = IVM_colname("sum", resname);
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/* avg = (mv.sum - t.sum)::aggtype / (mv.count - t.count) */
+		appendStringInfo(buf_old,
+			", %s = %s OPERATOR(pg_catalog./) %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, aggtype),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+		/* sum = mv.sum - t.sum */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, sum_col),
+			get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, NULL)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/* avg = (mv.sum + diff.sum)::aggtype / (mv.count + diff.count) */
+		appendStringInfo(buf_new,
+			", %s = %s OPERATOR(pg_catalog./) %s",
+			quote_qualified_identifier(NULL, resname),
+			get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, aggtype),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+		/* sum = mv.sum + diff.sum */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, sum_col),
+			get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, NULL)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("sum", resname)),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * append_set_clause_for_minmax
+ *
+ * Append SET clause string for min or max aggregation to given buffers.
+ * Also, append resnames required for calculating the aggregate value.
+ * is_min is true if this is min, false if not.
+ */
+static void
+append_set_clause_for_minmax(const char *resname, StringInfo buf_old,
+							 StringInfo buf_new, StringInfo aggs_list,
+							 bool is_min)
+{
+	char *count_col = IVM_colname("count", resname);
+
+	/* For tuple deletion */
+	if (buf_old)
+	{
+		/*
+		 * If the new value doesn't became NULL then use the value remaining
+		 * in the view although this will be recomputated afterwords.
+		 */
+		appendStringInfo(buf_old,
+			", %s = CASE WHEN %s THEN NULL ELSE %s END",
+			quote_qualified_identifier(NULL, resname),
+			get_null_condition_string(IVM_SUB, "mv", "t", count_col),
+			quote_qualified_identifier("mv", resname)
+		);
+		/* count = mv.count - t.count */
+		appendStringInfo(buf_old,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
+		);
+	}
+	/* For tuple insertion */
+	if (buf_new)
+	{
+		/*
+		 * min = LEAST(mv.min, diff.min)
+		 * max = GREATEST(mv.max, diff.max)
+		 */
+		appendStringInfo(buf_new,
+			", %s = CASE WHEN %s THEN NULL ELSE %s(%s,%s) END",
+			quote_qualified_identifier(NULL, resname),
+			get_null_condition_string(IVM_ADD, "mv", "diff", count_col),
+
+			is_min ? "LEAST" : "GREATEST",
+			quote_qualified_identifier("mv", resname),
+			quote_qualified_identifier("diff", resname)
+		);
+		/* count = mv.count + diff.count */
+		appendStringInfo(buf_new,
+			", %s = %s",
+			quote_qualified_identifier(NULL, count_col),
+			get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
+		);
+	}
+
+	appendStringInfo(aggs_list, ", %s, %s",
+		quote_qualified_identifier("diff", resname),
+		quote_qualified_identifier("diff", IVM_colname("count", resname))
+	);
+}
+
+/*
+ * get_operation_string
+ *
+ * Build a string to calculate the new aggregate values.
+ */
+static char *
+get_operation_string(IvmOp op, const char *col, const char *arg1, const char *arg2,
+					 const char* count_col, const char *castType)
+{
+	StringInfoData buf;
+	StringInfoData castString;
+	char   *col1 = quote_qualified_identifier(arg1, col);
+	char   *col2 = quote_qualified_identifier(arg2, col);
+	char	op_char = (op == IVM_SUB ? '-' : '+');
+
+	initStringInfo(&buf);
+	initStringInfo(&castString);
+
+	if (castType)
+		appendStringInfo(&castString, "::%s", castType);
+
+	if (!count_col)
+	{
+		/*
+		 * If the attributes don't have count columns then calc the result
+		 * by using the operator simply.
+		 */
+		appendStringInfo(&buf, "(%s OPERATOR(pg_catalog.%c) %s)%s",
+			col1, op_char, col2, castString.data);
+	}
+	else
+	{
+		/*
+		 * If the attributes have count columns then consider the condition
+		 * where the result becomes NULL.
+		 */
+		char *null_cond = get_null_condition_string(op, arg1, arg2, count_col);
+
+		appendStringInfo(&buf,
+			"(CASE WHEN %s THEN NULL "
+				"WHEN %s IS NULL THEN %s "
+				"WHEN %s IS NULL THEN %s "
+				"ELSE (%s OPERATOR(pg_catalog.%c) %s)%s END)",
+			null_cond,
+			col1, col2,
+			col2, col1,
+			col1, op_char, col2, castString.data
+		);
+	}
+
+	return buf.data;
+}
+
+/*
+ * get_null_condition_string
+ *
+ * Build a predicate string for CASE clause to check if an aggregate value
+ * will became NULL after the given operation is applied.
+ */
+static char *
+get_null_condition_string(IvmOp op, const char *arg1, const char *arg2,
+						  const char* count_col)
+{
+	StringInfoData null_cond;
+	initStringInfo(&null_cond);
+
+	switch (op)
+	{
+		case IVM_ADD:
+			appendStringInfo(&null_cond,
+				"%s OPERATOR(pg_catalog.=) 0 AND %s OPERATOR(pg_catalog.=) 0",
+				quote_qualified_identifier(arg1, count_col),
+				quote_qualified_identifier(arg2, count_col)
+			);
+			break;
+		case IVM_SUB:
+			appendStringInfo(&null_cond,
+				"%s OPERATOR(pg_catalog.=) %s",
+				quote_qualified_identifier(arg1, count_col),
+				quote_qualified_identifier(arg2, count_col)
+			);
+			break;
+		default:
+			elog(ERROR,"unknown operation");
+	}
+
+	return null_cond.data;
+}
+
+
 /*
  * apply_old_delta_with_count
  *
  * Execute a query for applying a delta table given by deltname_old
  * which contains tuples to be deleted from to a materialized view given by
  * matviewname.  This is used when counting is required, that is, the view
- * has aggregate or distinct.
+ * has aggregate or distinct. Also, when a table in EXISTS sub queries
+ * is modified.
+ *
+ * If the view desn't have aggregates or has GROUP BY, this requires a keys
+ * list to identify a tuple in the view. If the view has aggregates, this
+ * requires strings representing resnames of aggregates and SET clause for
+ * updating aggregate values.
+ *
+ * If the view has min or max aggregate, this requires a list of resnames of
+ * min/max aggregates and a list of boolean which represents which entries in
+ * minmax_list is min. These are necessary to check if we need to recalculate
+ * min or max aggregate values. In this case, this query returns TID and keys
+ * of tuples which need to be recalculated.  This result and the number of rows
+ * are stored in tuptables and num_recalc repectedly.
+ *
  */
 static void
 apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
-				List *keys, const char *count_colname)
+				List *keys, StringInfo aggs_list, StringInfo aggs_set,
+				List *minmax_list, List *is_min_list,
+				const char *count_colname,
+				SPITupleTable **tuptable_recalc, uint64 *num_recalc)
 {
 	StringInfoData	querybuf;
 	char   *match_cond;
+	char   *updt_returning = "";
+	char   *select_for_recalc = "SELECT";
+	bool	agg_without_groupby = (list_length(keys) == 0);
+
+	Assert(tuptable_recalc != NULL);
+	Assert(num_recalc != NULL);
 
 	/* build WHERE condition for searching tuples to be deleted */
 	match_cond = get_matching_condition_string(keys);
 
+	/*
+	 * We need a special RETURNING clause and SELECT statement for min/max to
+	 * check which tuple needs re-calculation from base tables.
+	 */
+	if (minmax_list)
+	{
+		updt_returning = get_returning_string(minmax_list, is_min_list, keys);
+		select_for_recalc = get_select_for_recalc_string(keys);
+	}
+
 	/* Search for matching tuples from the view and update or delete if found. */
 	initStringInfo(&querybuf);
 	appendStringInfo(&querybuf,
 					"WITH t AS ("			/* collecting tid of target tuples in the view */
 						"SELECT diff.%s, "			/* count column */
-								"(diff.%s OPERATOR(pg_catalog.=) mv.%s) AS for_dlt, "
+								"(diff.%s OPERATOR(pg_catalog.=) mv.%s AND %s) AS for_dlt, "
 								"mv.ctid "
+								"%s "				/* aggregate columns */
 						"FROM %s AS mv, %s AS diff "
 						"WHERE %s"					/* tuple matching condition */
 					"), updt AS ("			/* update a tuple if this is not to be deleted */
 						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.-) t.%s "
+											"%s"	/* SET clauses for aggregates */
 						"FROM t WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND NOT for_dlt "
-					")"
-					/* delete a tuple if this is to be deleted */
-					"DELETE FROM %s AS mv USING t "
-					"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt",
+						"%s"						/* RETURNING clause for recalc infomation */
+					"), dlt AS ("			/* delete a tuple if this is to be deleted */
+						"DELETE FROM %s AS mv USING t "
+						"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt"
+					") %s",							/* SELECT returning which tuples need to be recalculated */
 					count_colname,
-					count_colname, count_colname,
+					count_colname, count_colname, (agg_without_groupby ? "false" : "true"),
+					(aggs_list != NULL ? aggs_list->data : ""),
 					matviewname, deltaname_old,
 					match_cond,
 					matviewname, count_colname, count_colname, count_colname,
-					matviewname);
+					(aggs_set != NULL ? aggs_set->data : ""),
+					updt_returning,
+					matviewname,
+					select_for_recalc);
 
-	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_SELECT)
 		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+
+
+	/* Return tuples to be recalculated. */
+	if (minmax_list)
+	{
+		*tuptable_recalc = SPI_tuptable;
+		*num_recalc = SPI_processed;
+	}
+	else
+	{
+		*tuptable_recalc = NULL;
+		*num_recalc = 0;
+	}
 }
 
 /*
@@ -2194,10 +2721,15 @@ apply_old_delta(const char *matviewname, const char *deltaname_old,
  * matviewname.  This is used when counting is required, that is, the view
  * has aggregate or distinct. Also, when a table in EXISTS sub queries
  * is modified.
+ *
+ * If the view desn't have aggregates or has GROUP BY, this requires a keys
+ * list to identify a tuple in the view. If the view has aggregates, this
+ * requires strings representing SET clause for updating aggregate values.
  */
 static void
 apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
-				List *keys, StringInfo target_list, const char* count_colname)
+				List *keys, StringInfo aggs_set, StringInfo target_list,
+				const char* count_colname)
 {
 	StringInfoData	querybuf;
 	StringInfoData	returning_keys;
@@ -2228,6 +2760,7 @@ apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
 	appendStringInfo(&querybuf,
 					"WITH updt AS ("		/* update a tuple if this exists in the view */
 						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.+) diff.%s "
+											"%s "	/* SET clauses for aggregates */
 						"FROM %s AS diff "
 						"WHERE %s "					/* tuple matching condition */
 						"RETURNING %s"				/* returning keys of updated tuples */
@@ -2235,6 +2768,7 @@ apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
 						"SELECT %s FROM %s AS diff "
 						"WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE %s);",
 					matviewname, count_colname, count_colname, count_colname,
+					(aggs_set != NULL ? aggs_set->data : ""),
 					deltaname_new,
 					match_cond,
 					returning_keys.data,
@@ -2309,6 +2843,349 @@ get_matching_condition_string(List *keys)
 	return match_cond.data;
 }
 
+/*
+ * get_returning_string
+ *
+ * Build a string for RETURNING clause of UPDATE used in apply_old_delta_with_count.
+ * This clause returns ctid and a boolean value that indicates if we need to
+ * recalculate min or max value, for each updated row.
+ */
+static char *
+get_returning_string(List *minmax_list, List *is_min_list, List *keys)
+{
+	StringInfoData returning;
+	char		*recalc_cond;
+	ListCell	*lc;
+
+	Assert(minmax_list != NIL && is_min_list != NIL);
+	recalc_cond = get_minmax_recalc_condition_string(minmax_list, is_min_list);
+
+	initStringInfo(&returning);
+
+	appendStringInfo(&returning, "RETURNING mv.ctid AS tid, (%s) AS recalc", recalc_cond);
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		char *resname = NameStr(attr->attname);
+		appendStringInfo(&returning, ", %s", quote_qualified_identifier("mv", resname));
+	}
+
+	return returning.data;
+}
+
+/*
+ * get_minmax_recalc_condition_string
+ *
+ * Build a predicate string for checking if any min/max aggregate
+ * value needs to be recalculated.
+ */
+static char *
+get_minmax_recalc_condition_string(List *minmax_list, List *is_min_list)
+{
+	StringInfoData recalc_cond;
+	ListCell	*lc1, *lc2;
+
+	initStringInfo(&recalc_cond);
+
+	Assert (list_length(minmax_list) == list_length(is_min_list));
+
+	forboth (lc1, minmax_list, lc2, is_min_list)
+	{
+		char   *resname = (char *) lfirst(lc1);
+		bool	is_min = (bool) lfirst_int(lc2);
+		char   *op_str = (is_min ? ">=" : "<=");
+
+		appendStringInfo(&recalc_cond, "%s OPERATOR(pg_catalog.%s) %s",
+			quote_qualified_identifier("mv", resname),
+			op_str,
+			quote_qualified_identifier("t", resname)
+		);
+
+		if (lnext(minmax_list, lc1))
+			appendStringInfo(&recalc_cond, " OR ");
+	}
+
+	return recalc_cond.data;
+}
+
+/*
+ * get_select_for_recalc_string
+ *
+ * Build a query to return tid and keys of tuples which need
+ * recalculation. This is used as the result of the query
+ * built by apply_old_delta.
+ */
+static char *
+get_select_for_recalc_string(List *keys)
+{
+	StringInfoData qry;
+	ListCell	*lc;
+
+	initStringInfo(&qry);
+
+	appendStringInfo(&qry, "SELECT tid");
+	foreach (lc, keys)
+	{
+		Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+		appendStringInfo(&qry, ", %s", NameStr(attr->attname));
+	}
+
+	appendStringInfo(&qry, " FROM updt WHERE recalc");
+
+	return qry.data;
+}
+
+/*
+ * recalc_and_set_values
+ *
+ * Recalculate tuples in a materialized from base tables and update these.
+ * The tuples which needs recalculation are specified by keys, and resnames
+ * of columns to be updated are specified by namelist. TIDs and key values
+ * are given by tuples in tuptable_recalc. Its first attribute must be TID
+ * and key values must be following this.
+ */
+static void
+recalc_and_set_values(SPITupleTable *tuptable_recalc, int64 num_tuples,
+					  List *namelist, List *keys, Relation matviewRel)
+{
+	TupleDesc   tupdesc_recalc = tuptable_recalc->tupdesc;
+	Oid		   *keyTypes = NULL, *types = NULL;
+	char	   *keyNulls = NULL, *nulls = NULL;
+	Datum	   *keyVals = NULL, *vals = NULL;
+	int			num_vals = list_length(namelist);
+	int			num_keys = list_length(keys);
+	uint64      i;
+	Oid			matviewOid;
+	char	   *matviewname;
+
+	matviewOid = RelationGetRelid(matviewRel);
+	matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+											 RelationGetRelationName(matviewRel));
+
+	/* If we have keys, initialize arrays for them. */
+	if (keys)
+	{
+		keyTypes = palloc(sizeof(Oid) * num_keys);
+		keyNulls = palloc(sizeof(char) * num_keys);
+		keyVals = palloc(sizeof(Datum) * num_keys);
+		/* a tuple contains keys to be recalculated and ctid to be updated*/
+		Assert(tupdesc_recalc->natts == num_keys + 1);
+
+		/* Types of key attributes  */
+		for (i = 0; i < num_keys; i++)
+			keyTypes[i] = TupleDescAttr(tupdesc_recalc, i + 1)->atttypid;
+	}
+
+	/* allocate memory for all attribute names and tid */
+	types = palloc(sizeof(Oid) * (num_vals + 1));
+	nulls = palloc(sizeof(char) * (num_vals + 1));
+	vals = palloc(sizeof(Datum) * (num_vals + 1));
+
+	/* For each tuple which needs recalculation */
+	for (i = 0; i < num_tuples; i++)
+	{
+		int j;
+		bool isnull;
+		SPIPlanPtr plan;
+		SPITupleTable *tuptable_newvals;
+		TupleDesc   tupdesc_newvals;
+
+		/* Set group key values as parameters if needed. */
+		if (keys)
+		{
+			for (j = 0; j < num_keys; j++)
+			{
+				keyVals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, j + 2, &isnull);
+				if (isnull)
+					keyNulls[j] = 'n';
+				else
+					keyNulls[j] = ' ';
+			}
+		}
+
+		/*
+		 * Get recalculated values from base tables. The result must be
+		 * only one tuple thich contains the new values for specified keys.
+		 */
+		plan = get_plan_for_recalc(matviewOid, namelist, keys, keyTypes);
+		if (SPI_execute_plan(plan, keyVals, keyNulls, false, 0) != SPI_OK_SELECT)
+			elog(ERROR, "SPI_execute_plan");
+		if (SPI_processed != 1)
+			elog(ERROR, "SPI_execute_plan returned zero or more than one rows");
+
+		tuptable_newvals = SPI_tuptable;
+		tupdesc_newvals = tuptable_newvals->tupdesc;
+
+		Assert(tupdesc_newvals->natts == num_vals);
+
+		/* Set the new values as parameters */
+		for (j = 0; j < tupdesc_newvals->natts; j++)
+		{
+			if (i == 0)
+				types[j] = TupleDescAttr(tupdesc_newvals, j)->atttypid;
+
+			vals[j] = SPI_getbinval(tuptable_newvals->vals[0], tupdesc_newvals, j + 1, &isnull);
+			if (isnull)
+				nulls[j] = 'n';
+			else
+				nulls[j] = ' ';
+		}
+		/* Set TID of the view tuple to be updated as a parameter */
+		types[j] = TIDOID;
+		vals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, 1, &isnull);
+		nulls[j] = ' ';
+
+		/* Update the view tuple to the new values */
+		plan = get_plan_for_set_values(matviewOid, matviewname, namelist, types);
+		if (SPI_execute_plan(plan, vals, nulls, false, 0) != SPI_OK_UPDATE)
+			elog(ERROR, "SPI_execute_plan");
+	}
+}
+
+
+/*
+ * get_plan_for_recalc
+ *
+ * Create or fetch a plan for recalculating value in the view's target list
+ * from base tables using the definition query of materialized view specified
+ * by matviewOid. namelist is a list of resnames of values to be recalculated.
+ *
+ * keys is a list of keys to identify tuples to be recalculated if this is not
+ * empty. KeyTypes is an array of types of keys.
+ */
+static SPIPlanPtr
+get_plan_for_recalc(Oid matviewOid, List *namelist, List *keys, Oid *keyTypes)
+{
+	MV_QueryKey hash_key;
+	SPIPlanPtr	plan;
+
+	/* Fetch or prepare a saved plan for the recalculation */
+	mv_BuildQueryKey(&hash_key, matviewOid, MV_PLAN_RECALC);
+	if ((plan = mv_FetchPreparedPlan(&hash_key)) == NULL)
+	{
+		ListCell	   *lc;
+		StringInfoData	str;
+		char   *viewdef;
+
+		/* get view definition of matview */
+		viewdef = text_to_cstring((text *) DatumGetPointer(
+					DirectFunctionCall1(pg_get_viewdef, ObjectIdGetDatum(matviewOid))));
+		/* get rid of trailing semi-colon */
+		viewdef[strlen(viewdef)-1] = '\0';
+
+		/*
+		 * Build a query string for recalculating values. This is like
+		 *
+		 *  SELECT x1, x2, x3, ... FROM ( ... view definition query ...) mv
+		 *   WHERE (key1, key2, ...) = ($1, $2, ...);
+		 */
+
+		initStringInfo(&str);
+		appendStringInfo(&str, "SELECT ");
+		foreach (lc, namelist)
+		{
+			appendStringInfo(&str, "%s", (char *) lfirst(lc));
+			if (lnext(namelist, lc))
+				appendStringInfoString(&str, ", ");
+		}
+		appendStringInfo(&str, " FROM (%s) mv", viewdef);
+
+		if (keys)
+		{
+			int		i = 1;
+			char	paramname[16];
+
+			appendStringInfo(&str, " WHERE (");
+			foreach (lc, keys)
+			{
+				Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+				char   *resname = NameStr(attr->attname);
+				Oid		typid = attr->atttypid;
+
+				sprintf(paramname, "$%d", i);
+				appendStringInfo(&str, "(");
+				generate_equal(&str, typid, resname, paramname);
+				appendStringInfo(&str, " OR (%s IS NULL AND %s IS NULL))",
+								 resname, paramname);
+
+				if (lnext(keys, lc))
+					appendStringInfoString(&str, " AND ");
+				i++;
+			}
+			appendStringInfo(&str, ")");
+		}
+		else
+			keyTypes = NULL;
+
+		plan = SPI_prepare(str.data, list_length(keys), keyTypes);
+		if (plan == NULL)
+			elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), str.data);
+
+		SPI_keepplan(plan);
+		mv_HashPreparedPlan(&hash_key, plan);
+	}
+
+	return plan;
+}
+
+/*
+ * get_plan_for_set_values
+ *
+ * Create or fetch a plan for applying new values calculated by
+ * get_plan_for_recalc to a materialized view specified by matviewOid.
+ * matviewname is the name of the view.  namelist is a list of resnames
+ * of attributes to be updated, and valTypes is an array of types of the
+ * values.
+ */
+static SPIPlanPtr
+get_plan_for_set_values(Oid matviewOid, char *matviewname, List *namelist,
+						Oid *valTypes)
+{
+	MV_QueryKey	key;
+	SPIPlanPtr	plan;
+
+	/* Fetch or prepare a saved plan for the real check */
+	mv_BuildQueryKey(&key, matviewOid, MV_PLAN_SET_VALUE);
+	if ((plan = mv_FetchPreparedPlan(&key)) == NULL)
+	{
+		ListCell	  *lc;
+		StringInfoData str;
+		int		i;
+
+		/*
+		 * Build a query string for applying min/max values. This is like
+		 *
+		 *  UPDATE matviewname AS mv
+		 *   SET (x1, x2, x3, x4) = ($1, $2, $3, $4)
+		 *   WHERE ctid = $5;
+		 */
+
+		initStringInfo(&str);
+		appendStringInfo(&str, "UPDATE %s AS mv SET (", matviewname);
+		foreach (lc, namelist)
+		{
+			appendStringInfo(&str, "%s", (char *) lfirst(lc));
+			if (lnext(namelist, lc))
+				appendStringInfoString(&str, ", ");
+		}
+		appendStringInfo(&str, ") = ROW(");
+
+		for (i = 1; i <= list_length(namelist); i++)
+			appendStringInfo(&str, "%s$%d", (i==1 ? "" : ", "), i);
+
+		appendStringInfo(&str, ") WHERE ctid OPERATOR(pg_catalog.=) $%d", i);
+
+		plan = SPI_prepare(str.data, list_length(namelist) + 1, valTypes);
+		if (plan == NULL)
+			elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), str.data);
+
+		SPI_keepplan(plan);
+		mv_HashPreparedPlan(&key, plan);
+	}
+
+	return plan;
+}
+
 /*
  * generate_equals
  *
@@ -2342,6 +3219,13 @@ mv_InitHashTables(void)
 {
 	HASHCTL		ctl;
 
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(MV_QueryKey);
+	ctl.entrysize = sizeof(MV_QueryHashEntry);
+	mv_query_cache = hash_create("MV query cache",
+								 MV_INIT_QUERYHASHSIZE,
+								 &ctl, HASH_ELEM | HASH_BLOBS);
+
 	memset(&ctl, 0, sizeof(ctl));
 	ctl.keysize = sizeof(Oid);
 	ctl.entrysize = sizeof(MV_TriggerHashEntry);
@@ -2350,6 +3234,99 @@ mv_InitHashTables(void)
 								 &ctl, HASH_ELEM | HASH_BLOBS);
 }
 
+/*
+ * mv_FetchPreparedPlan
+ */
+static SPIPlanPtr
+mv_FetchPreparedPlan(MV_QueryKey *key)
+{
+	MV_QueryHashEntry *entry;
+	SPIPlanPtr	plan;
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_query_cache)
+		mv_InitHashTables();
+
+	/*
+	 * Lookup for the key
+	 */
+	entry = (MV_QueryHashEntry *) hash_search(mv_query_cache,
+											  (void *) key,
+											  HASH_FIND, NULL);
+	if (entry == NULL)
+		return NULL;
+
+	/*
+	 * Check whether the plan is still valid.  If it isn't, we don't want to
+	 * simply rely on plancache.c to regenerate it; rather we should start
+	 * from scratch and rebuild the query text too.  This is to cover cases
+	 * such as table/column renames.  We depend on the plancache machinery to
+	 * detect possible invalidations, though.
+	 *
+	 * CAUTION: this check is only trustworthy if the caller has already
+	 * locked both materialized views and base tables.
+	 */
+	plan = entry->plan;
+	if (plan && SPI_plan_is_valid(plan))
+		return plan;
+
+	/*
+	 * Otherwise we might as well flush the cached plan now, to free a little
+	 * memory space before we make a new one.
+	 */
+	entry->plan = NULL;
+	if (plan)
+		SPI_freeplan(plan);
+
+	return NULL;
+}
+
+/*
+ * mv_HashPreparedPlan
+ *
+ * Add another plan to our private SPI query plan hashtable.
+ */
+static void
+mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan)
+{
+	MV_QueryHashEntry *entry;
+	bool		found;
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!mv_query_cache)
+		mv_InitHashTables();
+
+	/*
+	 * Add the new plan.  We might be overwriting an entry previously found
+	 * invalid by mv_FetchPreparedPlan.
+	 */
+	entry = (MV_QueryHashEntry *) hash_search(mv_query_cache,
+											  (void *) key,
+											  HASH_ENTER, &found);
+	Assert(!found || entry->plan == NULL);
+	entry->plan = plan;
+}
+
+/*
+ * mv_BuildQueryKey
+ *
+ * Construct a hashtable key for a prepared SPI plan for IVM.
+ */
+static void
+mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type)
+{
+	/*
+	 * We assume struct MV_QueryKey contains no padding bytes, else we'd need
+	 * to use memset to clear them.
+	 */
+	key->matview_id = matview_id;
+	key->query_type = query_type;
+}
+
 /*
  * AtAbort_IVM
  *
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index c369b3ba5e..abcc31023c 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -30,6 +30,7 @@ extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_cr
 extern void CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create);
 
 extern Query *rewriteQueryForIMMV(Query *query, List *colNames);
+extern void makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber *next_resno, List **aggs);
 
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
 
-- 
2.17.1

v27-0008-Add-regression-tests-for-Incremental-View-Mainte.patchtext/x-diff; name=v27-0008-Add-regression-tests-for-Incremental-View-Mainte.patchDownload
From 6728273fdc2d1fb6c637d63e5c0258999873238c Mon Sep 17 00:00:00 2001
From: Takuma Hoshiai <takuma.hoshiai@gmail.com>
Date: Wed, 10 Mar 2021 11:11:13 +0900
Subject: [PATCH v27 8/9] Add regression tests for Incremental View Maintenance

---
 .../regress/expected/incremental_matview.out  | 840 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/incremental_matview.sql  | 424 +++++++++
 3 files changed, 1265 insertions(+), 1 deletion(-)
 create mode 100644 src/test/regress/expected/incremental_matview.out
 create mode 100644 src/test/regress/sql/incremental_matview.sql

diff --git a/src/test/regress/expected/incremental_matview.out b/src/test/regress/expected/incremental_matview.out
new file mode 100644
index 0000000000..201af5f087
--- /dev/null
+++ b/src/test/regress/expected/incremental_matview.out
@@ -0,0 +1,840 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) WITH NO DATA;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ERROR:  materialized view "mv_ivm_1" has not been populated
+HINT:  Use the REFRESH MATERIALIZED VIEW command.
+REFRESH MATERIALIZED VIEW mv_ivm_1;
+NOTICE:  could not create an index on materialized view "mv_ivm_1" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- REFRESH WITH NO DATA
+BEGIN;
+CREATE FUNCTION dummy_ivm_trigger_func() RETURNS TRIGGER AS $$
+  BEGIN
+    RETURN NULL;
+  END
+$$ language plpgsql;
+CREATE CONSTRAINT TRIGGER dummy_ivm_trigger AFTER INSERT
+ON mv_base_a FROM mv_ivm_1 FOR EACH ROW
+EXECUTE PROCEDURE dummy_ivm_trigger_func();
+SELECT COUNT(*)
+FROM pg_depend pd INNER JOIN pg_trigger pt ON pd.objid = pt.oid
+WHERE pd.classid = 'pg_trigger'::regclass AND pd.refobjid = 'mv_ivm_1'::regclass;
+ count 
+-------
+    13
+(1 row)
+
+REFRESH MATERIALIZED VIEW mv_ivm_1 WITH NO DATA;
+SELECT COUNT(*)
+FROM pg_depend pd INNER JOIN pg_trigger pt ON pd.objid = pt.oid
+WHERE pd.classid = 'pg_trigger'::regclass AND pd.refobjid = 'mv_ivm_1'::regclass;
+ count 
+-------
+     1
+(1 row)
+
+ROLLBACK;
+-- immediate maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+ 5 | 50 | 105
+(5 rows)
+
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 |  0 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ i | j  |  k  
+---+----+-----
+ 1 | 10 | 101
+ 2 | 20 | 102
+ 3 | 30 | 103
+ 4 | 40 | 104
+(4 rows)
+
+-- rename of IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_rename AS SELECT DISTINCT * FROM mv_base_a;
+NOTICE:  created index "mv_ivm_rename_index" on materialized view "mv_ivm_rename"
+ALTER MATERIALIZED VIEW mv_ivm_rename RENAME COLUMN __ivm_count__ TO xxx;
+ERROR:  IVM column can not be renamed
+DROP MATERIALIZED VIEW mv_ivm_rename;
+-- unique index on IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_unique AS SELECT DISTINCT * FROM mv_base_a;
+NOTICE:  created index "mv_ivm_unique_index" on materialized view "mv_ivm_unique"
+CREATE UNIQUE INDEX ON mv_ivm_unique(__ivm_count__);
+ERROR:  unique index creation on IVM columns is not supported
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__));
+ERROR:  unique index creation on IVM columns is not supported
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__ + 1));
+ERROR:  unique index creation on IVM columns is not supported
+DROP MATERIALIZED VIEW mv_ivm_unique;
+-- some query syntax
+BEGIN;
+CREATE FUNCTION ivm_func() RETURNS int LANGUAGE 'sql'
+       AS 'SELECT 1' IMMUTABLE;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_func AS SELECT * FROM ivm_func();
+NOTICE:  could not create an index on materialized view "mv_ivm_func" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_no_tbl AS SELECT 1;
+NOTICE:  could not create an index on materialized view "mv_ivm_no_tbl" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+ROLLBACK;
+-- result of materialized view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_duplicate" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+NOTICE:  created index "mv_ivm_distinct_index" on materialized view "mv_ivm_distinct"
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 20
+ 30
+ 40
+ 50
+(6 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ j  
+----
+ 10
+ 20
+ 30
+ 40
+ 50
+(5 rows)
+
+ROLLBACK;
+-- support SUM(), COUNT() and AVG() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i), AVG(j) FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_agg_index" on materialized view "mv_ivm_agg"
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 | 120 |     2 | 60.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg          
+---+-----+-------+----------------------
+ 1 |  10 |     1 |  10.0000000000000000
+ 2 | 220 |     2 | 110.0000000000000000
+ 3 |  30 |     1 |  30.0000000000000000
+ 4 |  40 |     1 |  40.0000000000000000
+ 5 |  50 |     1 |  50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support COUNT(*) aggregate function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(*) FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_agg_index" on materialized view "mv_ivm_agg"
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 |  20 |     1
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ i | sum | count 
+---+-----+-------
+ 1 |  10 |     1
+ 2 | 120 |     2
+ 3 |  30 |     1
+ 4 |  40 |     1
+ 5 |  50 |     1
+(5 rows)
+
+ROLLBACK;
+-- support aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j), COUNT(j), AVG(j) FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_group" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count |         avg         
+-----+-------+---------------------
+ 150 |     5 | 30.0000000000000000
+(1 row)
+
+INSERT INTO mv_base_a VALUES(6,60);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count |         avg         
+-----+-------+---------------------
+ 210 |     6 | 35.0000000000000000
+(1 row)
+
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ sum | count | avg 
+-----+-------+-----
+     |     0 |    
+(1 row)
+
+ROLLBACK;
+-- resolved issue: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+NOTICE:  created index "mv_ivm_avg_bug_index" on materialized view "mv_ivm_avg_bug"
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     3 |  3.3333333333333333
+ 2 |  80 |     3 | 26.6666666666666667
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ i | sum | count |         avg         
+---+-----+-------+---------------------
+ 1 |  10 |     1 | 10.0000000000000000
+ 2 |  20 |     1 | 20.0000000000000000
+ 3 |  30 |     1 | 30.0000000000000000
+ 4 |  40 |     1 | 40.0000000000000000
+ 5 |  50 |     1 | 50.0000000000000000
+(5 rows)
+
+ROLLBACK;
+-- support MIN(), MAX() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT i, MIN(j), MAX(j)  FROM mv_base_a GROUP BY i;
+NOTICE:  created index "mv_ivm_min_max_index" on materialized view "mv_ivm_min_max"
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  10 |  10
+ 2 |  20 |  20
+ 3 |  30 |  30
+ 4 |  40 |  40
+ 5 |  50 |  50
+(5 rows)
+
+INSERT INTO mv_base_a VALUES
+  (1,11), (1,12),
+  (2,21), (2,22),
+  (3,31), (3,32),
+  (4,41), (4,42),
+  (5,51), (5,52);
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  10 |  12
+ 2 |  20 |  22
+ 3 |  30 |  32
+ 4 |  40 |  42
+ 5 |  50 |  52
+(5 rows)
+
+DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ i | min | max 
+---+-----+-----
+ 1 |  11 |  12
+ 2 |  20 |  22
+ 3 |  30 |  31
+ 4 |  40 |  42
+ 5 |  50 |  52
+(5 rows)
+
+ROLLBACK;
+-- support MIN(), MAX() aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT MIN(j), MAX(j)  FROM mv_base_a;
+NOTICE:  could not create an index on materialized view "mv_ivm_min_max" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+  10 |  50
+(1 row)
+
+INSERT INTO mv_base_a VALUES
+  (0,0), (6,60), (7,70);
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+   0 |  70
+(1 row)
+
+DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+  10 |  60
+(1 row)
+
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+ min | max 
+-----+-----
+     |    
+(1 row)
+
+ROLLBACK;
+-- support self join view and multiple change on the same table
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1, 10), (2, 20), (3, 30);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_self(v1, v2) AS
+ SELECT t1.v, t2.v FROM base_t AS t1 JOIN base_t AS t2 ON t1.i = t2.i;
+NOTICE:  could not create an index on materialized view "mv_self" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv_self ORDER BY v1;
+ v1 | v2 
+----+----
+ 10 | 10
+ 20 | 20
+ 30 | 30
+(3 rows)
+
+INSERT INTO base_t VALUES (4,40);
+DELETE FROM base_t WHERE i = 1;
+UPDATE base_t SET v = v*10 WHERE i=2;
+SELECT * FROM mv_self ORDER BY v1;
+ v1  | v2  
+-----+-----
+  30 |  30
+  40 |  40
+ 200 | 200
+(3 rows)
+
+WITH
+ ins_t1 AS (INSERT INTO base_t VALUES (5,50) RETURNING 1),
+ ins_t2 AS (INSERT INTO base_t VALUES (6,60) RETURNING 1),
+ upd_t AS (UPDATE base_t SET v = v + 100  RETURNING 1),
+ dlt_t AS (DELETE FROM base_t WHERE i IN (4,5)  RETURNING 1)
+SELECT NULL;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT * FROM mv_self ORDER BY v1;
+ v1  | v2  
+-----+-----
+  50 |  50
+  60 |  60
+ 130 | 130
+ 300 | 300
+(4 rows)
+
+ROLLBACK;
+-- support simultaneous table changes
+BEGIN;
+CREATE TABLE base_r (i int, v int);
+CREATE TABLE base_s (i int, v int);
+INSERT INTO base_r VALUES (1, 10), (2, 20), (3, 30);
+INSERT INTO base_s VALUES (1, 100), (2, 200), (3, 300);
+CREATE INCREMENTAL MATERIALIZED VIEW mv(v1, v2) AS
+ SELECT r.v, s.v FROM base_r AS r JOIN base_s AS s USING(i);
+NOTICE:  could not create an index on materialized view "mv" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY v1;
+ v1 | v2  
+----+-----
+ 10 | 100
+ 20 | 200
+ 30 | 300
+(3 rows)
+
+WITH
+ ins_r AS (INSERT INTO base_r VALUES (1,11) RETURNING 1),
+ ins_r2 AS (INSERT INTO base_r VALUES (3,33) RETURNING 1),
+ ins_s AS (INSERT INTO base_s VALUES (2,222) RETURNING 1),
+ upd_r AS (UPDATE base_r SET v = v + 1000 WHERE i = 2 RETURNING 1),
+ dlt_s AS (DELETE FROM base_s WHERE i = 3 RETURNING 1)
+SELECT NULL;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT * FROM mv ORDER BY v1;
+  v1  | v2  
+------+-----
+   10 | 100
+   11 | 100
+ 1020 | 200
+ 1020 | 222
+(4 rows)
+
+ROLLBACK;
+-- support foreign reference constraints
+BEGIN;
+CREATE TABLE ri1 (i int PRIMARY KEY);
+CREATE TABLE ri2 (i int PRIMARY KEY REFERENCES ri1(i) ON UPDATE CASCADE ON DELETE CASCADE, v int);
+INSERT INTO ri1 VALUES (1),(2),(3);
+INSERT INTO ri2 VALUES (1),(2),(3);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ri(i1, i2) AS
+ SELECT ri1.i, ri2.i FROM ri1 JOIN ri2 USING(i);
+NOTICE:  created index "mv_ri_index" on materialized view "mv_ri"
+SELECT * FROM mv_ri ORDER BY i1;
+ i1 | i2 
+----+----
+  1 |  1
+  2 |  2
+  3 |  3
+(3 rows)
+
+UPDATE ri1 SET i=10 where i=1;
+DELETE FROM ri1 WHERE i=2;
+SELECT * FROM mv_ri ORDER BY i2;
+ i1 | i2 
+----+----
+  3 |  3
+ 10 | 10
+(2 rows)
+
+ROLLBACK;
+-- views including NULL
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1,10),(2, NULL);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+NOTICE:  could not create an index on materialized view "mv" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY i;
+ i | v  
+---+----
+ 1 | 10
+ 2 |   
+(2 rows)
+
+UPDATE base_t SET v = 20 WHERE i = 2;
+SELECT * FROM mv ORDER BY i;
+ i | v  
+---+----
+ 1 | 10
+ 2 | 20
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+NOTICE:  could not create an index on materialized view "mv" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT * FROM mv ORDER BY i;
+ i 
+---
+(0 rows)
+
+INSERT INTO base_t VALUES (1),(NULL);
+SELECT * FROM mv ORDER BY i;
+ i 
+---
+ 1
+  
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (1, 10), (1, 20);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, sum(v) FROM base_t GROUP BY i;
+NOTICE:  created index "mv_index" on materialized view "mv"
+SELECT * FROM mv ORDER BY i;
+ i | sum 
+---+-----
+ 1 |  30
+   |   3
+(2 rows)
+
+UPDATE base_t SET v = v * 10;
+SELECT * FROM mv ORDER BY i;
+ i | sum 
+---+-----
+ 1 | 300
+   |  30
+(2 rows)
+
+ROLLBACK;
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (NULL, 3), (NULL, 4), (NULL, 5);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, min(v), max(v) FROM base_t GROUP BY i;
+NOTICE:  created index "mv_index" on materialized view "mv"
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   1 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 1;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 3;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   5
+(1 row)
+
+DELETE FROM base_t WHERE v = 5;
+SELECT * FROM mv ORDER BY i;
+ i | min | max 
+---+-----+-----
+   |   2 |   4
+(1 row)
+
+ROLLBACK;
+-- IMMV containing user defined type
+BEGIN;
+CREATE TYPE mytype;
+CREATE FUNCTION mytype_in(cstring)
+ RETURNS mytype AS 'int4in'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+NOTICE:  return type mytype is only a shell
+CREATE FUNCTION mytype_out(mytype)
+ RETURNS cstring AS 'int4out'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+NOTICE:  argument type mytype is only a shell
+CREATE TYPE mytype (
+ LIKE = int4,
+ INPUT = mytype_in,
+ OUTPUT = mytype_out
+);
+CREATE FUNCTION mytype_eq(mytype, mytype)
+ RETURNS bool AS 'int4eq'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_lt(mytype, mytype)
+ RETURNS bool AS 'int4lt'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_cmp(mytype, mytype)
+ RETURNS integer AS 'btint4cmp'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE OPERATOR = (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_eq);
+CREATE OPERATOR < (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_lt);
+CREATE OPERATOR CLASS mytype_ops
+ DEFAULT FOR TYPE mytype USING btree AS
+ OPERATOR        1       <,
+ OPERATOR        3       = ,
+ FUNCTION		1		mytype_cmp(mytype,mytype);
+CREATE TABLE t_mytype (x mytype);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_mytype AS
+ SELECT * FROM t_mytype;
+NOTICE:  could not create an index on materialized view "mv_mytype" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+INSERT INTO t_mytype VALUES ('1'::mytype);
+SELECT * FROM mv_mytype;
+ x 
+---
+ 1
+(1 row)
+
+ROLLBACK;
+-- outer join is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i;
+ERROR:  OUTER JOIN is not supported on incrementally maintainable materialized view
+-- CTE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS
+    WITH b AS ( SELECT * FROM mv_base_b) SELECT a.i,a.j FROM mv_base_a a, b WHERE a.i = b.i;
+ERROR:  CTE is not supported on incrementally maintainable materialized view
+-- contain system column
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm01 AS SELECT i,j,xmin FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT i,j,xmin::text AS x_min FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm06 AS SELECT i,j,xidsend(xmin) AS x_min FROM mv_base_a;
+ERROR:  system column is not supported on incrementally maintainable materialized view
+-- contain subquery
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm03 AS SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT * FROM mv_base_b) b WHERE a.i = b.i;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm05 AS SELECT i,j, (SELECT k FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+-- contain ORDER BY
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm07 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) ORDER BY i,j,k;
+ERROR:  ORDER BY clause is not supported on incrementally maintainable materialized view
+-- contain HAVING
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm08 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) GROUP BY i,j,k HAVING SUM(i) > 5;
+ERROR:   HAVING clause is not supported on incrementally maintainable materialized view
+-- contain view or materialized view
+CREATE VIEW b_view AS SELECT i,k FROM mv_base_b;
+CREATE MATERIALIZED VIEW b_mview AS SELECT i,k FROM mv_base_b;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm07 AS SELECT a.i,a.j FROM mv_base_a a,b_view b WHERE a.i = b.i;
+ERROR:  VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm08 AS SELECT a.i,a.j FROM mv_base_a a,b_mview b WHERE a.i = b.i;
+ERROR:  VIEW or MATERIALIZED VIEW is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm09 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm10 AS SELECT a.i,a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) OR a.i > 5;
+ERROR:  subquery is not supported on incrementally maintainable materialized view
+-- contain mutable functions
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+ERROR:  mutable function is not supported on incrementally maintainable materialized view
+HINT:  functions must be marked IMMUTABLE
+-- LIMIT/OFFSET is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm13 AS SELECT i,j FROM mv_base_a LIMIT 10 OFFSET 5;
+ERROR:  LIMIT/OFFSET clause is not supported on incrementally maintainable materialized view
+-- DISTINCT ON is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm14 AS SELECT DISTINCT ON(i) i, j FROM mv_base_a;
+ERROR:  DISTINCT ON is not supported on incrementally maintainable materialized view
+-- TABLESAMPLE clause is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm15 AS SELECT i, j FROM mv_base_a TABLESAMPLE SYSTEM(50);
+ERROR:  TABLESAMPLE clause is not supported on incrementally maintainable materialized view
+-- window functions are not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm16 AS SELECT *, cume_dist() OVER (ORDER BY i) AS rank FROM mv_base_a;
+ERROR:  window functions are not supported on incrementally maintainable materialized view
+-- aggregate function with some options is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm17 AS SELECT COUNT(*) FILTER(WHERE i < 3) FROM mv_base_a;
+ERROR:  aggregate function with FILTER clause is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm18 AS SELECT COUNT(DISTINCT i)  FROM mv_base_a;
+ERROR:  aggregate function with DISTINCT arguments is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm19 AS SELECT array_agg(j ORDER BY i DESC) FROM mv_base_a;
+ERROR:  aggregate function with ORDER clause is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm20 AS SELECT i,SUM(j) FROM mv_base_a GROUP BY GROUPING SETS((i),());
+ERROR:  GROUPING SETS, ROLLUP, or CUBE clauses is not supported on incrementally maintainable materialized view
+-- inheritance parent is not supported
+BEGIN;
+CREATE TABLE parent (i int, v int);
+CREATE TABLE child_a(options text) INHERITS(parent);
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm21 AS SELECT * FROM parent;
+ERROR:  inheritance parent is not supported on incrementally maintainable materialized view
+ROLLBACK;
+-- UNION statement is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm22 AS SELECT i,j FROM mv_base_a UNION ALL SELECT i,k FROM mv_base_b;;
+ERROR:  UNION/INTERSECT/EXCEPT statements are not supported on incrementally maintainable materialized view
+-- empty target list is not allowed with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm25 AS SELECT FROM mv_base_a;
+ERROR:  empty target list is not supported on incrementally maintainable materialized view
+-- FOR UPDATE/SHARE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm26 AS SELECT i,j FROM mv_base_a FOR UPDATE;
+ERROR:  FOR UPDATE/SHARE clause is not supported on incrementally maintainable materialized view
+-- tartget list cannot contain ivm column that start with '__ivm'
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm28 AS SELECT i AS "__ivm_count__" FROM mv_base_a;
+ERROR:  column name __ivm_count__ is not supported on incrementally maintainable materialized view
+-- expressions specified in GROUP BY must appear in the target list.
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm29 AS SELECT COUNT(i) FROM mv_base_a GROUP BY i;
+ERROR:  GROUP BY expression not appearing in select list is not supported on incrementally maintainable materialized view
+-- experssions containing an aggregate is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm30 AS SELECT sum(i)*0.5 FROM mv_base_a;
+ERROR:  expression containing an aggregate in it is not supported on incrementally maintainable materialized view
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm31 AS SELECT sum(i)/sum(j) FROM mv_base_a;
+ERROR:  expression containing an aggregate in it is not supported on incrementally maintainable materialized view
+-- VALUES is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_only_values1 AS values(1);
+ERROR:  VALUES is not supported on incrementally maintainable materialized view
+-- base table which has row level security
+DROP USER IF EXISTS ivm_admin;
+NOTICE:  role "ivm_admin" does not exist, skipping
+DROP USER IF EXISTS ivm_user;
+NOTICE:  role "ivm_user" does not exist, skipping
+CREATE USER ivm_admin;
+CREATE USER ivm_user;
+SET SESSION AUTHORIZATION ivm_admin;
+CREATE TABLE rls_tbl(id int, data text, owner name);
+INSERT INTO rls_tbl VALUES
+  (1,'foo','ivm_user'),
+  (2,'bar','postgres');
+CREATE TABLE num_tbl(id int, num text);
+INSERT INTO num_tbl VALUES
+  (1,'one'),
+  (2,'two'),
+  (3,'three'),
+  (4,'four');
+CREATE POLICY rls_tbl_policy ON rls_tbl FOR SELECT TO PUBLIC USING(owner = current_user);
+CREATE POLICY rls_tbl_policy2 ON rls_tbl FOR INSERT TO PUBLIC WITH CHECK(current_user LIKE 'ivm_%');
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+GRANT ALL on rls_tbl TO PUBLIC;
+GRANT ALL on num_tbl TO PUBLIC;
+SET SESSION AUTHORIZATION ivm_user;
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls AS SELECT * FROM rls_tbl;
+NOTICE:  could not create an index on materialized view "ivm_rls" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+ id | data |  owner   
+----+------+----------
+  1 | foo  | ivm_user
+(1 row)
+
+INSERT INTO rls_tbl VALUES
+  (3,'baz','ivm_user'),
+  (4,'qux','postgres');
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+ id | data |  owner   
+----+------+----------
+  1 | foo  | ivm_user
+  3 | baz  | ivm_user
+(2 rows)
+
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls2 AS SELECT * FROM rls_tbl JOIN num_tbl USING(id);
+NOTICE:  could not create an index on materialized view "ivm_rls2" automatically
+DETAIL:  This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
+HINT:  Create an index on the materialized view for efficient incremental maintenance.
+RESET SESSION AUTHORIZATION;
+WITH
+ x AS (UPDATE rls_tbl SET data = data || '_2' where id in (3,4)),
+ y AS (UPDATE num_tbl SET num = num || '_2' where id in (3,4))
+SELECT;
+--
+(1 row)
+
+SELECT * FROM ivm_rls2 ORDER BY 1,2,3;
+ id | data  |  owner   |   num   
+----+-------+----------+---------
+  1 | foo   | ivm_user | one
+  3 | baz_2 | ivm_user | three_2
+(2 rows)
+
+DROP TABLE rls_tbl CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to materialized view ivm_rls
+drop cascades to materialized view ivm_rls2
+DROP TABLE num_tbl CASCADE;
+DROP USER ivm_user;
+DROP USER ivm_admin;
+DROP TABLE mv_base_b CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to materialized view mv_ivm_1
+drop cascades to view b_view
+drop cascades to materialized view b_mview
+DROP TABLE mv_base_a CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 103e11483d..3027f4e78c 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -86,7 +86,7 @@ test: brin_bloom brin_multi
 # psql depends on create_am
 # amutils depends on geometry, create_index_spgist, hash_index, brin
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort create_role
+test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort create_role incremental_matview
 
 # collate.*.utf8 tests cannot be run in parallel with each other
 test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8
diff --git a/src/test/regress/sql/incremental_matview.sql b/src/test/regress/sql/incremental_matview.sql
new file mode 100644
index 0000000000..a4ce2c741e
--- /dev/null
+++ b/src/test/regress/sql/incremental_matview.sql
@@ -0,0 +1,424 @@
+-- create a table to use as a basis for views and materialized views in various combinations
+CREATE TABLE mv_base_a (i int, j int);
+INSERT INTO mv_base_a VALUES
+  (1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+CREATE TABLE mv_base_b (i int, k int);
+INSERT INTO mv_base_b VALUES
+  (1,101),
+  (2,102),
+  (3,103),
+  (4,104);
+
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_1 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) WITH NO DATA;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+REFRESH MATERIALIZED VIEW mv_ivm_1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+
+-- REFRESH WITH NO DATA
+BEGIN;
+CREATE FUNCTION dummy_ivm_trigger_func() RETURNS TRIGGER AS $$
+  BEGIN
+    RETURN NULL;
+  END
+$$ language plpgsql;
+
+CREATE CONSTRAINT TRIGGER dummy_ivm_trigger AFTER INSERT
+ON mv_base_a FROM mv_ivm_1 FOR EACH ROW
+EXECUTE PROCEDURE dummy_ivm_trigger_func();
+
+SELECT COUNT(*)
+FROM pg_depend pd INNER JOIN pg_trigger pt ON pd.objid = pt.oid
+WHERE pd.classid = 'pg_trigger'::regclass AND pd.refobjid = 'mv_ivm_1'::regclass;
+
+REFRESH MATERIALIZED VIEW mv_ivm_1 WITH NO DATA;
+
+SELECT COUNT(*)
+FROM pg_depend pd INNER JOIN pg_trigger pt ON pd.objid = pt.oid
+WHERE pd.classid = 'pg_trigger'::regclass AND pd.refobjid = 'mv_ivm_1'::regclass;
+ROLLBACK;
+
+-- immediate maintenance
+BEGIN;
+INSERT INTO mv_base_b VALUES(5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+UPDATE mv_base_a SET j = 0 WHERE i = 1;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+DELETE FROM mv_base_b WHERE (i,k) = (5,105);
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+ROLLBACK;
+SELECT * FROM mv_ivm_1 ORDER BY 1,2,3;
+
+-- rename of IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_rename AS SELECT DISTINCT * FROM mv_base_a;
+ALTER MATERIALIZED VIEW mv_ivm_rename RENAME COLUMN __ivm_count__ TO xxx;
+DROP MATERIALIZED VIEW mv_ivm_rename;
+
+-- unique index on IVM columns
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_unique AS SELECT DISTINCT * FROM mv_base_a;
+CREATE UNIQUE INDEX ON mv_ivm_unique(__ivm_count__);
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__));
+CREATE UNIQUE INDEX ON mv_ivm_unique((__ivm_count__ + 1));
+DROP MATERIALIZED VIEW mv_ivm_unique;
+
+-- some query syntax
+BEGIN;
+CREATE FUNCTION ivm_func() RETURNS int LANGUAGE 'sql'
+       AS 'SELECT 1' IMMUTABLE;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_func AS SELECT * FROM ivm_func();
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_no_tbl AS SELECT 1;
+ROLLBACK;
+
+-- result of materialized view have DISTINCT clause or the duplicate result.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_duplicate AS SELECT j FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_distinct AS SELECT DISTINCT j FROM mv_base_a;
+INSERT INTO mv_base_a VALUES(6,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+DELETE FROM mv_base_a WHERE (i,j) = (2,20);
+SELECT * FROM mv_ivm_duplicate ORDER BY 1;
+SELECT * FROM mv_ivm_distinct ORDER BY 1;
+ROLLBACK;
+
+-- support SUM(), COUNT() and AVG() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i), AVG(j) FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+UPDATE mv_base_a SET j = 200 WHERE (i,j) = (2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+DELETE FROM mv_base_a WHERE (i,j) = (2,200);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4;
+ROLLBACK;
+
+-- support COUNT(*) aggregate function
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(*) FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES(2,100);
+SELECT * FROM mv_ivm_agg ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_group AS SELECT SUM(j), COUNT(j), AVG(j) FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+INSERT INTO mv_base_a VALUES(6,60);
+SELECT * FROM mv_ivm_group ORDER BY 1;
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_group ORDER BY 1;
+ROLLBACK;
+
+-- resolved issue: When use AVG() function and values is indivisible, result of AVG() is incorrect.
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_avg_bug AS SELECT i, SUM(j), COUNT(j), AVG(j) FROM mv_base_A GROUP BY i;
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES
+  (1,0),
+  (1,0),
+  (2,30),
+  (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) = (1,0);
+DELETE FROM mv_base_a WHERE (i,j) = (2,30);
+SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support MIN(), MAX() aggregate functions
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT i, MIN(j), MAX(j)  FROM mv_base_a GROUP BY i;
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+INSERT INTO mv_base_a VALUES
+  (1,11), (1,12),
+  (2,21), (2,22),
+  (3,31), (3,32),
+  (4,41), (4,42),
+  (5,51), (5,52);
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
+SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
+ROLLBACK;
+
+-- support MIN(), MAX() aggregate functions without GROUP clause
+BEGIN;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_min_max AS SELECT MIN(j), MAX(j)  FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+INSERT INTO mv_base_a VALUES
+  (0,0), (6,60), (7,70);
+SELECT * FROM mv_ivm_min_max;
+DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
+SELECT * FROM mv_ivm_min_max;
+DELETE FROM mv_base_a;
+SELECT * FROM mv_ivm_min_max;
+ROLLBACK;
+
+-- support self join view and multiple change on the same table
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1, 10), (2, 20), (3, 30);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_self(v1, v2) AS
+ SELECT t1.v, t2.v FROM base_t AS t1 JOIN base_t AS t2 ON t1.i = t2.i;
+SELECT * FROM mv_self ORDER BY v1;
+INSERT INTO base_t VALUES (4,40);
+DELETE FROM base_t WHERE i = 1;
+UPDATE base_t SET v = v*10 WHERE i=2;
+SELECT * FROM mv_self ORDER BY v1;
+WITH
+ ins_t1 AS (INSERT INTO base_t VALUES (5,50) RETURNING 1),
+ ins_t2 AS (INSERT INTO base_t VALUES (6,60) RETURNING 1),
+ upd_t AS (UPDATE base_t SET v = v + 100  RETURNING 1),
+ dlt_t AS (DELETE FROM base_t WHERE i IN (4,5)  RETURNING 1)
+SELECT NULL;
+SELECT * FROM mv_self ORDER BY v1;
+ROLLBACK;
+
+-- support simultaneous table changes
+BEGIN;
+CREATE TABLE base_r (i int, v int);
+CREATE TABLE base_s (i int, v int);
+INSERT INTO base_r VALUES (1, 10), (2, 20), (3, 30);
+INSERT INTO base_s VALUES (1, 100), (2, 200), (3, 300);
+CREATE INCREMENTAL MATERIALIZED VIEW mv(v1, v2) AS
+ SELECT r.v, s.v FROM base_r AS r JOIN base_s AS s USING(i);
+SELECT * FROM mv ORDER BY v1;
+WITH
+ ins_r AS (INSERT INTO base_r VALUES (1,11) RETURNING 1),
+ ins_r2 AS (INSERT INTO base_r VALUES (3,33) RETURNING 1),
+ ins_s AS (INSERT INTO base_s VALUES (2,222) RETURNING 1),
+ upd_r AS (UPDATE base_r SET v = v + 1000 WHERE i = 2 RETURNING 1),
+ dlt_s AS (DELETE FROM base_s WHERE i = 3 RETURNING 1)
+SELECT NULL;
+SELECT * FROM mv ORDER BY v1;
+ROLLBACK;
+
+-- support foreign reference constraints
+BEGIN;
+CREATE TABLE ri1 (i int PRIMARY KEY);
+CREATE TABLE ri2 (i int PRIMARY KEY REFERENCES ri1(i) ON UPDATE CASCADE ON DELETE CASCADE, v int);
+INSERT INTO ri1 VALUES (1),(2),(3);
+INSERT INTO ri2 VALUES (1),(2),(3);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ri(i1, i2) AS
+ SELECT ri1.i, ri2.i FROM ri1 JOIN ri2 USING(i);
+SELECT * FROM mv_ri ORDER BY i1;
+UPDATE ri1 SET i=10 where i=1;
+DELETE FROM ri1 WHERE i=2;
+SELECT * FROM mv_ri ORDER BY i2;
+ROLLBACK;
+
+-- views including NULL
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (1,10),(2, NULL);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+SELECT * FROM mv ORDER BY i;
+UPDATE base_t SET v = 20 WHERE i = 2;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT * FROM base_t;
+SELECT * FROM mv ORDER BY i;
+INSERT INTO base_t VALUES (1),(NULL);
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (1, 10), (1, 20);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, sum(v) FROM base_t GROUP BY i;
+SELECT * FROM mv ORDER BY i;
+UPDATE base_t SET v = v * 10;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+BEGIN;
+CREATE TABLE base_t (i int, v int);
+INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (NULL, 3), (NULL, 4), (NULL, 5);
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS SELECT i, min(v), max(v) FROM base_t GROUP BY i;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 1;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 3;
+SELECT * FROM mv ORDER BY i;
+DELETE FROM base_t WHERE v = 5;
+SELECT * FROM mv ORDER BY i;
+ROLLBACK;
+
+-- IMMV containing user defined type
+BEGIN;
+
+CREATE TYPE mytype;
+CREATE FUNCTION mytype_in(cstring)
+ RETURNS mytype AS 'int4in'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_out(mytype)
+ RETURNS cstring AS 'int4out'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE TYPE mytype (
+ LIKE = int4,
+ INPUT = mytype_in,
+ OUTPUT = mytype_out
+);
+
+CREATE FUNCTION mytype_eq(mytype, mytype)
+ RETURNS bool AS 'int4eq'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_lt(mytype, mytype)
+ RETURNS bool AS 'int4lt'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+CREATE FUNCTION mytype_cmp(mytype, mytype)
+ RETURNS integer AS 'btint4cmp'
+ LANGUAGE INTERNAL STRICT IMMUTABLE;
+
+CREATE OPERATOR = (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_eq);
+CREATE OPERATOR < (
+ leftarg = mytype, rightarg = mytype,
+ procedure = mytype_lt);
+
+CREATE OPERATOR CLASS mytype_ops
+ DEFAULT FOR TYPE mytype USING btree AS
+ OPERATOR        1       <,
+ OPERATOR        3       = ,
+ FUNCTION		1		mytype_cmp(mytype,mytype);
+
+CREATE TABLE t_mytype (x mytype);
+CREATE INCREMENTAL MATERIALIZED VIEW mv_mytype AS
+ SELECT * FROM t_mytype;
+INSERT INTO t_mytype VALUES ('1'::mytype);
+SELECT * FROM mv_mytype;
+
+ROLLBACK;
+
+-- outer join is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv(a,b) AS SELECT a.i, b.i FROM mv_base_a a LEFT JOIN mv_base_b b ON a.i=b.i;
+-- CTE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv AS
+    WITH b AS ( SELECT * FROM mv_base_b) SELECT a.i,a.j FROM mv_base_a a, b WHERE a.i = b.i;
+-- contain system column
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm01 AS SELECT i,j,xmin FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT i,j,xmin::text AS x_min FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm06 AS SELECT i,j,xidsend(xmin) AS x_min FROM mv_base_a;
+-- contain subquery
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm03 AS SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 );
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm04 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT * FROM mv_base_b) b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm05 AS SELECT i,j, (SELECT k FROM mv_base_b b WHERE a.i = b.i) FROM mv_base_a a;
+-- contain ORDER BY
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm07 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) ORDER BY i,j,k;
+-- contain HAVING
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm08 AS SELECT i,j,k FROM mv_base_a a INNER JOIN mv_base_b b USING(i) GROUP BY i,j,k HAVING SUM(i) > 5;
+
+-- contain view or materialized view
+CREATE VIEW b_view AS SELECT i,k FROM mv_base_b;
+CREATE MATERIALIZED VIEW b_mview AS SELECT i,k FROM mv_base_b;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm07 AS SELECT a.i,a.j FROM mv_base_a a,b_view b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm08 AS SELECT a.i,a.j FROM mv_base_a a,b_mview b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm09 AS SELECT a.i,a.j FROM mv_base_a a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm10 AS SELECT a.i,a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) OR a.i > 5;
+
+-- contain mutable functions
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+
+-- LIMIT/OFFSET is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm13 AS SELECT i,j FROM mv_base_a LIMIT 10 OFFSET 5;
+
+-- DISTINCT ON is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm14 AS SELECT DISTINCT ON(i) i, j FROM mv_base_a;
+
+-- TABLESAMPLE clause is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm15 AS SELECT i, j FROM mv_base_a TABLESAMPLE SYSTEM(50);
+
+-- window functions are not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm16 AS SELECT *, cume_dist() OVER (ORDER BY i) AS rank FROM mv_base_a;
+
+-- aggregate function with some options is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm17 AS SELECT COUNT(*) FILTER(WHERE i < 3) FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm18 AS SELECT COUNT(DISTINCT i)  FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm19 AS SELECT array_agg(j ORDER BY i DESC) FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm20 AS SELECT i,SUM(j) FROM mv_base_a GROUP BY GROUPING SETS((i),());
+
+-- inheritance parent is not supported
+BEGIN;
+CREATE TABLE parent (i int, v int);
+CREATE TABLE child_a(options text) INHERITS(parent);
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm21 AS SELECT * FROM parent;
+ROLLBACK;
+
+-- UNION statement is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm22 AS SELECT i,j FROM mv_base_a UNION ALL SELECT i,k FROM mv_base_b;;
+
+-- empty target list is not allowed with IVM
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm25 AS SELECT FROM mv_base_a;
+
+-- FOR UPDATE/SHARE is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm26 AS SELECT i,j FROM mv_base_a FOR UPDATE;
+
+-- tartget list cannot contain ivm column that start with '__ivm'
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm28 AS SELECT i AS "__ivm_count__" FROM mv_base_a;
+
+-- expressions specified in GROUP BY must appear in the target list.
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm29 AS SELECT COUNT(i) FROM mv_base_a GROUP BY i;
+
+-- experssions containing an aggregate is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm30 AS SELECT sum(i)*0.5 FROM mv_base_a;
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm31 AS SELECT sum(i)/sum(j) FROM mv_base_a;
+
+-- VALUES is not supported
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_only_values1 AS values(1);
+
+-- base table which has row level security
+DROP USER IF EXISTS ivm_admin;
+DROP USER IF EXISTS ivm_user;
+CREATE USER ivm_admin;
+CREATE USER ivm_user;
+SET SESSION AUTHORIZATION ivm_admin;
+
+CREATE TABLE rls_tbl(id int, data text, owner name);
+INSERT INTO rls_tbl VALUES
+  (1,'foo','ivm_user'),
+  (2,'bar','postgres');
+CREATE TABLE num_tbl(id int, num text);
+INSERT INTO num_tbl VALUES
+  (1,'one'),
+  (2,'two'),
+  (3,'three'),
+  (4,'four');
+CREATE POLICY rls_tbl_policy ON rls_tbl FOR SELECT TO PUBLIC USING(owner = current_user);
+CREATE POLICY rls_tbl_policy2 ON rls_tbl FOR INSERT TO PUBLIC WITH CHECK(current_user LIKE 'ivm_%');
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+GRANT ALL on rls_tbl TO PUBLIC;
+GRANT ALL on num_tbl TO PUBLIC;
+
+SET SESSION AUTHORIZATION ivm_user;
+
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls AS SELECT * FROM rls_tbl;
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+INSERT INTO rls_tbl VALUES
+  (3,'baz','ivm_user'),
+  (4,'qux','postgres');
+SELECT id, data, owner FROM ivm_rls ORDER BY 1,2,3;
+CREATE INCREMENTAL MATERIALIZED VIEW  ivm_rls2 AS SELECT * FROM rls_tbl JOIN num_tbl USING(id);
+
+RESET SESSION AUTHORIZATION;
+
+WITH
+ x AS (UPDATE rls_tbl SET data = data || '_2' where id in (3,4)),
+ y AS (UPDATE num_tbl SET num = num || '_2' where id in (3,4))
+SELECT;
+SELECT * FROM ivm_rls2 ORDER BY 1,2,3;
+
+DROP TABLE rls_tbl CASCADE;
+DROP TABLE num_tbl CASCADE;
+DROP USER ivm_user;
+DROP USER ivm_admin;
+
+DROP TABLE mv_base_b CASCADE;
+DROP TABLE mv_base_a CASCADE;
-- 
2.17.1

v27-0009-Add-documentations-about-Incremental-View-Mainte.patchtext/x-diff; name=v27-0009-Add-documentations-about-Incremental-View-Mainte.patchDownload
From 92de414a2c153ada64eff69d66e1815dcef16a71 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 20 Dec 2019 10:25:34 +0900
Subject: [PATCH v27 9/9] Add documentations about Incremental View Maintenance

---
 doc/src/sgml/catalogs.sgml                    |  24 +-
 .../sgml/ref/create_materialized_view.sgml    | 131 +++++-
 .../sgml/ref/refresh_materialized_view.sgml   |   8 +-
 doc/src/sgml/rules.sgml                       | 443 ++++++++++++++++++
 4 files changed, 601 insertions(+), 5 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index a533a2153e..2561faa163 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2193,6 +2193,15 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>relisivm</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if materialized view enables incremental view maintenance
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>relrewrite</structfield> <type>oid</type>
@@ -3508,6 +3517,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para>
      </listitem>
     </varlistentry>
+
+    <varlistentry>
+     <term><symbol>DEPENDENCY_IMMV</symbol> (<literal>m</literal>)</term>
+     <listitem>
+      <para>
+       The dependent object was created as part of creation of the Materialized
+       View with Incremental View Maintenance reference, and is really just a 
+       part of its internal implementation. The dependent object must not be
+       dropped unless the materialized view is also dropped.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
 
    Other dependency flavors might be needed in future.
@@ -3891,7 +3912,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <structfield>extrelocatable</structfield> <type>bool</type>
       </para>
       <para>
-       True if extension can be relocated to another schema
+      True for materialized views which are enabled for incremental
+      view maintenance (IVM).
       </para></entry>
      </row>
 
diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml
index 0d2fea2b97..52a4d206b1 100644
--- a/doc/src/sgml/ref/create_materialized_view.sgml
+++ b/doc/src/sgml/ref/create_materialized_view.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
+CREATE [ INCREMENTAL ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
     [ (<replaceable>column_name</replaceable> [, ...] ) ]
     [ USING <replaceable class="parameter">method</replaceable> ]
     [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
@@ -60,6 +60,132 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
   <title>Parameters</title>
 
   <variablelist>
+   <varlistentry>
+    <term><literal>INCREMENTAL</literal></term>
+    <listitem>
+     <para>
+      If specified, some triggers are automatically created so that the rows
+      of the materialized view are immediately updated when base tables of the
+      materialized view are updated. In general, this allows faster update of
+      the materialized view at a price of slower update of the base tables
+      because the triggers will be invoked. We call this form of materialized
+      view as "Incrementally Maintainable Materialized View" (IMMV).
+     </para>
+     <para>
+      When <acronym>IMMV</acronym> is defined without using <command>WITH NO DATA</command>,
+      a unique index is created on the view automatically if possible.  If the view
+      definition query has a GROUP BY clause, a unique index is created on the columns
+      of GROUP BY expressions.  Also, if the view has DISTINCT clause, a unique index
+      is created on all columns in the target list.  Otherwise, if the view contains all
+      primary key attritubes of its base tables in the target list, a unique index is
+      created on these attritubes.  In other cases, no index is created.
+     </para>
+     <para>
+      There are restrictions of query definitions allowed to use this
+      option. The following are supported in query definitions for IMMV:
+      <itemizedlist>
+
+       <listitem>
+        <para>
+         Inner joins (including self-joins).
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Some built-in aggregate functions (count, sum, avg, min, max) without a HAVING
+         clause. 
+        </para>
+        </listitem>
+      </itemizedlist>
+
+      Unsupported queries with this option include the following:
+
+      <itemizedlist>
+       <listitem>
+        <para>
+         Outer joins.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Sub-queries.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Aggregate functions other than built-in count, sum, avg, min and max.
+        </para>
+       </listitem>
+       <listitem>
+        <para>
+         Aggregate functions with a HAVING clause.
+        </para>
+       </listitem>
+       <listitem>
+        <para>
+         DISTINCT ON, WINDOW, VALUES, LIMIT and OFFSET clause.
+        </para>
+       </listitem>
+      </itemizedlist>
+
+      Other restrictions include:
+      <itemizedlist>
+
+       <listitem>
+        <para>
+         IMMVs must be based on simple base tables. It's not supported to
+         create them on top of views or materialized views.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         When the TRUNCATE command is executed on a base table,
+         no changes are made to the materialized view.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         It is not supported to include system columns in an IMMV.
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+ERROR:  system column is not supported with IVM
+         </programlisting>
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Non-immutable functions are not supported.
+         <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW  mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+ERROR:  functions in IMMV must be marked IMMUTABLE
+         </programlisting>
+        </para>
+        </listitem>
+
+       <listitem>
+        <para>
+         IMMVs do not support expressions that contains aggregates
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         Logical replication does not support IMMVs.
+        </para>
+       </listitem>
+
+      </itemizedlist>
+
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>IF NOT EXISTS</literal></term>
     <listitem>
@@ -155,7 +281,8 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
       This clause specifies whether or not the materialized view should be
       populated at creation time.  If not, the materialized view will be
       flagged as unscannable and cannot be queried until <command>REFRESH
-      MATERIALIZED VIEW</command> is used.
+      MATERIALIZED VIEW</command> is used.  Also, if the view is IMMV,
+      triggers for maintaining the view are not created.
      </para>
     </listitem>
    </varlistentry>
diff --git a/doc/src/sgml/ref/refresh_materialized_view.sgml b/doc/src/sgml/ref/refresh_materialized_view.sgml
index 675d6090f3..c29cfc19b6 100644
--- a/doc/src/sgml/ref/refresh_materialized_view.sgml
+++ b/doc/src/sgml/ref/refresh_materialized_view.sgml
@@ -35,9 +35,13 @@ REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] <replaceable class="parameter">name</
    owner of the materialized view.  The old contents are discarded.  If
    <literal>WITH DATA</literal> is specified (or defaults) the backing query
    is executed to provide the new data, and the materialized view is left in a
-   scannable state.  If <literal>WITH NO DATA</literal> is specified no new
+   scannable state.  If the view is an incrementally maintainable materialized
+   view (IMMV) and was unpopulated, triggers for maintaining the view are
+   created. Also, a unique index is created for IMMV if it is possible and the
+   view doesn't have that yet.
+   If <literal>WITH NO DATA</literal> is specified no new
    data is generated and the materialized view is left in an unscannable
-   state.
+   state.  If the view is IMMV, the triggers are dropped.
   </para>
   <para>
    <literal>CONCURRENTLY</literal> and <literal>WITH NO DATA</literal> may not
diff --git a/doc/src/sgml/rules.sgml b/doc/src/sgml/rules.sgml
index 4b2ba5a4e6..71e10ef5a1 100644
--- a/doc/src/sgml/rules.sgml
+++ b/doc/src/sgml/rules.sgml
@@ -1090,6 +1090,449 @@ SELECT word FROM words ORDER BY word &lt;-&gt; 'caterpiler' LIMIT 10;
 
 </sect1>
 
+<sect1 id="rules-ivm">
+<title>Incremental View Maintenance</title>
+
+<indexterm zone="rules-ivm">
+ <primary>incremental view maintenance</primary>
+</indexterm>
+
+<indexterm zone="rules-ivm">
+ <primary>materialized view</primary>
+ <secondary>incremental view maintenance</secondary>
+</indexterm>
+
+<indexterm zone="rules-ivm">
+ <primary>view</primary>
+ <secondary>incremental view maintenance</secondary>
+</indexterm>
+
+<sect2 id="rules-ivm-overview">
+<title>Overview</title>
+
+<para>
+    Incremental View Maintenance (<acronym>IVM</acronym>) is a way to make
+    materialized views up-to-date in which only incremental changes are computed
+    and applied on views rather than recomputing the contents from scratch as
+    <command>REFRESH MATERIALIZED VIEW</command> does.  <acronym>IVM</acronym>
+    can update materialized views more efficiently than recomputation when only
+    small parts of the view are changed.
+</para>
+
+<para>
+    There are two approaches with regard to timing of view maintenance:
+    immediate and deferred.  In immediate maintenance, views are updated in the
+    same transaction that its base table is modified.  In deferred maintenance,
+    views are updated after the transaction is committed, for example, when the
+    view is accessed, as a response to user command like <command>REFRESH
+    MATERIALIZED VIEW</command>, or periodically in background, and so on.
+    <productname>PostgreSQL</productname> currently implements only a kind of
+    immediate maintenance, in which materialized views are updated immediately
+    in AFTER triggers when a base table is modified.
+</para>
+
+<para>
+    To create materialized views supporting <acronym>IVM</acronym>, use the
+    <command>CREATE INCREMENTAL MATERIALIZED VIEW</command>, for example:
+<programlisting>
+CREATE <emphasis>INCREMENTAL</emphasis> MATERIALIZED VIEW mymatview AS SELECT * FROM mytab;
+</programlisting>
+    When a materialized view is created with the <literal>INCREMENTAL</literal>
+    keyword, some triggers are automatically created so that the view's contents are
+    immediately updated when its base tables are modified. We call this form
+    of materialized view an Incrementally Maintainable Materialized View
+    (<acronym>IMMV</acronym>).
+<programlisting>
+postgres=# CREATE INCREMENTAL MATERIALIZED VIEW m AS SELECT * FROM t0;
+NOTICE:  could not create an index on materialized view "m" automatically
+HINT:  Create an index on the materialized view for effcient incremental maintenance.
+SELECT 3
+postgres=# SELECT * FROM m;
+ i
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+postgres=# INSERT INTO t0 VALUES (4);
+INSERT 0 1
+postgres=# SELECT * FROM m; -- automatically updated
+ i
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+</programlisting>
+</para>
+
+<para>
+    Some <acronym>IMMV</acronym>s have hidden columns which are added
+    automatically when a materialized view is created. Their name starts
+    with <literal>__ivm_</literal> and they contain information required
+    for maintaining the <acronym>IMMV</acronym>. Such columns are not visible
+    when the <acronym>IMMV</acronym> is accessed by <literal>SELECT *</literal>
+    but are visible if the column name is explicitly specified in the target
+    list. We can also see the hidden columns in <literal>\d</literal>
+    meta-commands of <command>psql</command> commands.
+</para>
+
+<para>
+    In general, <acronym>IMMV</acronym>s allow faster updates of materialized
+    views at the price of slower updates to their base tables. Updates of
+    <acronym>IMMV</acronym> is slower because triggers will be invoked and the
+    view is updated in triggers per modification statement.
+</para>
+
+<para>
+    For example, suppose a normal materialized view defined as below:
+
+<programlisting>
+test=# CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm AS
+        SELECT a.aid, b.bid, a.abalance, b.bbalance
+        FROM pgbench_accounts a JOIN pgbench_branches b USING(bid);
+SELECT 10000000
+
+</programlisting>
+
+    Updating a tuple in a base table of this materialized view is rapid but the
+   <command>REFRESH MATERIALIZED VIEW</command> command on this view takes a long time:
+
+<programlisting>
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 0.990 ms
+
+test=# REFRESH MATERIALIZED VIEW mv_normal ;
+REFRESH MATERIALIZED VIEW
+Time: 33533.952 ms (00:33.534)
+</programlisting>
+</para>
+
+<para>
+    On the other hand, after creating <acronym>IMMV</acronym> with the same view
+    definition as below:
+
+<programlisting>
+test=# CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm AS
+        SELECT a.aid, b.bid, a.abalance, b.bbalance
+        FROM pgbench_accounts a JOIN pgbench_branches b USING(bid);
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+NOTICE:  created index "mv_ivm_index" on materialized view "mv_ivm"
+</programlisting>
+
+    updating a tuple in a base table takes more than the normal view,
+    but its content is updated automatically and this is faster than the
+    <command>REFRESH MATERIALIZED VIEW</command> command.
+
+<programlisting>
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 13.068 ms
+</programlisting>
+
+</para>
+
+<para>
+    Appropriate indexes on <acronym>IMMV</acronym>s are necessary for
+    efficient <acronym>IVM</acronym> because it looks for tuples to be
+    updated in <acronym>IMMV</acronym>.  If there are no indexes, it
+    will take a long time.
+</para>
+
+<para>
+    Therefore, when <acronym>IMMV</acronym> is defined, a unique index is created on the view
+    automatically if possible.  If the view definition query has a GROUP BY clause, a unique
+    index is created on the columns of GROUP BY expressions.  Also, if the view has DISTINCT
+    clause, a unique index is created on all columns in the target list. Otherwise, if the
+    view contains all primary key attritubes of its base tables in the target list, a unique
+    index is created on these attritubes.  In other cases, no index is created.
+</para>
+
+<para>
+    In the previous example, a unique index "mv_ivm_index" is created on aid and bid
+    columns of materialized view "mv_ivm", and this enables the rapid update of the view.
+    Dropping this index make updating the view take a loger time.
+<programlisting>
+test=# DROP INDEX mv_ivm_index;
+DROP INDEX
+Time: 67.081 ms
+
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 16386.245 ms (00:16.386)
+</programlisting>
+
+</para>
+
+<para>
+    <acronym>IVM</acronym> is effective when we want to keep a materialized
+    view up-to-date and small fraction of a base table is modified
+    infrequently.  Due to the overhead of immediate maintenance, <acronym>IVM</acronym>
+    is not effective when a base table is modified frequently.  Also, when a
+    large part of a base table is modified or large data is inserted into a
+    base table, <acronym>IVM</acronym> is not effective and the cost of
+    maintenance can be larger than the <command>REFRESH MATERIALIZED VIEW</command>
+    command. In such situation, we can use <command>REFRESH MATERIALIZED VIEW</command>
+    and specify <literal>WITH NO DATA</literal> to disable immediate
+    maintenance before modifying a base table. After a base table modification,
+    execute the <command>REFRESH MATERIALIZED VIEW</command> (with <literal>WITH DATA</literal>)
+    command to refresh the view data and enable immediate maintenance.
+</para>
+
+</sect2>
+
+<sect2>
+<title>Supported View Definitions and Restrictions</title>
+
+<para>
+    Currently, we can create <acronym>IMMV</acronym>s using inner joins, and some
+    aggregates. However, several restrictions apply to the definition of IMMV.
+</para>
+
+<sect3>
+<title>Joins</title>
+<para>
+    Inner joins including self-join are supported. Outer joins are not supported.
+</para>
+</sect3>
+
+<sect3>
+<title>Aggregates</title>
+<para>
+    Supported aggregate functions are <function>count</function>, <function>sum</function>,
+    <function>avg</function>, <function>min</function>, and <function>max</function>.
+    Currently, only built-in aggregate functions are supported and user defined
+    aggregates cannot be used.  When a base table is modified, the new aggregated
+    values are incrementally calculated using the old aggregated values and values
+    of related hidden columns stored in <acronym>IMMV</acronym>.
+</para>
+
+<para>
+     Note that for <function>min</function> or <function>max</function>, the new values
+     could be re-calculated from base tables with regard to the affected groups when a
+     tuple containing the current minimal or maximal values are deleted from a base table.
+     Therefore, it can takes a long time to update an <acronym>IMMV</acronym> containing
+     these functions.
+</para>
+
+<para>
+    Also note that using <function>sum</function> or <function>avg</function> on
+    <type>real</type> (<type>float4</type>) type or <type>double precision</type>
+    (<type>float8</type>) type in <acronym>IMMV</acronym> is unsafe. This is
+    because aggregated values in <acronym>IMMV</acronym> can become different from
+    results calculated from base tables due to the limited precision of these types.
+    To avoid this problem, use the <type>numeric</type> type instead.
+</para>
+
+    <sect4>
+    <title>Restrictions on Aggregates</title>
+    <para>
+        There are the following restrictions:
+    <itemizedlist>
+        <listitem>
+        <para>
+            If we have a <literal>GROUP BY</literal> clause, expressions specified in
+               <literal>GROUP BY</literal> must appear in the target list.  This is
+               how tuples to be updated in the <acronym>IMMV</acronym> are identified.
+               These attributes are used as scan keys for searching tuples in the
+               <acronym>IMMV</acronym>, so indexes on them are required for efficient
+               <acronym>IVM</acronym>.
+        </para>
+        </listitem>
+
+        <listitem>
+        <para>
+            <literal>HAVING</literal> clause cannot be used.
+        </para>
+        </listitem>
+    </itemizedlist>
+    </para>
+    </sect4>
+</sect3>
+
+<sect3>
+<title>Other General Restrictions</title>
+<para>
+    There are other restrictions which generally apply to <acronym>IMMV</acronym>:
+    <itemizedlist>
+        <listitem>
+          <para>
+           Sub-queries cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+           CTEs cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+           Window functions cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s must be based on simple base tables.  It's not
+               supported to create them on top of views, materialized views, foreign tables, inhe.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            LIMIT and OFFSET clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s cannot contain system columns.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <acronym>IMMV</acronym>s cannot contain non-immutable functions.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            UNION/INTERSECT/EXCEPT clauses cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            DISTINCT ON clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            TABLESAMPLE parameter cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            inheritance parent tables cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            VALUES clause cannnot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            <literal>GROUPING SETS</literal> and <literal>FILTER</literal> clauses cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            FOR UPDATE/SHARE cannot be used.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            targetlist cannot contain columns whose name start with <literal>__ivm_</literal>.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            targetlist cannot contain expressions which contain an aggregate in it.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+              Logical replication is not supported, that is, even when a base table
+               at a publisher node is modified, <acronym>IMMV</acronym>s at subscriber
+               nodes are not updated.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            When the <literal>TRUNCATE</literal> command is executed on a base table,
+               nothing is changed on the <acronym>IMMV</acronym>.
+          </para>
+        </listitem>
+
+    </itemizedlist>
+</para>
+</sect3>
+
+</sect2>
+
+<sect2>
+<title><literal>DISTINCT</literal></title>
+
+<para>
+    <productname>PostgreSQL</productname> supports <acronym>IMMV</acronym> with
+    <literal>DISTINCT</literal>.  For example, suppose a <acronym>IMMV</acronym>
+    defined with <literal>DISTINCT</literal> on a base table containing duplicate
+    tuples.  When tuples are deleted from the base table, a tuple in the view is
+    deleted if and only if the multiplicity of the tuple becomes zero.  Moreover,
+    when tuples are inserted into the base table, a tuple is inserted into the
+    view only if the same tuple doesn't already exist in it.
+</para>
+
+<para>
+    Physically, an <acronym>IMMV</acronym> defined with <literal>DISTINCT</literal>
+    contains tuples after eliminating duplicates, and the multiplicity of each tuple
+    is stored in a hidden column named <literal>__ivm_count__</literal>.
+</para>
+</sect2>
+
+<sect2>
+<title>Concurrent Transactions</title>
+<para>
+    Suppose an <acronym>IMMV</acronym> is defined on two base tables and each
+    table was modified in different a concurrent transaction simultaneously.
+    In the transaction which was committed first, <acronym>IMMV</acronym> can
+    be updated considering only the change which happened in this transaction.
+    On the other hand, in order to update the view correctly in the transaction
+    which was committed later, we need to know the changes occurred in
+    both transactions.  For this reason, <literal>ExclusiveLock</literal>
+    is held on an <acronym>IMMV</acronym> immediately after a base table is
+    modified in <literal>READ COMMITTED</literal> mode to make sure that
+    the <acronym>IMMV</acronym> is updated in the latter transaction after
+    the former transaction is committed.  In <literal>REPEATABLE READ</literal>
+    or <literal>SERIALIZABLE</literal> mode, an error is raised immediately
+    if lock acquisition fails because any changes which occurred in
+    other transactions are not be visible in these modes and
+    <acronym>IMMV</acronym> cannot be updated correctly in such situations.
+    However, as an exception if the view has only one base table, 
+    the lock held on thew view is <literal>RowExclusiveLock</literal>.
+</para>
+</sect2>
+
+<sect2>
+<title>Row Level Security</title>
+<para>
+    If some base tables have row level security policy, rows that are not visible
+    to the materialized view's owner are excluded from the result.  In addition, such
+    rows are excluded as well when views are incrementally maintained.  However, if a
+    new policy is defined or policies are changed after the materialized view was created,
+    the new policy will not be applied to the view contents.  To apply the new policy,
+    you need to refresh materialized views.
+</para>
+</sect2>
+
+</sect1>
+
 <sect1 id="rules-update">
 <title>Rules on <command>INSERT</command>, <command>UPDATE</command>, and <command>DELETE</command></title>
 
-- 
2.17.1

#221Greg Stark
stark@mit.edu
In reply to: Yugo NAGATA (#220)
Re: Implementing Incremental View Maintenance

I'm trying to figure out how to get this feature more attention. Everyone
agrees it would be a huge help but it's a scary patch to review.

I wonder if it would be helpful to have a kind of "readers guide"
explanation of the patches to help a reviewer understand what the point of
each patch is and how the whole system works? I think Andres and Robert
have both taken that approach before with big patches and it really helped
imho.

On Fri., Apr. 22, 2022, 08:01 Yugo NAGATA, <nagata@sraoss.co.jp> wrote:

Show quoted text

On Fri, 22 Apr 2022 11:29:39 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi,

On Fri, 1 Apr 2022 11:09:16 -0400
Greg Stark <stark@mit.edu> wrote:

This patch has bitrotted due to some other patch affecting trigger.c.

Could you post a rebase?

This is the last week of the CF before feature freeze so time is of

the essence.

I attached a rebased patch-set.

Also, I made the folowing changes from the previous.

1. Fix to not use a new deptye

In the previous patch, we introduced a new deptye 'm' into pg_depend.
This deptype was used for looking for IVM triggers to be removed at
REFRESH WITH NO DATA. However, we decided to not use it for reducing
unnecessary change in the core code. Currently, the trigger name and
dependent objclass are used at that time instead of it.

As a result, the number of patches are reduced to nine from ten.

2. Bump the version numbers in psql and pg_dump

This feature's target is PG 16 now.

Sorry, I revert this change. It was too early to bump up the
version number.

--
Yugo NAGATA <nagata@sraoss.co.jp>

#222Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Greg Stark (#221)
Re: Implementing Incremental View Maintenance

Hello Greg,

On Sat, 23 Apr 2022 08:18:01 +0200
Greg Stark <stark@mit.edu> wrote:

I'm trying to figure out how to get this feature more attention. Everyone
agrees it would be a huge help but it's a scary patch to review.

I wonder if it would be helpful to have a kind of "readers guide"
explanation of the patches to help a reviewer understand what the point of
each patch is and how the whole system works? I think Andres and Robert
have both taken that approach before with big patches and it really helped
imho.

Thank you very much for your suggestion!

Following your advice, I am going to write a readers guide referring to the past
posts of Andres and Rebert.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#223huyajun
hu_yajun@qq.com
In reply to: Yugo NAGATA (#220)
Re: Implementing Incremental View Maintenance

2022年4月22日 下午1:58,Yugo NAGATA <nagata@sraoss.co.jp> 写道:

On Fri, 22 Apr 2022 11:29:39 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:

Hi,

On Fri, 1 Apr 2022 11:09:16 -0400
Greg Stark <stark@mit.edu> wrote:

This patch has bitrotted due to some other patch affecting trigger.c.

Could you post a rebase?

This is the last week of the CF before feature freeze so time is of the essence.

I attached a rebased patch-set.

Also, I made the folowing changes from the previous.

1. Fix to not use a new deptye

In the previous patch, we introduced a new deptye 'm' into pg_depend.
This deptype was used for looking for IVM triggers to be removed at
REFRESH WITH NO DATA. However, we decided to not use it for reducing
unnecessary change in the core code. Currently, the trigger name and
dependent objclass are used at that time instead of it.

As a result, the number of patches are reduced to nine from ten.

2. Bump the version numbers in psql and pg_dump

This feature's target is PG 16 now.

Sorry, I revert this change. It was too early to bump up the
version number.

--
Yugo NAGATA <nagata@sraoss.co.jp>

<v27-0001-Add-a-syntax-to-create-Incrementally-Maintainabl.patch><v27-0002-Add-relisivm-column-to-pg_class-system-catalog.patch><v27-0003-Allow-to-prolong-life-span-of-transition-tables-.patch><v27-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patch><v27-0005-Add-Incremental-View-Maintenance-support-to-psql.patch><v27-0006-Add-Incremental-View-Maintenance-support.patch><v27-0007-Add-aggregates-support-in-IVM.patch><v27-0008-Add-regression-tests-for-Incremental-View-Mainte.patch><v27-0009-Add-documentations-about-Incremental-View-Mainte.patch>

Hi, Nagata-san
I read your patch with v27 version and has some new comments,I want to discuss with you.

1. How about use DEPENDENCY_INTERNAL instead of DEPENDENCY_AUTO
when record dependence on trigger created by IMV.( related code is in the end of CreateIvmTrigger)
Otherwise, User can use sql to drop trigger and corrupt IVM, DEPENDENCY_INTERNAL is also semantically more correct
Crash case like:
create table t( a int);
create incremental materialized view s as select * from t;
drop trigger "IVM_trigger_XXXX”;
Insert into t values(1);

2. In get_matching_condition_string, Considering NULL values, we can not use simple = operator.
But how about 'record = record', record_eq treat NULL = NULL
it should fast than current implementation for only one comparation
Below is my simple implementation with this, Variables are named arbitrarily..
I test some cases it’s ok

static char *
get_matching_condition_string(List *keys)
{
StringInfoData match_cond;
ListCell *lc;

/* If there is no key columns, the condition is always true. */
if (keys == NIL)
return "true";
else
{
StringInfoData s1;
StringInfoData s2;
initStringInfo(&match_cond);
initStringInfo(&s1);
initStringInfo(&s2);
/* Considering NULL values, we can not use simple = operator. */
appendStringInfo(&s1, "ROW(");
appendStringInfo(&s2, "ROW(");
foreach (lc, keys)
{
Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
char *resname = NameStr(attr->attname);
char *mv_resname = quote_qualified_identifier("mv", resname);
char *diff_resname = quote_qualified_identifier("diff", resname);

appendStringInfo(&s1, "%s", mv_resname);
appendStringInfo(&s2, "%s", diff_resname);

if (lnext(lc))
{
appendStringInfo(&s1, ", ");
appendStringInfo(&s2, ", ");
}
}
appendStringInfo(&s1, ")::record");
appendStringInfo(&s2, ")::record");
appendStringInfo(&match_cond, "%s operator(pg_catalog.=) %s", s1.data, s2.data);
return match_cond.data;
}
}

3. Consider truncate base tables, IVM will not refresh, maybe raise an error will be better

4. In IVM_immediate_before,I know Lock base table with ExclusiveLock is
for concurrent updates to the IVM correctly, But how about to Lock it when actually
need to maintain MV which in IVM_immediate_maintenance
In this way you don't have to lock multiple times.

5. Why we need CreateIndexOnIMMV, is it a optimize?
It seems like when maintenance MV,
the index may not be used because of our match conditions can’t use simple = operator

Looking forward to your early reply to answer my above doubts, thank you a lot!
Regards,
Yajun Hu

#224Yugo NAGATA
nagata@sraoss.co.jp
In reply to: huyajun (#223)
Re: Implementing Incremental View Maintenance

Hi huyajun,

Thank you for your comments!

On Wed, 29 Jun 2022 17:56:39 +0800
huyajun <hu_yajun@qq.com> wrote:

Hi, Nagata-san
I read your patch with v27 version and has some new comments,I want to discuss with you.

1. How about use DEPENDENCY_INTERNAL instead of DEPENDENCY_AUTO
when record dependence on trigger created by IMV.( related code is in the end of CreateIvmTrigger)
Otherwise, User can use sql to drop trigger and corrupt IVM, DEPENDENCY_INTERNAL is also semantically more correct
Crash case like:
create table t( a int);
create incremental materialized view s as select * from t;
drop trigger "IVM_trigger_XXXX”;
Insert into t values(1);

We use DEPENDENCY_AUTO because we want to delete the triggers when
REFRESH ... WITH NO DATA is performed on the materialized view in order
to disable IVM. Triggers created with DEPENDENCY_INTERNAL cannot be dropped.
Such triggers are re-created when REFRESH ... [WITH DATA] is performed.

We can use DEPENDENCY_INTERNAL if we disable/enable such triggers instead of
dropping/re-creating them, although users also can disable triggers using
ALTER TRIGGER.

2. In get_matching_condition_string, Considering NULL values, we can not use simple = operator.
But how about 'record = record', record_eq treat NULL = NULL
it should fast than current implementation for only one comparation
Below is my simple implementation with this, Variables are named arbitrarily..
I test some cases it’s ok

static char *
get_matching_condition_string(List *keys)
{
StringInfoData match_cond;
ListCell *lc;

/* If there is no key columns, the condition is always true. */
if (keys == NIL)
return "true";
else
{
StringInfoData s1;
StringInfoData s2;
initStringInfo(&match_cond);
initStringInfo(&s1);
initStringInfo(&s2);
/* Considering NULL values, we can not use simple = operator. */
appendStringInfo(&s1, "ROW(");
appendStringInfo(&s2, "ROW(");
foreach (lc, keys)
{
Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
char *resname = NameStr(attr->attname);
char *mv_resname = quote_qualified_identifier("mv", resname);
char *diff_resname = quote_qualified_identifier("diff", resname);

appendStringInfo(&s1, "%s", mv_resname);
appendStringInfo(&s2, "%s", diff_resname);

if (lnext(lc))
{
appendStringInfo(&s1, ", ");
appendStringInfo(&s2, ", ");
}
}
appendStringInfo(&s1, ")::record");
appendStringInfo(&s2, ")::record");
appendStringInfo(&match_cond, "%s operator(pg_catalog.=) %s", s1.data, s2.data);
return match_cond.data;
}
}

As you say, we don't have to use IS NULL if we use ROW(...)::record, but we
cannot use an index in this case and it makes IVM ineffecient. As showed
bellow (#5), an index works even when we use simple = operations together
with together "IS NULL" operations.

3. Consider truncate base tables, IVM will not refresh, maybe raise an error will be better

I fixed to support TRUNCATE on base tables in our repository.
https://github.com/sraoss/pgsql-ivm/commit/a1365ed69f34e1adbd160f2ce8fd1e80e032392f

When a base table is truncated, the view content will be empty if the
view definition query does not contain an aggregate without a GROUP clause.
Therefore, such views can be truncated.

Aggregate views without a GROUP clause always have one row. Therefore,
if a base table is truncated, the view will not be empty and will contain
a row with NULL value (or 0 for count()). So, in this case, we refresh the
view instead of truncating it.

The next version of the patch-set will include this change.

4. In IVM_immediate_before,I know Lock base table with ExclusiveLock is
for concurrent updates to the IVM correctly, But how about to Lock it when actually
need to maintain MV which in IVM_immediate_maintenance
In this way you don't have to lock multiple times.

Yes, as you say, we don't have to lock the view multiple times.
I'll investigate better locking ways including the way that you suggest.

5. Why we need CreateIndexOnIMMV, is it a optimize?
It seems like when maintenance MV,
the index may not be used because of our match conditions can’t use simple = operator

No, the index works even when we use simple = operator together with "IS NULL".
For example:

postgres=# \d mv
Materialized view "public.mv"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |
v1 | integer | | |
v2 | integer | | |
Indexes:
"mv_index" UNIQUE, btree (id) NULLS NOT DISTINCT

postgres=# EXPLAIN ANALYZE
WITH diff(id, v1, v2) AS MATERIALIZED ((VALUES(42, 420, NULL::int)))
SELECT mv.* FROM mv, diff
WHERE (mv.id = diff.id OR (mv.id IS NULL AND diff.id IS NULL)) AND
(mv.v1 = diff.v1 OR (mv.v1 IS NULL AND diff.v1 IS NULL)) AND
(mv.v2 = diff.v2 OR (mv.v2 IS NULL AND diff.v2 IS NULL));

QUERY PLAN

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------
Nested Loop (cost=133.87..137.92 rows=1 width=12) (actual time=0.180..0.191 rows=1 loops=1)
CTE diff
-> Result (cost=0.00..0.01 rows=1 width=12) (actual time=0.027..0.028 rows=1 loops=1)
-> CTE Scan on diff (cost=0.00..0.02 rows=1 width=12) (actual time=0.037..0.040 rows=1 loops=1)
-> Bitmap Heap Scan on mv (cost=133.86..137.88 rows=1 width=12) (actual time=0.127..0.132 rows=1 loops=1)
Recheck Cond: ((id = diff.id) OR (id IS NULL))
Filter: (((id = diff.id) OR ((id IS NULL) AND (diff.id IS NULL))) AND ((v1 = diff.v1) OR ((v1 IS NULL) AND (diff.v1 IS NULL))) AND ((v2 = diff.v2) OR ((v2 IS NULL) AND (diff.v2
IS NULL))))
Heap Blocks: exact=1
-> BitmapOr (cost=133.86..133.86 rows=1 width=0) (actual time=0.091..0.093 rows=0 loops=1)
-> Bitmap Index Scan on mv_index (cost=0.00..4.43 rows=1 width=0) (actual time=0.065..0.065 rows=1 loops=1)
Index Cond: (id = diff.id)
-> Bitmap Index Scan on mv_index (cost=0.00..4.43 rows=1 width=0) (actual time=0.021..0.021 rows=0 loops=1)
Index Cond: (id IS NULL)
Planning Time: 0.666 ms
Execution Time: 0.399 ms
(15 rows)

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#225huyajun
hu_yajun@qq.com
In reply to: Yugo NAGATA (#224)
Re: Implementing Incremental View Maintenance

Hi, Nagata-san

Thank you for your answer, I agree with your opinion, and found some new problems to discuss with you

3. Consider truncate base tables, IVM will not refresh, maybe raise an error will be better

I fixed to support TRUNCATE on base tables in our repository.
https://github.com/sraoss/pgsql-ivm/commit/a1365ed69f34e1adbd160f2ce8fd1e80e032392f

When a base table is truncated, the view content will be empty if the
view definition query does not contain an aggregate without a GROUP clause.
Therefore, such views can be truncated.

Aggregate views without a GROUP clause always have one row. Therefore,
if a base table is truncated, the view will not be empty and will contain
a row with NULL value (or 0 for count()). So, in this case, we refresh the
view instead of truncating it.

The next version of the patch-set will include this change.

I read your patch and think this processing is greet, but there is a risk of deadlock.
Although I have not thought of a suitable processing method for the time being,
it is also acceptable for truncate scenarios.The deadlock scene is as follows:

Mv define is: select * from base_a,base_b;
S1: truncate base_a; — only AccessExclusiveLock base_a and not run into after trigger
S2: insert into base_b; — The update has been completed and the incremental refresh is started in the after trigger,RowExclusive on base_b and ExclusiveLock on mv
S1: continue truncate mv, wait for AccessExclusiveLock on mv, wait for S2
S2: continue refresh mv, wait for AccessShardLock on base_a, wait for S1
So deadlock occurred

I also found some new issues that I would like to discuss with you
1. Concurrent DML causes imv data error, case like below
Setup:
Create table t( a int);
Insert into t select 1 from generate_series(1,3);
create incremental materialized view s as select count(*) from t;

S1: begin;delete from t where ctid in (select ctid from t limit 1);
S2: begin;delete from t where ctid in (select ctid from t limit 1 offset 1);
S1: commit;
S2: commit;

After this, The count data of s becomes 2 but correct data is 1.
I found out that the problem is probably because to our use of ctid update
Consider user behavior unrelated to imv:

Create table t( a int);
Insert into t select 1;
s1: BEGIN
s1: update t set a = 2 where ctid in (select ctid from t); -- UPDATE 1
s2: BEGIN
s2: update t set a = 3 where ctid in (select ctid from t); -- wait row lock
s1: COMMIT
s2: -- UPDATE 0 -- ctid change so can't UPDATE one rows
So we lost the s2 update

2. Sometimes it will crash when the columns of the created materialized view do not match
Create table t( a int);
create incremental materialized view s(z) as select sum(1) as a, sum(1) as b from t;

The problem should be that colNames in rewriteQueryForIMMV does not consider this situation

3. Sometimes no error when the columns of the created materialized view do not match
Create table t( a int);
create incremental materialized view s(y,z) as select count(1) as b from t;

But the hidden column of IMV is overwritten to z which will cause refresh failed.

The problem should be that checkRuleResultList we should only skip imv hidden columns check

4. A unique index should not be created in the case of a Cartesian product

create table base_a (i int primary key, j varchar);
create table base_b (i int primary key, k varchar);
INSERT INTO base_a VALUES
(1,10),
(2,20),
(3,30),
(4,40),
(5,50);
INSERT INTO base_b VALUES
(1,101),
(2,102),
(3,103),
(4,104);
CREATE incremental MATERIALIZED VIEW s as
select base_a.i,base_a.j from base_a,base_b; — create error because of unique index

5. Besides, I would like to ask you if you have considered implementing an IMV with delayed refresh?
The advantage of delayed refresh is that it will not have much impact on write performance
I probably have some ideas about it now, do you think it works?
1. After the base table is updated, the delayed IMV's after trigger is used to record the delta
information in another table similar to the incremental log of the base table
2. When incremental refresh, use the data in the log instead of the data in the trasient table
of the after trigger
3. We need to merge the incremental information in advance to ensure that the base_table
after transaction filtering UNION ALL old_delta is the state before the base table is updated
Case like below:
Create table t( a int);
—begin to record log
Insert into t select 1; — newlog: 1 oldlog: empty
Delete from t; —newlog:1, oldlog:1
— begin to incremental refresh
Select * from t where xmin < xid or (xmin = xid and cmin < cid); — empty
So this union all oldlog is not equal to before the base table is updated
We need merge the incremental log in advance to make newlog: empty, oldlog: empty

If implemented, incremental refresh must still be serialized, but the DML of the base table
can not be blocked, that is to say, the base table can still record logs during incremental refresh,
as long as we use same snapshot when incrementally updating.

do you think there will be any problems with this solution?

Looking forward to your reply to answer my above doubts, thank you a lot!
Regards,
Yajun Hu

#226Yugo NAGATA
nagata@sraoss.co.jp
In reply to: huyajun (#225)
Re: Implementing Incremental View Maintenance

Hello huyajun,

I'm sorry for delay in my response.

On Tue, 26 Jul 2022 12:00:26 +0800
huyajun <hu_yajun@qq.com> wrote:

I read your patch and think this processing is greet, but there is a risk of deadlock.
Although I have not thought of a suitable processing method for the time being,
it is also acceptable for truncate scenarios.The deadlock scene is as follows:

Mv define is: select * from base_a,base_b;
S1: truncate base_a; — only AccessExclusiveLock base_a and not run into after trigger
S2: insert into base_b; — The update has been completed and the incremental refresh is started in the after trigger,RowExclusive on base_b and ExclusiveLock on mv
S1: continue truncate mv, wait for AccessExclusiveLock on mv, wait for S2
S2: continue refresh mv, wait for AccessShardLock on base_a, wait for S1
So deadlock occurred

Hmm, this deadlock scenario is possible, indeed.

One idea to resolve it is to acquire RowExclusive locks on all base tables
in the BEFORE trigger. If so, S2 can not progress its process because it
waits for a RowExclusive lock on base_b, and it can not acquire ExeclusiveLock
on mv before S1 finishes.

I also found some new issues that I would like to discuss with you

Thank you so much for your massive bug reports!

1. Concurrent DML causes imv data error, case like below
Setup:
Create table t( a int);
Insert into t select 1 from generate_series(1,3);
create incremental materialized view s as select count(*) from t;

S1: begin;delete from t where ctid in (select ctid from t limit 1);
S2: begin;delete from t where ctid in (select ctid from t limit 1 offset 1);
S1: commit;
S2: commit;

After this, The count data of s becomes 2 but correct data is 1.
I found out that the problem is probably because to our use of ctid update
Consider user behavior unrelated to imv:

Create table t( a int);
Insert into t select 1;
s1: BEGIN
s1: update t set a = 2 where ctid in (select ctid from t); -- UPDATE 1
s2: BEGIN
s2: update t set a = 3 where ctid in (select ctid from t); -- wait row lock
s1: COMMIT
s2: -- UPDATE 0 -- ctid change so can't UPDATE one rows
So we lost the s2 update

2. Sometimes it will crash when the columns of the created materialized view do not match
Create table t( a int);
create incremental materialized view s(z) as select sum(1) as a, sum(1) as b from t;

The problem should be that colNames in rewriteQueryForIMMV does not consider this situation

3. Sometimes no error when the columns of the created materialized view do not match
Create table t( a int);
create incremental materialized view s(y,z) as select count(1) as b from t;

But the hidden column of IMV is overwritten to z which will cause refresh failed.

The problem should be that checkRuleResultList we should only skip imv hidden columns check

4. A unique index should not be created in the case of a Cartesian product

create table base_a (i int primary key, j varchar);
create table base_b (i int primary key, k varchar);
INSERT INTO base_a VALUES
(1,10),
(2,20),
(3,30),
(4,40),
(5,50);
INSERT INTO base_b VALUES
(1,101),
(2,102),
(3,103),
(4,104);
CREATE incremental MATERIALIZED VIEW s as
select base_a.i,base_a.j from base_a,base_b; — create error because of unique index

I am working on above issues (#1-#4) now, and I'll respond on each later.

5. Besides, I would like to ask you if you have considered implementing an IMV with delayed refresh?
The advantage of delayed refresh is that it will not have much impact on write performance

Yes, I've been thinking to implement deferred maintenance since the beginning of
this IVM project. However, we've decided to start from immediate maintenance, and
will plan to propose deferred maintenance to the core after the current patch is
accepted. (I plan to implement this feature in pg_ivm extension module first,
though.)

I probably have some ideas about it now, do you think it works?
1. After the base table is updated, the delayed IMV's after trigger is used to record the delta
information in another table similar to the incremental log of the base table
2. When incremental refresh, use the data in the log instead of the data in the trasient table
of the after trigger
3. We need to merge the incremental information in advance to ensure that the base_table
after transaction filtering UNION ALL old_delta is the state before the base table is updated
Case like below:
Create table t( a int);
—begin to record log
Insert into t select 1; — newlog: 1 oldlog: empty
Delete from t; —newlog:1, oldlog:1
— begin to incremental refresh
Select * from t where xmin < xid or (xmin = xid and cmin < cid); — empty
So this union all oldlog is not equal to before the base table is updated
We need merge the incremental log in advance to make newlog: empty, oldlog: empty

If implemented, incremental refresh must still be serialized, but the DML of the base table
can not be blocked, that is to say, the base table can still record logs during incremental refresh,
as long as we use same snapshot when incrementally updating.

do you think there will be any problems with this solution?

I guess the deferred maintenance process would be basically what similar
to above. Especially, as you say, we need to merge incremental information
in some way before calculating deltas on the view. I investigated some
research papers, but I'll review again before working on deferred approach
design.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

#227Michael Paquier
michael@paquier.xyz
In reply to: Yugo NAGATA (#226)
Re: Implementing Incremental View Maintenance

On Fri, Sep 09, 2022 at 08:10:32PM +0900, Yugo NAGATA wrote:

I am working on above issues (#1-#4) now, and I'll respond on each later.

Okay, well. There has been some feedback sent lately and no update
for one month, so I am marking it as RwF for now. As a whole the
patch has been around for three years and it does not seem that a lot
has happened in terms of design discussion (now the thread is long so
I would easily miss something)..
--
Michael