Subject: Re: Declarative partitioning in pgAdmin4

From: Dave Page <[email-masked]>
Date: Wed, 26 Apr 2017 09:25:41 +0100

Hi

[moving to the pgadmin-hackers mailing list as this a pgAdmin feature]

On Wed, Apr 26, 2017 at 8:20 AM, Akshay Joshi <[email-masked]
> wrote:

> Hi Dave
>
> Murtuza and I started thinking about "How to add Declarative
> Partitioning" support in pgAdmin4. We thought instead of showing Partition
> Table under existing Tables collection, we should add new collection node
> "Partition Tables". Showing table under the table node recursively will
> require lots of code changes in table and it's child nodes (column, index,
> trigger, etc..) which is more complex and error prone.
>

Perhaps, but from the user's perspective, there's no reason to list them
separately - they are just tables with a different structure from others.
We shouldn't confuse the user just because it's more convenient for us.

I really think it should look like this:

- Tables
  - t1
    - Columns
    - Constraints
    - Partitions
      - p1
        - Sub Objects (whatever they may be)
        ...
      - p2
      ...
  - t2
  ...

>
> Below is the design that we can implement:
>
>    - Create new "Partition Tables" collection node. User will be able to
>    create partition table by clicking "Create -> Partition Table" menu that we
>    will add on collection node. We will share the dialog prototype later
>    once we will have complete understanding of it.
>
> Can you share a mock-up of the dialog? The Figma tool that Shirley shared
looks like it'll be good for doing that - I can invite you to the team.

>
>    - Once table is created user will be able to create partitions by
>    clicking "Create -> Partitions" menu will be added on each partitioned
>    table node. We will share the dialog prototype later once we will have
>    complete understanding of it.
>
> I would expect the user to be able to define the partitioning scheme when
they create the table; e.g. on a new tab. It shouldn't be a two step
process.

>
>    - We will have to show sub nodes like (column, index, trigger,
>    constraints, etc..) on main table while some of the sub nodes won't require
>    for partitions like (column and many more again require some more knowledge
>    on partitioning).
>
> OK.

> Apart from above we will have to figure out following:
>
>    - How to remove partitions(table) from existing tables node as value
>    of relkind column is 'r' for partitions.
>    - Partitioning scheme to show in SQL pane for partitions.
>    - Some unknown issue/features of Declarative partitioning.
>
> OK.

> The above implementation may take more time, so it might possible that we
> may not be able to finish it by 14th May (deadline).
>

It would be nice to have it by then, but the true deadline will be a later
beta (TBD, but probably beta 2 which is sufficiently far off).

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

---

From: Shirley Wang <[email-masked]>
Date: Wed, 26 Apr 2017 17:36:10 +0000

Hello!

On Wed, Apr 26, 2017 at 4:26 AM Dave Page <[email-masked]> wrote:

> On Wed, Apr 26, 2017 at 8:20 AM, Akshay Joshi <
> [email-masked]> wrote:
>
>> Hi Dave
>>

>>    table node. We will share the dialog prototype later once we will
>>    have complete understanding of it.
>>

Seems like there are a couple of assumptions being made here:
- Users need to see partitioned tables when expanding parent table
- Users need to view partitioned tables in context to their parent table
(Dave says yes, Akshay and Murtuza say no)
- Users want to create a partitioned table through the browser (Akshay and
Murtuza say yes, Dave says no)

Plus some technical concerns:
- Making code changes in table is complex and error prone
- How to move partitions from one node to another

I think the first assumption is important to validate or invalidate before
even thinking about how to implement or addressing technical concerns. We
may come to learn that there are solutions that don't require a lot of
technical maneuvering, or perhaps learn there's no need for change at all.

Akshay and Murtuza, I'm happy to work with you on doing some research
(interviews to discover user needs and pains, creating mockups, getting
feedback etc) and coming up with some solutions based on user feedback.

---

From: Akshay Joshi <[email-masked]>
Date: Thu, 27 Apr 2017 12:18:01 +0530

On Wed, Apr 26, 2017 at 11:06 PM, Shirley Wang <[email-masked]> wrote:

>>>    - Create new "Partition Tables" collection node. User will be able
>>>    to create partition table by clicking "Create -> Partition Table" menu that
>>>    we will add on collection node. We will share the dialog prototype
>>>    later once we will have complete understanding of it.
>>>
>>> Can you share a mock-up of the dialog? The Figma tool that Shirley
>> shared looks like it'll be good for doing that - I can invite you to the
>> team.
>>
>
>>> I would expect the user to be able to define the partitioning scheme
>> when they create the table; e.g. on a new tab. It shouldn't be a two step
>> process.
>>
>>>

    Sure, it would be great. We will require some R&D about which
feature/controls(Inheritance, Constraints, Indexes, Triggers and may be
more) works with partitioning and which we will have to disabled if user
will create partition table.

-- 
*Akshay Joshi*
*Principal Software Engineer *

*Phone: +91 20-3058-9517Mobile: +91 976-788-8246*

---

From: Dave Page <[email-masked]>
Date: Thu, 27 Apr 2017 09:14:16 +0100

> - Users need to see partitioned tables when expanding parent table

If by "assumption" you mean "fact", then yes :-). Users need to be able to
see and manipulate partitions. Whilst some sub-objects are defined on the
parent table (e.g. the columns), others are defined on the individual
partitions (e.g. triggers, indexes).

> (Dave says yes, Akshay and Murtuza say no)

That's not what was said. Akshay and Murtuza were proposing a new
collection node, e.g.

- Schema
  - Functions
  - Partitioned Tables
  - Tables
  - Views

I'm saying that that unnecessarily complicates things for the user. The
fact that a table happens to use declarative partitioning, doesn't make it
a different type of object as far as Postgres is concerned, nor should it
for us.

> Murtuza say yes, Dave says no)

I didn't say that. I said it shouldn't be a two-part process.

> feedback etc) and coming up with some solutions based on user feedback.

How would users come up with feedback, given that the feature doesn't exist
in the field yet?

---

From: Akshay Joshi <[email-masked]>
Date: Thu, 27 Apr 2017 16:31:03 +0530

Hi Dave

As per discussion I have changed the logic of showing partitioned table in
browser tree. Attached is the screenshot.
Let me know your thoughts.

On Thu, Apr 27, 2017 at 1:44 PM, Dave Page <[email-masked]> wrote:

>>>>    - How to remove partitions(table) from existing tables node as
>>>>    value of relkind column is 'r' for partitions.
>> - Users want to create a partitioned table through the browser (Akshay
>> and Murtuza say yes, Dave says no)
>>
>
>> I think the first assumption is important to validate or invalidate
>> before even thinking about how to implement or addressing technical
>> concerns. We may come to learn that there are solutions that don't require
>> a lot of technical maneuvering, or perhaps learn there's no need for change
>> at all.
>>
>
> How would users come up with feedback, given that the feature doesn't
> exist in the field yet?
>
> --
> Dave Page

-- 
Sent via pgadmin-hackers mailing list ([email-masked])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers

---

From: Robert Eckhardt <[email-masked]>
Date: Thu, 27 Apr 2017 10:18:02 -0400

On Thu, Apr 27, 2017 at 7:01 AM, Akshay Joshi <[email-masked]

> Let me know your thoughts.

Greenplum has had declarative partitioning for quite some time, I haven't
spent much time diving into the Postgres implementation specifically,
however, we have had some pain and I would suggest a little bit of thought
behind this.

The issues we consistently face:

   - The huge (often thousands sometimes tens of thousands) number of
   partitions makes rendering all of the partitions painfully slow and
   frequently not useful.
   - When end users are interested in looking at their partitions they
   frequently don't want all of them displayed mindlessly
      - They are looking at a subset of partitions
      - Partitions are typically grouped around their inheritance
      properties.

-- Rob

---

From: Dave Page <[email-masked]>
Date: Thu, 27 Apr 2017 16:09:05 +0100

> Let me know your thoughts.

That's pretty much what I had in mind, yes. There are certain object types
that would need to be rendered under the partitions themselves instead of
the parent though.

>>>> Perhaps, but from the user's perspective, there's no reason to list
>>>> them separately - they are just tables with a different structure from
>>>> others. We shouldn't confuse the user just because it's more convenient for
>>>> us.
>>>>
>> If by "assumption" you mean "fact", then yes :-). Users need to be able
>> to see and manipulate partitions. Whilst some sub-objects are defined on
>> the parent table (e.g. the columns), others are defined on the individual
> *Phone: +91 20-3058-9517 <+91%2020%203058%209517>Mobile: +91 976-788-8246
> <+91%2097678%2088246>*
>

---

From: Dave Page <[email-masked]>
Date: Thu, 27 Apr 2017 16:11:54 +0100

On Thu, Apr 27, 2017 at 3:18 PM, Robert Eckhardt <[email-masked]>
wrote:

>
>
> On Thu, Apr 27, 2017 at 7:01 AM, Akshay Joshi <
>> As per discussion I have changed the logic of showing partitioned table
>> in browser tree. Attached is the screenshot.
> Perhaps, though I doubt that number would be common in Postgres. The
problem though, is that there are both stats and sub-objects (indexes and
triggers for example) that are part of the child partitions, not the parent
- and they may differ from partition to partition. I don't see that we have
any choice but to display them so users can work with them.

> How might you propose grouping them (based on the way they work in
Postgres)?

---

From: Robert Eckhardt <[email-masked]>
Date: Thu, 27 Apr 2017 11:44:43 -0400

> - and they may differ from partition to partition.
>

Certainly there differences in Postgres and Greenplum and this might very
well be one of those places.

> I don't see that we have any choice but to display them so users can work
> with them.
>

We don't want to hide them, I do think we want to make accessing them a
useful experience. If we rephrase this statement as "How might we display
partitioned tables so that users are able to work with and modify the
pieces they need?", this opens us up to different opportunities in how we
display them.

Even with a simple case of 90 days of data partitioned by day, a drop down
showing 90 tables that are all mostly the same is a little overwhelming.

>> How might you propose grouping them (based on the way they work in

Honestly I'm not sure. We didn't really start thinking about this until the
other day so we are starting to look into the pains that Greenplum
customers have. Sharing that pain we discover back to the pgAdmin community
and seeing if it makes sense from a Postgres perspective.  After that I
need to dive into the Postgres implementation.

-- Rob

---

From: Akshay Joshi <[email-masked]>
Date: Tue, 2 May 2017 19:16:45 +0530

Hi All

To implement Declarative Partitioning in existing Table dialog
below changes should be implemented:

   1. *Icon: *As we have separate icon for view and materialised view, we
   should have for partition table. I didn't find any in font awesome.
   2. *Inheritance*:
      - A partition cannot have any parents other than the partitioned
      table it is a partition of, nor can a regular table inherit from a
      partitioned table making the latter its parent. That means partitioned
      tables and partitions do not participate in inheritance with
regular tables.
      - When user creates regular table then Inherited from table(s)
      control should not display partitioned table.
   3. *Constraints*:
      - Primary/Foreign/Unique/Exclusion constraints are not supported on
      partitioned table. In that case respective controls should be
disabled for
      partitioned table.
      - We will have to check which constraints are applicable on
      partitions(of partitioned table) still some R&D require. Can someone help
      me here.
      - For regular tables in Foreign Key constraints tab References
      control should not list partition tables.
      - Check constraints : cannot add NO INHERIT constraint to partitioned
      table, so that control is disabled for partition table.
   4. *Advanced Tab*:
      - Relation works with partition table theirs is an error if "With
      indexes?" is set to Yes, so we need to disabled that for partition table.
      - "Has OIDs?" and "Unlogged?" works but not sure about "Fill factor"
      and "Of type".
   5. *Parameter Tab*:
      - Gives error (unrecognized parameter "autovacuum_enabled") for all
      parameters  of Table Tab and working fine for "Toast Table" it's
      working.

Apart from above we will have to do following:

   - Required switch control to specify whether it is a regular table or
   partitioned table. I have added it on General tab. Please refer
   Partition_Switch.png
   - Will have to add new tab "Partition" which will have one select2
   control to define its Range partition or List partition. Refer
   Partition_Tab.png
   - Design following controls in *Partition* tab:
      - How to add columns in case of Range/List partition? LIST partition
      key supports only one column. For RANGE user can specify multiple columns.
      - How to specify expression, COLLATE while adding columns
      for partition.
      - We need subnode control so that user will add number of partition
      with there values of the main table. Need lot of R&D for this.
   - We will have to provide "Create partition", "Attach Partition" and "
   Detech partition" context menu options on Partitions collection node.

Let me know if I forgot something to add that we may need to
handle/implement.

On Thu, Apr 27, 2017 at 9:14 PM, Robert Eckhardt <[email-masked]>
wrote:

> Honestly I'm not sure. We didn't really start thinking about this until
> the other day so we are starting to look into the pains that Greenplum

---

From: Dave Page <[email-masked]>
Date: Tue, 2 May 2017 15:55:48 +0100

Hi

On Tue, May 2, 2017 at 2:46 PM, Akshay Joshi <[email-masked]>
wrote:

> They are really different object types though (even having their own
collections), which isn't the case here. I'm not against having a slightly
modified icon, but I don't think it's necessary. Note that the object icons
come from pgAdmin III, and were custom designed for us. They aren't in font
awesome etc. We'd need to tweak one of the existing ones.

>    1. *Inheritance*:
>       tables and partitions do not participate in inheritance with regular tables.
>    2. *Constraints*:
>       partitioned table. In that case respective controls should be disabled for
>       partitioned table.
>       - Check constraints : cannot add NO INHERIT constraint to
>       partitioned table, so that control is disabled for partition table.
>    3. *Advanced Tab*:
>       - "Has OIDs?" and "Unlogged?" works but not sure about "Fill
>       factor" and "Of type".
>    4. *Parameter Tab*:
> Can you detail what operations someone would likely want (or need) to
perform on the parent/child tables; e.g.

Parent:

- View stats
- View data
- Truncate
- View/create columns
- Bulk-create indexes
- Bulk-create foreign keys

Child:

- View stats
- View data
- Truncate
- Create indexes
- Create foreign keys

> "Partitions"?

>       - How to add columns in case of Range/List partition? LIST
>       partition key supports only one column. For RANGE user can specify multiple
>       columns.

Thanks! This is a complex one :-(

>>> I don't see that we have any choice but to display them so users can
>>> work with them.
>>>
>>
>> Even with a simple case of 90 days of data partitioned by day, a drop
>> down showing 90 tables that are all mostly the same is a little
>> overwhelming.
>>
>>
>>>

---

From: Shirley Wang <[email-masked]>
Date: Tue, 02 May 2017 22:01:29 +0000

On Tue, May 2, 2017 at 10:56 AM Dave Page <[email-masked]> wrote:

> Hi
>
> On Tue, May 2, 2017 at 2:46 PM, Akshay Joshi <
>>    1. *Icon: *As we have separate icon for view and materialised view,
>>    we should have for partition table. I didn't find any in font awesome.
>>
>>       - Primary/Foreign/Unique/Exclusion constraints are not supported
>>       on partitioned table. In that case respective controls should be disabled
>>       for partitioned table.
>>       - Gives error (unrecognized parameter "autovacuum_enabled") for
>>       all parameters  of Table Tab and working fine for "Toast Table"
>>       it's working.
>>

Is there a reason why 'Partition' needs to open in a new tab? If there's
only one field, we should include it on the same page since the tabs don't
dictate necessary steps in a sequential order. Users will be able to find
what they need without navigating to another part of the dialog.

Example:

>>       - We need subnode control so that user will add number of
>>       partition with there values of the main table. Need lot of R&D for this.
>>> Certainly there differences in Postgres and Greenplum and this might
>>> very well be one of those places.
>>>
>>>
>>>>>    - When end users are interested in looking at their partitions
>>>>>    they frequently don't want all of them displayed mindlessly

---

From: Akshay Joshi <[email-masked]>
Date: Wed, 3 May 2017 10:13:51 +0530

Hi Shirley

On Wed, May 3, 2017 at 3:31 AM, Shirley Wang <[email-masked]> wrote:

>>>    - Required switch control to specify whether it is a regular table
>>>    or partitioned table. I have added it on General tab. Please refer
>>>    Partition_Switch.png

    There are lots of controls yet to design like column(s), number of
partitions, partition schemes will be part of "Partitions" Tab. We will
need that.

>>>    - We will have to provide "Create partition", "Attach Partition" and
>>>    "Detech partition" context menu options on Partitions collection
>>>    node.
>>>
>>> OK.
>>
>>>>>>    - The huge (often thousands sometimes tens of thousands) number
>>>>>>    of partitions makes rendering all of the partitions painfully slow and
>>>>>>    frequently not useful.
>>>>>>
>>> *Phone: +91 20-3058-9517 <+91%2020%203058%209517>Mobile: +91
>>> 976-788-8246 <+91%2097678%2088246>*
>>>
>>>
>>> --

---

From: Akshay Joshi <[email-masked]>
Date: Wed, 3 May 2017 17:30:10 +0530

Hi Dave

As per my understanding below operations required

Parent:

   - View table data.
   - View stats.
   - Create regular/partitioned table
   - Create N number of partitions.
   - Drop/ Drop cascade, Truncate.
   - Attach/Detach Partitions.
   - Not able to create constraints excluding check constraint.

Child:

   - View Table Data.
   - View stats.
   - View partition scheme in SQL pane
   - Create primary/foreign/.. key constraint.
   - Drop/ Drop cascade, Truncate

---

From: Dave Page <[email-masked]>
Date: Wed, 3 May 2017 13:05:17 +0100

Great, thanks.

I think it's clear that we need to display the child partitions in the
treeview. I don't see any other sensible way of enabling those operations
without an extremely contrived dialogue design.

Please now document how those features will be implemented; e.g, for each
one:

- View table data: Parent and partition context menu.
- Attach/detach partitions: Parent properties dialogue
...

That will then give us a list of places we'll need to (re)design dialogues
and menus etc. for.

---

From: Shirley Wang <[email-masked]>
Date: Wed, 03 May 2017 15:42:17 +0000

Hi!

> without an extremely contrived dialogue design.

Either way we implement this feature, we should test the workflow of how
people go through table partitioning with users to get validation on
whether or not our decisions make sense for them.

> On Wed, May 3, 2017 at 1:00 PM, Akshay Joshi <
>> It seems like the operations above detail a potential full feature set
for table partitioning which is a good starting point. It would be
worthwhile to consider what's the minimum we need to include for the first
release of table partitioning.

As we get live feedback and release frequently, we can add additional
features and fix bugs. If we cut the scope of this, we'll be more confident
that we can reach the deadline and deliver user value.

We can determine what should be included by plotting these features within
a matrix. Typically user value is on one axis and technical complexity on
another, although these can change depending on what your team needs.
We've found that this matrix is
really helpful in answering "What's the smallest thing we can build that
solves the most important problems?". We typically do this as a team
(engineers, designers, and product managers) once we have enough context
about user behavior and technical complexity.

I can facilitate a session where we run through this exercise. It typically
takes about an hour.

---

From: Akshay Joshi <[email-masked]>
Date: Thu, 4 May 2017 14:59:38 +0530

Hi All

> That will then give us a list of places we'll need to (re)design dialogues

    As per my knowledge on Partitioning, I think we will have to implement
following things in parent and child:

   Parent:

   1. View Table data :  No need to change any logic, it's working.
   2. Correct jinja template to show correct SQL in SQL pane.
   3. Create partitioned table -
      - Add one switch control ("Partitioned Table?") in General tab of
      Table dialog.
      - Add new tab "Partitions".
      - Add one select2 control (Partition Type :Range/List) in
      "Partitions" tab.
      - Create one subnode control to specify number of key columns with
      expressions. For List partition only one row will be there +
button will be
      disabled, and for Range partition + button will be enabled. Here is the
      syntax as per documentation [ PARTITION BY { RANGE | LIST } ( {
      *column_name* | ( *expression* ) } [ COLLATE *collation* ] [ *opclass* ]
      [, ... ] ) ]. *Design discussion required here for how user will
      specify expression, collate and opclass*.
   4. Create N number of partitions:
      - Design one control (subnode control) so that user will add N number
      of partitions. Here is the syntax as per documentation CREATE
TABLE  *table_name PARTITION
      OF parent_table [ (   { column_name [ WITH OPTIONS ] [
column_constraint [
      ... ] ]     | table_constraint }     [, ... ] ) ] FOR VALUES
      partition_bound_spec **partition_bound_spec* is:

      { IN ( { *bound_literal* | NULL } [, ...] ) |
        FROM ( { *bound_literal* | UNBOUNDED } [, ...] ) TO ( {
*bound_literal* | UNBOUNDED } [, ...] ) }

      - *Design discussion required here for how user will specify all the
      above combinations.*
   5. Properties dialog "Partitions" Tab:
      - Partition Type control must be disabled.
      - User will be able to create/modify existing partitions. User won't
      be able to delete partitions as there are two modes Detach/Drop
and we will
      have separate menu for it.
   6. Drop/ Drop cascade, Truncate: No need to change any logic, it's
   working.
   7. Attach Partitions:  Create context menu on partitioned table. When
   user clicks, open one dialog with some controls to provide table(to be
   attach) and partition_bound_spec*. Design discussion required here.*
   8. Not able to create constraints excluding check constraint:  We will
   have to disable context menu, remove child nodes from browser tree for
   constraints and disable controls from the dialog.

    Child:

   1. View Table Data: Add context menu.
   2. Detach partition: Create context menu, when user click popped up
   confirmation message box.
   3. View partition scheme in SQL pane: Changes required in jinja
   template.
   4. Create primary/foreign/.. key constraint: No need to change any logic
   on GUI, but may need to change queries to fetch the partitioned tables.
   5. Drop/ Drop cascade, Truncate: No need to change any logic.

    Apart from above it may be possible that I miss something, so we need
to cover that too.

>>>>       - Relation works with partition table theirs is an error if
>>>>       "With indexes?" is set to Yes, so we need to disabled that for partition
>>>>       table.
>>>>    - We will have to provide "Create partition", "Attach Partition"
>>>>    and "Detech partition" context menu options on Partitions
>>>>    collection node.
>>>>
>>>> OK.
>>>
>>>>> We don't want to hide them, I do think we want to make accessing them
>>>>> a useful experience. If we rephrase this statement as "How might we display
>>>>> Honestly I'm not sure. We didn't really start thinking about this
>>>>> until the other day so we are starting to look into the pains that
>>>>> Greenplum customers have. Sharing that pain we discover back to the pgAdmin
>>>>> community and seeing if it makes sense from a Postgres perspective.  After
>>>>> that I need to dive into the Postgres implementation.
>>>>>
>>>>> -- Rob
>>>>>
>>>>
>>>>
>>>>
>>>> --
>>>> *Akshay Joshi*

---

From: Dave Page <[email-masked]>
Date: Thu, 4 May 2017 11:30:25 +0100

Hi

On Thu, May 4, 2017 at 10:29 AM, Akshay Joshi <[email-masked]

>> That will then give us a list of places we'll need to (re)design
>> dialogues and menus etc. for.
>>
>
>       expressions. For List partition only one row will be there + button will be
>       *column_name* | ( *expression* ) } [ COLLATE *collation* ] [
>       *opclass* ] [, ... ] ) ]. *Design discussion required here for how
>       user will specify expression, collate and opclass*.
>       - Design one control (subnode control) so that user will add N
>       number of partitions. Here is the syntax as per documentation CREATE
>        TABLE  *table_name PARTITION OF parent_table [ (   { column_name [
>       WITH OPTIONS ] [ column_constraint [ ... ] ]     | table_constraint }
>       [, ... ] ) ] FOR VALUES partition_bound_spec **partition_bound_spec*
>        is:
>
>         FROM ( { *bound_literal* | UNBOUNDED } [, ...] ) TO ( { *bound_literal* | UNBOUNDED } [, ...] ) }
>
>       - *Design discussion required here for how user will specify all
>       the above combinations.*
>       - User will be able to create/modify existing partitions. User
>       won't be able to delete partitions as there are two modes Detach/Drop and
>       we will have separate menu for it.
>    4. Create primary/foreign/.. key constraint: No need to change any
>    logic on GUI, but may need to change queries to fetch the partitioned
>    tables.

OK, good. So now, let's break that down into a list of tasks, that we can
prioritise with Shirley. The initial list should be prioritised based on
your understanding I think, given the following criteria:

- Changes that prevent pgAdmin breaking
- Changes that prevent pgAdmin showing incorrect data/info
- Changes that enable pgAdmin to show correct info
- Changes that add functionality for creating/dropping partitioned tables
as one unit
- Changes that add functionality for modifying individual partitions
independently

Please document the requirements and initial plan on the pgAdmin Redmine
Wiki.

Thanks!

---

From: Akshay Joshi <[email-masked]>
Date: Thu, 11 May 2017 16:05:00 +0530

Hi

On Thu, May 4, 2017 at 4:00 PM, Dave Page <[email-masked]> wrote:

> Hi
>
> On Thu, May 4, 2017 at 10:29 AM, Akshay Joshi <
>>> Please now document how those features will be implemented; e.g, for
>>> each one:
>>>
>>     As per my knowledge on Partitioning, I think we will have to
>> implement following things in parent and child:
>>
>>    Parent:
>>
>>        TABLE  *table_name PARTITION OF parent_table [ (   { column_name
>>       [ WITH OPTIONS ] [ column_constraint [ ... ] ]     | table_constraint }
>>       [, ... ] ) ] FOR VALUES partition_bound_spec *
>>       *partition_bound_spec* is:
>>
>>    user clicks, open one dialog with some controls to provide table(to
>>    be attach) and partition_bound_spec*. Design discussion required
>>    here.*
>>    8. Not able to create constraints excluding check constraint:  We
>>    will have to disable context menu, remove child nodes from browser tree for

     I have updated Redmine Wiki page regarding what needs to be
implemented for partitioning. Can we discuss prioritisation of the task
based on above criteria in the our meeting. Meanwhile I have started
working on showing correct SQL for partitioned table.

---

From: Dave Page <[email-masked]>
Date: Thu, 11 May 2017 14:24:19 +0100

On Thu, May 11, 2017 at 11:35 AM, Akshay Joshi <
[email-masked]> wrote:

>>>       - Create one subnode control to specify number of key columns
>>>       with expressions. For List partition only one row will be there + button
>>>       will be disabled, and for Range partition + button will be enabled. Here is
>>>       the syntax as per documentation [ PARTITION BY { RANGE | LIST } (
>>>       { *column_name* | ( *expression* ) } [ COLLATE *collation* ] [
>>>       *opclass* ] [, ... ] ) ]. *Design discussion required here for
>>>       how user will specify expression, collate and opclass*.
>>>    7. Attach Partitions:  Create context menu on partitioned table.
>>>    When user clicks, open one dialog with some controls to provide
>>>    table(to be attach) and partition_bound_spec*. Design discussion
>>>    required here.*
>>>     Apart from above it may be possible that I miss something, so we
>>> need to cover that too.
>>>
>>

Which meeting?

-- 
Dave Page
VP, Chief Architect, Tools & Installers
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

---

From: Anne Thomas <[email-masked]>
Date: Thu, 11 May 2017 13:32:37 +0000

Akshay, Dave,

I'm filling in for Shirley this week. If you are referring to the weekly
meeting tomorrow, I'm planning on joining that and can walk through an
exercise to help figure out prioritization.

Thanks,
Anne

On Thu, May 11, 2017 at 9:24 AM Dave Page <[email-masked]>
wrote:

>>>>       - Add one switch control ("Partitioned Table?") in General tab
>>>>       of Table dialog.
>>>>       the syntax as per documentation [ PARTITION BY { RANGE | LIST }
>>>>       ( { *column_name* | ( *expression* ) } [ COLLATE *collation* ] [
>>>>        TABLE  *table_name PARTITION OF parent_table [ (   {
>>>>       column_name [ WITH OPTIONS ] [ column_constraint [ ... ] ]     |
>>>>       table_constraint }     [, ... ] ) ] FOR VALUES partition_bound_spec *
>>> OK, good. So now, let's break that down into a list of tasks, that we
>>> can prioritise with Shirley. The initial list should be prioritised based
>>> on your understanding I think, given the following criteria:
>>>
>>> - Changes that add functionality for creating/dropping partitioned
>>> tables as one unit
-- 
Anne Thomas
Product Manager
Pivotal Labs

---

From: Akshay Joshi <[email-masked]>
Date: Thu, 11 May 2017 19:06:44 +0530

Hi Dave

>> working on showing correct SQL for partitioned table.

   Meeting with Shirley, which wasn't schedule last Friday as I was on
leave.

---

From: Akshay Joshi <[email-masked]>
Date: Thu, 18 May 2017 16:11:45 +0530

Hi All

I have started implementation for Declarative Partitioning in pgAdmin4.
Following are the tasks that I have implemented till now:

   - Show partitioned table and it's partitions under the parent table.
   Refer Partitioned_Table.png
   - To implement above I have created 'partitions' collection node and
   'partition' node under table node which is nothing but table node itself.
   To reduce redundant/duplicate code I have made following changes:
      - Create new file "*utils.py*" under tables folder. Create a new
      class BaseTableView(PGChildNodeView): derived from PGChildNodeView.
      TableView and PartitionsView (new class for partition table) is
      derived from BaseTableView.
      - Move the common logic like dependencies, dependents, reversed
      engineered sql, statistics, reset statistics in BaseTableView class
      functions and then call that function from derived class like
      BaseTableView.get_table_dependencies(self, tid)
      - Will move more generic logic as we progress on this task.
   - Updated supported nodes list in DataGrid(View Data),
   Backup, Maintenance, Restore to show context menu for partitions.
   - Make sure dependencies, dependents, statistics, truncate, delete/drop
   and Reset Statistics works with partitions.
   - Updated jinja template to show correct reversed engineered sql for
   partitioned table. Please refer the "List_with_expression.png" for List
   partition and "Range_with_column_expression.png" for Range partition.
   - Updated jinja template to show correct sql for partitions of parent
   table. Please refer "SQL_Range_Partitions.png" and "SQL_List_Partitions.
   png". Some R&D is still require for other syntax too.

Please let me know above looks good and am I going in right direction.

On Thu, May 11, 2017 at 7:06 PM, Akshay Joshi <[email-masked]

>>>>>> I think it's clear that we need to display the child partitions in
>>>>>> the treeview. I don't see any other sensible way of enabling those
>>>>>> operations without an extremely contrived dialogue design.
>>>>>>
>>>>>       ( { *column_name* | ( *expression* ) } [ COLLATE *collation* ]
>>>>>       [ *opclass* ] [, ... ] ) ]. *Design discussion required here
>>>>>       for how user will specify expression, collate and opclass*.
>>>>>       - *Design discussion required here for how user will specify
>>>>>       all the above combinations.*
>>>>>    2. Detach partition: Create context menu, when user click popped
>>>>>    up confirmation message box.
>>>> Please document the requirements and initial plan on the pgAdmin
>>>> Redmine Wiki.
>>>>
>>>

---

From: Shirley Wang <[email-masked]>
Date: Thu, 18 May 2017 15:36:13 +0000

Hi Akshay!

Is this the workflow that you think users are going to engage in given what
you're building? Anne mentioned you're in the process of figuring out
what's required for defining the partitions, so you'll notice pink boxes
with text in areas where that might happen.

The modules that appear for partitioning are based on the ones we saw a few
weeks ago, let me know if that has changed in any way.

*01 user creates a table, if one doesn't already exist*

*02 user selects 'yes' for partitioning*

*03 user defines type of partition and ranges*
*04 After hitting submit, browser is updated with new table and partitions,
and user is taken to SQL tab. (What is this step for?)*

You mentioned Postgres users need to go into individual partitions
frequently and its common to have different indexes for each partition.
However, I wonder if the naming convention created will provide enough
context for people to remember which partition has the specific properties
they're looking for. If we imagine a scenario where a user has more than 30
or 40 partitions, and they need to look for one partition, it might be
quite time consuming for people to find the right one.

We're trying to find Postgres users to test this flow with, especially
since the success of this design relies on users knowing how to interact
with the partitions in their browser.

On Thu, May 18, 2017 at 6:41 AM Akshay Joshi <[email-masked]>
wrote:

>    - Make sure dependencies, dependents, statistics, truncate,
>    delete/drop and Reset Statistics works with partitions.
>    partitioned table. Please refer the "List_with_expression.png" for
>    List partition and "Range_with_column_expression.png" for Range
>    partition.
>    table. Please refer "SQL_Range_Partitions.png" and "SQL
>    _List_Partitions.png". Some R&D is still require for other syntax too.
>
> On Thu, May 11, 2017 at 7:06 PM, Akshay Joshi <
>>>>>>       the syntax as per documentation [ PARTITION BY { RANGE | LIST
>>>>>>       } ( { *column_name* | ( *expression* ) } [ COLLATE *collation* ]
>>>>>>    6. Drop/ Drop cascade, Truncate: No need to change any logic,
>>>>>>    it's working.
>>>>>>    4. Create primary/foreign/.. key constraint: No need to change
>>>>>>    any logic on GUI, but may need to change queries to fetch the partitioned
>>>>>>    tables.

---

From: Shirley Wang <[email-masked]>
Date: Thu, 18 May 2017 17:52:51 +0000

Also, I forgot to mention, these mockups are in Figma:
https://www.figma.com/file/RnmYyytaZdjsDHQWrMw5U0co/Create-partition-table?node-id=16%3A54

Feel free to play around and change things :)

On Thu, May 18, 2017 at 11:36 AM Shirley Wang <[email-masked]> wrote:

> Hi Akshay!
>
> Is this the workflow that you think users are going to engage in given
> what you're building? Anne mentioned you're in the process of figuring out
> The modules that appear for partitioning are based on the ones we saw a
> few weeks ago, let me know if that has changed in any way.
>
> *04 After hitting submit, browser is updated with new table and
> partitions, and user is taken to SQL tab. (What is this step for?)*
>
> On Thu, May 18, 2017 at 6:41 AM Akshay Joshi <
>>       engineered sql, statistics, reset statistics in BaseTableView
>>       class functions and then call that function from derived class
>>       like BaseTableView.get_table_dependencies(self, tid)
>>>>>>>> Please now document how those features will be implemented; e.g,
>>>>>>>> for each one:
>>>>>>>>
>>>>>>>       - Add one switch control ("Partitioned Table?") in General
>>>>>>>       tab of Table dialog.
>>>>>>>       - Design one control (subnode control) so that user will add
>>>>>>>       N number of partitions. Here is the syntax as per documentation CREATE
>>>>>>>       - User will be able to create/modify existing partitions.
>>>>>>>       User won't be able to delete partitions as there are two modes Detach/Drop
>>>>>>>       and we will have separate menu for it.
>>>>>>>    8. Not able to create constraints excluding check constraint:
>>>>>>>     We will have to disable context menu, remove child nodes from browser tree
>>>>>>>    for constraints and disable controls from the dialog.
>>>>>>>
>>>>>>>     Child:
>>>>>>>

---

From: Akshay Joshi <[email-masked]>
Date: Fri, 19 May 2017 11:47:02 +0530

Hi Shirley

On Thu, May 18, 2017 at 9:06 PM, Shirley Wang <[email-masked]> wrote:

>    Step 1 and 2 are correct.

> *03 user defines type of partition and ranges*

   We will create a new tabs "Partitions" and 'Partition Type' combo will
go on that tab along with following controls:

   - User should be able to specify Key Column(s) (Based on partition type)
   to create partitioned(parent) table. Some of the examples of list and range
   partitions are as below:
   - CREATE TABLE prt1 (a int, b int, c varchar) PARTITION BY RANGE(a);
      - CREATE TABLE prt1_e (a int, b int, c varchar) PARTITION BY
      RANGE(((a + b)/2));
      - CREATE TABLE prt1_m (a int, b int, c varchar) PARTITION BY RANGE(a,
      ((a + b)/2));
      - CREATE TABLE plt1 (a int, b int, c text) PARTITION BY LIST(c);
      - CREATE TABLE plt1_e (a int, b int, c text) PARTITION BY
      LIST(ltrim(c, 'A'));
      - User should be able to create N number of partitions:
      - Design one control (subnode control) so that user will add N number
      of partitions. Here is the syntax as per documentation CREATE  TABLE
       table_name PARTITION OF parent_table [ (   { column_name [ WITH
OPTIONS ]
      [ column_constraint [ ... ] ]     | table_constraint }     [,
... ] ) ] FOR
      VALUES partition_bound_spec partition_bound_spec is:{ IN ( {
bound_literal
      | NULL } [, ...] ) | FROM ( { bound_literal | UNBOUNDED } [,
...] ) TO ( {
      bound_literal | UNBOUNDED } [, ...] ) }

   Design(GUI) discussion required for above two, which control should we
used so that user will be easily able to create N number of partitions.

> partitions, and user is taken to SQL tab. (What is this step for?)*
  When user click on "OK" button of create table dialog, browser tree will
be updated, but i am not sure SQL tab comes in focus, no need of that.

> quite time consuming for people to find the right one.

   User will provide the name of his/her choice while creating partitions
from 'Create Table' dialog, so it's upto the user.

---

From: Akshay Joshi <[email-masked]>
Date: Fri, 19 May 2017 11:49:07 +0530

> Feel free to play around and change things :)

   Not able to open the above URL. It's 404 when I opened it.

>>>    - Updated jinja template to show correct sql for partitions of
>>>    parent table. Please refer "SQL_Range_Partitions.png" and "SQL
>>>>>>>>       - Create one subnode control to specify number of key
>>>>>>>>       columns with expressions. For List partition only one row will be there +
>>>>>>>>       button will be disabled, and for Range partition + button will be enabled.
>>>>>>>>       Here is the syntax as per documentation [ PARTITION BY {
>>>>>>>>       RANGE | LIST } ( { *column_name* | ( *expression* ) } [
>>>>>>>>       COLLATE *collation* ] [ *opclass* ] [, ... ] ) ]. *Design
>>>>>>>>       discussion required here for how user will specify expression, collate and
>>>>>>>>       opclass*.
>>>>>>>>    7. Attach Partitions:  Create context menu on partitioned
>>>>>>>>    table. When user clicks, open one dialog with some controls
>>>>>>>>    to provide table(to be attach) and partition_bound_spec*. Design discussion
>>>>>>>>    required here.*
>>>>>>>>    2. Detach partition: Create context menu, when user click
>>>>>>>>    popped up confirmation message box.
>>>>>>>>     Apart from above it may be possible that I miss something, so
>>>>>>>> we need to cover that too.
>>>>>>>>
>>>>>>>
>>>>>>> OK, good. So now, let's break that down into a list of tasks, that
>>>>>>> we can prioritise with Shirley. The initial list should be prioritised
>>>>>>> based on your understanding I think, given the following criteria:
>>>>>>>

---

From: Dave Page <[email-masked]>
Date: Fri, 19 May 2017 15:18:51 +0100

> Please let me know above looks good and am I going in right direction.

Certainly looks like it to me. We may want to tweak some things based on
the work Shirley is doing, but I think we're on the right path.

Good work!

---

From: Shirley Wang <[email-masked]>
Date: Fri, 19 May 2017 14:49:46 +0000

>    Not able to open the above URL. It's 404 when I opened it.
Whoops. I just sent you an invite for edit access.

For the rest of the community, here's a link for view access:
https://www.figma.com/file/RnmYyytaZdjsDHQWrMw5U0co/Create-partition-table

---

From: Akshay Joshi <[email-masked]>
Date: Mon, 22 May 2017 14:58:09 +0530

Hi All

As continuation I have worked on "Partition" Tab and added some controls
where user will be able to specify the key columns and create N number of
partitions:

   - Added Partition Type combo box where user will define the
   Type(Range/List)
   - "Partition Keys" subnode control where there is combo box to specify
   it is column or expression. If it is column then on expanding subnode
   control all the columns specified to create main table will be listed here.
   User won't be able to select multiple columns here. If it is expression
   then user will be able to specify correct expression with correct
   parenthesis. In case of List partition only one row will be allowed.
   - "Partitions" control is used to create N number of partitions. In case
   of Range partition there are three columns "Name", "Value From" and "Value
   To". If partition key is combination of multiple columns or combination of
   column and expression then user will have to provide (,) comma separated
   values. Refer "*Range_Partition.png*". In case of List partition there
   are two columns "Name" and "Values In" and user will have to provide (,)
   separated list for "Values In" column. Refer "*List_Partition.png*".

*Note*: Apart from above there are following that needs to be taken care:

   - Once user will define the partition keys and partitions based on the
   columns define for main table and then user will rename/delete any column
   from "Columns" Tab we will warn user about renaming/deleting any column
   will reset all the rows define under partitions tab. This is just because
   we can rename/delete the column from partition keys but not sure how we can
   remove/rename it from the expression defined by the user.
   - *Major challenge* here is while creating partitions(second subnode
   control in attached image) according to the documentation user will be able
   to create constraints( Primary, Foreign, Unique...). How user will be able
   to add constraints from GUI? Do we need to implement a new control where
   one subnode control contains the whole "Constraints" Tab or any other
   solution.

Please review it. Suggestions/Comments are welcome.

On Fri, May 19, 2017 at 11:03 PM, Shirley Wang <[email-masked]> wrote:

> On Fri, May 19, 2017 at 2:17 AM Akshay Joshi <
>>    - User should be able to specify Key Column(s) (Based on partition
>>    type) to create partitioned(parent) table. Some of the examples of list and
>>    range partitions are as below:
>>       - CREATE TABLE prt1_m (a int, b int, c varchar) PARTITION BY
>>       RANGE(a, ((a + b)/2));
>>        table_name PARTITION OF parent_table [ (   { column_name [ WITH OPTIONS ]
>>       [ column_constraint [ ... ] ]     | table_constraint }     [, ... ] ) ] FOR
>>       bound_literal | NULL } [, ...] ) | FROM ( { bound_literal | UNBOUNDED } [,
>>       ...] ) TO ( { bound_literal | UNBOUNDED } [, ...] ) }
>>
>>
>>
> Ok. Hopefully we'll get some interviews set up for next week that'll help
> us determine the best workflow for this.
>

---

From: Shirley Wang <[email-masked]>
Date: Mon, 22 May 2017 21:29:27 +0000

On Mon, May 22, 2017 at 5:28 AM Akshay Joshi <[email-masked]>
wrote:

> To get out an MVP, perhaps we should focus on just establishing a
partition for a single range and list, then figure out how an expression
would work. It seems like the majority of use cases for using a range
partition would be by date so let's get that functionality working and
worry about expressions once we have live feedback to minimize risk (excess
time spent developing features with many unknowns about user behavior)

>
>    - "Partitions" control is used to create N number of partitions. In
>    case of Range partition there are three columns "Name", "Value From" and
>    "Value To". If partition key is combination of multiple columns or
>    combination of column and expression then user will have to provide (,)
>    comma separated values. Refer "*Range_Partition.png*". In case of List
>    partition there are two columns "Name" and "Values In" and user will have
>    to provide (,) separated list for "Values In" column. Refer "
>    *List_Partition.png*".
>
> Here's a sketch of what a single range would look like:

> That's fine, as long as they are still able to do so after a warning.

>    one subnode control contains the whole "Constraints" Tab or any other
We can probably leverage the same table. We're investigating constraints as
well.

Shirley & Rob

---

From: Akshay Joshi <[email-masked]>
Date: Tue, 23 May 2017 11:17:00 +0530

On Tue, May 23, 2017 at 2:59 AM, Shirley Wang <[email-masked]> wrote:

> On Mon, May 22, 2017 at 5:28 AM Akshay Joshi <
>>    - "Partition Keys" subnode control where there is combo box to
>>    specify it is column or expression. If it is column then on expanding
>>    subnode control all the columns specified to create main table will
>>    be listed here. User won't be able to select multiple columns here. If it
>>    is expression then user will be able to specify correct expression with
>>    correct parenthesis. In case of List partition only one row will be allowed.
>>

    I personally think it is not that difficult to implement expressions we
just need to get the value given by user and create proper SQL to create
the table. For example User will create one partitioned table with three
columns a,b and c. from partitions key control user will select first row
as column (a) and second row as expression ((a+b)/2) so we will have to
create table with "*PARTITION BY RANGE (a, ((a+b)/2)*". I think we should
consider this as well.

>>    comma separated values. Refer "*Range_Partition.png*". In case of
>>    List partition there are two columns "Name" and "Values In" and user will
>>    have to provide (,) separated list for "Values In" column. Refer "
>>    *List_Partition.png*".
>>
>>    - Once user will define the partition keys and partitions based on
>>    the columns define for main table and then user will rename/delete any
>>    column from "Columns" Tab we will warn user about renaming/deleting any
>>    column will reset all the rows define under partitions tab. This is just
>>    because we can rename/delete the column from partition keys but not sure
>>    how we can remove/rename it from the expression defined by the user.
>>
> We can probably leverage the same table. We're investigating constraints
> as well.
>
> Shirley & Rob
>
>>

---

From: Shirley Wang <[email-masked]>
Date: Tue, 23 May 2017 14:09:37 +0000

It's less about implementing what's easy, and more about implementing what
we know for sure will provide user value.

Given that in the expression you used, the variables 'a' and 'b' have
undefined values, there's still a chance that we're missing a large chunk
of context for successful implementation.

Could you explain what 'a' and 'b' represent?
Where would users find values for 'a' and 'b'? (are they columns?)
What is the use case for partitioning by (a+b)/2?
How frequently will people partition this way?

It's possible to design for the range and list partitions and know we can
achieve success because we understand how users would go through this
workflow. Not sure about expressions.

---

From: Robert Eckhardt <[email-masked]>
Date: Tue, 23 May 2017 10:39:00 -0400

> workflow. Not sure about expressions.

Maybe to pile on this a bit.

When Shirley and I were discussing the workflows it was obvious when we
were looking at 'normal' range or list partition use cases. Generally the
only open question we had about the workflow was whether or not users would
be building tables net new or whether they were more likely to have a table
that was growing too large and therefore needed to create a new partitioned
table.

We couldn't think of a reason why a user would want to take the average of
two columns and partition by this derived value. It added to the question
of why/how a user would consider this as an idea a priori or whether this
would be an insight given analysis of existing data.

I assume this was supported for a specific use case. if you could share
that it would be awesome. I guess the long and short of it is, we are
having a difficult time imagining the workflow for this feature.

-- Rob

---

From: Akshay Joshi <[email-masked]>
Date: Wed, 24 May 2017 12:51:48 +0530

Hi Shirley

> we know for sure will provide user value.

   Agreed, but if that feature(expression) is available in postgresql then
users/QA will ask why that is not present in pgAdmin4. If implementation is
complex then we could think to include it or not, if we will provide that
its upto the user they want to use it or not.

    a and b are columns.

    That is just an example of expression, there is no use case for above
example.

---

From: Akshay Joshi <[email-masked]>
Date: Wed, 24 May 2017 13:05:59 +0530

Hi Robert

> having a difficult time imagining the workflow for this feature.

   Taking average of two columns is just an example/representation of
expression, there is no use case of that. As I am also in learning phase.
Below are some use case that I can think of:

   -

   Partitions based on first letter of their username

   CREATE TABLE users (
       id             serial not null,
       username       text not null,
       password       text,
       created_on     timestamptz not null,
       last_logged_on timestamptz not null
   )PARTITION BY RANGE ( lower( left( username, 1 ) ) );
   CREATE TABLE users_0
       partition of users (id, primary key (id), unique (username))
       for values from ('a') to ('g');
   CREATE TABLE users_1
       partition of users (id, primary key (id), unique (username))
       for values from ('g') to (unbounded);

   -  Partition based on country's sale for each month of an year.

CREATE TABLE public.sales

(

    country text NOT NULL,

    sales bigint NOT NULL,

    saledate date

) PARTITION BY RANGE (country, (extract (YEAR FROM saledate)),
(extract(MONTH FROM saledate)))

CREATE TABLE public.sale_usa_2017_jan PARTITION OF sales

    FOR VALUES FROM ('usa', 2017, 01) TO ('usa', 2017, 02);

CREATE TABLE public.sale_india_2017_jan PARTITION OF sales

    FOR VALUES FROM ('india', 2017, 01) TO ('india', 2017, 02);

CREATE TABLE public.sale_uk_2017_jan PARTITION OF sales

    FOR VALUES FROM ('uk', 2017, 01) TO ('uk', 2017, 02);

INSERT INTO sales VALUES ('india', 10000, '2017-1-15');

INSERT INTO sales VALUES ('uk', 20000, '2017-1-08');

INSERT INTO sales VALUES ('usa', 30000, '2017-1-10');

   Apart from above there may be N number of use cases that depends on
specific requirement of user.

---

From: Robert Eckhardt <[email-masked]>
Date: Wed, 24 May 2017 10:30:16 -0400

On Wed, May 24, 2017 at 3:35 AM, Akshay Joshi <[email-masked]

> specific requirement of user.

Thank you for the example, you are absolutely correct and we were confused.

Given our new found understanding do you mind if we iterate a bit on the
UI/UX?  What we were suggesting with the daily/monthly/yearly drop down was
a specific example of an expression. Given that fact that doesn't seem to
be required in an MVP, however, I do think a more interactive experience
between the definition of the child partitions and the creation of the
partitions would be optimal.

I'm not sure where you are with respect to implementing the UI but I'd love
to float some ideas and mock ups past you.

-- Rob

---

From: Akshay Joshi <[email-masked]>
Date: Fri, 2 Jun 2017 13:31:27 +0530

Hi All

Following are the further implementation updates to support Declarative
Partitioning:

   - Show all the existing partitions of the parent table in Partitions tab
   (Refer Existing_Partitions.png)
   - Ability to create N partitions and detach existing partitions. Refer
   (Create_Detach_Partition.png), in this example I have detach two
   existing partition and create two new partitions.
   - Added "Detach Partition" menu to partitions node only and user will be
   able to detach from there as well. Refer (Detach.png)

On Wed, May 24, 2017 at 8:00 PM, Robert Eckhardt <[email-masked]>
wrote:

>
>
> On Wed, May 24, 2017 at 3:35 AM, Akshay Joshi <
> I'm not sure where you are with respect to implementing the UI but I'd
> love to float some ideas and mock ups past you.
>
> -- Rob
>

---

From: Dave Page <[email-masked]>
Date: Mon, 5 Jun 2017 10:21:13 +0100

On Fri, Jun 2, 2017 at 9:01 AM, Akshay Joshi <[email-masked]>
wrote:

>    - Show all the existing partitions of the parent table in Partitions
>    tab (Refer Existing_Partitions.png)
>    - Added "Detach Partition" menu to partitions node only and user will
>    be able to detach from there as well. Refer (Detach.png)
>
> That's looking good to me :-)

---

From: Shirley Wang <[email-masked]>
Date: Mon, 05 Jun 2017 15:34:51 +0000

Hi!

Here's a summary of the interviews thus far:

DBAs tend to create a partition strategy based on their experience and some
alignment on their end users goals (analysts, report writers, and app
developers). Once that partition strategy is created, they are usually
forced to iterate on that strategy later based on feedback from end users
of what the usage pattern are.

We've identified a couple workflows that are key in partitioning based on
whether they are maintaining a successful strategy or iterating to improve
the strategy.

One workflow is for rollups, which is for maintaining partitions at
different granularities as data ages. We've learned that older data is less
acted upon than recent data so users group together older data for viewing
purposes. The other workflow is for splits, which when users discover that
the data isn't granular enough so a single partition is being leveraged too
many times. Users need to then reevaluate their strategy and tune
partitions.

To reevaluate strategies, DBAs ask themselves a few questions
- Is the partition stable?
- Are the queries analysts, report writers, and app developers are writing
getting the correct data?
- Are the partitions organized in a way that analysts, report writers, and
app developers are able to achieve their goals? (ex. goals for app
developer might be fast query while goal for report writer might be ability
to get data so they can turn out reports faster. Goals might be conflicting)

There are two needs from DBAs in terms of tuning partitioning strategies
(there are more but addressing these two will provide the most value to
users). One is to modify one or more child partitions by adding indexes or
other such things, and the other is to recreate the parent table because
there is inheritance to consider.

For the former, this can be addressed by enabling users to modify one or
more child partitions at the same time. For the latter, that is a workflow
that might be addressed outside of the create table with partition workflow
we're working on currently.

On Mon, Jun 5, 2017 at 5:21 AM Dave Page <[email-masked]> wrote:

> On Fri, Jun 2, 2017 at 9:01 AM, Akshay Joshi <
>>    - Ability to create N partitions and detach existing partitions.
>>    Refer (Create_Detach_Partition.png), in this example I have detach
>>    two existing partition and create two new partitions.

---

From: Dave Page <[email-masked]>
Date: Mon, 5 Jun 2017 16:45:32 +0100

Hi

On Mon, Jun 5, 2017 at 4:34 PM, Shirley Wang <[email-masked]> wrote:

> DBAs tend to create a partition strategy based on their experience and
> some alignment on their end users goals (analysts, report writers, and app

The former is what I was bleating about when I said we needed to expose
partitions to the user. The latter isn't relevant - declarative
partitioning in Postgres doesn't use inheritance.

So... it sounds like we're on the right lines :-)

>>>    - Added "Detach Partition" menu to partitions node only and user
>>>    will be able to detach from there as well. Refer (Detach.png)
>>>
>>>> Given our new found understanding do you mind if we iterate a bit on
>>>> the UI/UX?  What we were suggesting with the daily/monthly/yearly drop down
>>>> was a specific example of an expression. Given that fact that doesn't seem
>>>> to be required in an MVP, however, I do think a more interactive experience

---

From: Robert Eckhardt <[email-masked]>
Date: Mon, 5 Jun 2017 12:17:21 -0400

> partitioning in Postgres doesn't use inheritance.

The former is certainly the most interesting.  We do need to expose the
partitions but only exposing them individually might be a bit overwhelming.
What we found was that the number of partitions users have, (given existing
means of leveraging partitions) vary from ~100 up to 10k. Basically what we
were thinking about was how we can create a workflow/interface that allows
users to modify one or more children at once. Furthermore, it would be nice
if we could figure out an easy (easy-ish) way for users to identify the one
or more partitions that need to be modified.

For roll up this pattern seems obvious, identify the n partitions you
need/want to combine and then run a job to combine them.

For other patterns such as creating indexes and such it requires a bit more
thought. Generally users described wanting to treat all of the children
like a single table (just like Oracle), however, other users described
potentially modifying chunks of partitions differently depending on some
criterion. This means that users will need to identify the subset they want
to optimize and then ideally be able to act on them all at once.

-- Rob

>>>>    - Show all the existing partitions of the parent table in
>>>>    Partitions tab (Refer Existing_Partitions.png)
>>>>>>    Apart from above there may be N number of use cases that depends
>>>>>> on specific requirement of user.
>>>>>>
>>>>>

---

From: Robert Eckhardt <[email-masked]>
Date: Mon, 5 Jun 2017 12:19:24 -0400

I guess what I didn't say is YES. What Akshay and Ashesh are building is
going to absolutely be fundamental to any workflows being defined in these
interviews.

-- Rob

On Mon, Jun 5, 2017 at 12:17 PM, Robert Eckhardt <[email-masked]>
wrote:

> For other patterns such as creating indexes and such it requires a bit
> more thought. Generally users described wanting to treat all of the
> children like a single table (just like Oracle), however, other users
> described potentially modifying chunks of partitions differently depending
> on some criterion. This means that users will need to identify the subset
> they want to optimize and then ideally be able to act on them all at once.
>
> -- Rob
>
>
>
>
>
>
>>
>>>>> Following are the further implementation updates to support
>>>>> Declarative Partitioning:
>>>>>
>>>>> On Wed, May 24, 2017 at 8:00 PM, Robert Eckhardt <[email-masked]
>>>>> > wrote:
>>>>>
>>>>>>
>>>>>>
>>>>>>>    Taking average of two columns is just an example/representation
>>>>>>> of expression, there is no use case of that. As I am also in learning
>>>>>>> phase. Below are some use case that I can think of:
>>>>>>>
>>>>>>>    -
>>>>>>>
>>>>>> I'm not sure where you are with respect to implementing the UI but
>>>>>> I'd love to float some ideas and mock ups past you.
>>>>>>
>>>>>> -- Rob
>>>>>>
>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> *Akshay Joshi*

---

From: Dave Page <[email-masked]>
Date: Tue, 6 Jun 2017 09:32:44 +0100

> or more partitions that need to be modified.

Yes, that does need more thought.

> need/want to combine and then run a job to combine them.

You're thinking Greenplum :-). There is no roll up in PostgreSQL, unless
you're thinking we should create such a feature in pgAdmin.

Of course, I have no objection to extending what we do in PG to add GP
feature support, but let's start with PG.

> they want to optimize and then ideally be able to act on them all at once.

Right.

---

From: Robert Eckhardt <[email-masked]>
Date: Tue, 6 Jun 2017 07:00:42 -0400

> feature support, but let's start with PG.

No not at all. That was a very specific and consistent pattern described by
users leveraging time based range partitions in Postgres. I'm not sure if
that same use case will be supported with partitioning as implemented in
Postgres 10 but it is a Postgres pattern.

-- Rob

>>>> For the former, this can be addressed by enabling users to modify one
>>>> or more child partitions at the same time. For the latter, that is a
>>>> workflow that might be addressed outside of the create table with partition
>>>> workflow we're working on currently.
>>>>
>>>>
>>>>
>>>>
>>>>
>>>>>>    Refer (Create_Detach_Partition.png), in this example I have
>>>>>>    detach two existing partition and create two new partitions.
>>>>>> On Wed, May 24, 2017 at 8:00 PM, Robert Eckhardt <
>>>>>> [email-masked]> wrote:
>>>>>>
>>>>>>>
>>>>>>>

---

From: Akshay Joshi <[email-masked]>
Date: Tue, 13 Jun 2017 19:29:33 +0530

Hi All

For further implementation following task needs to be work upon:

   - How to parse and show partitions keys. For example user has created
   below partitioned table

CREATE TABLE public.sales
(
    country character varying COLLATE pg_catalog."default" NOT NULL,
    sales bigint,
    saledate date
) PARTITION BY RANGE (*country, date_part('year'::text, sale date)*)

When user open the properties dialog I am not able to figure out how to
parse keys(displayed in bold in above example) and show them in our control
that we used. For the time being I have hide that control in 'Edit' mode
(Refer Attach Partition.png)

   - *Support of sub partitioning*: To implement sub-partitioning, specify
   the PARTITION BY clause in the commands used to create individual
   partitions, for example:
   -

   CREATE TABLE measurement_y2006 PARTITION OF measurement
       FOR VALUES FROM ('2006-02-01') TO ('2006-03-01')
       PARTITION BY RANGE (peaktemp);

           To achieve above I have made some changes in GUI (Refer Sub
Partition.png).
           *Complex and challenging part here is "measurement_y2006" is
partition of "measurement" and parent table for other partitions too which
user can create later. How we will going to show this in browser tree? *
           One option could be
           Tables
             ->measurement(table)
               ->Partitions
                 ->measurement_y2006(Partition of measurement and parent of
p1)
                   ->Partitions
                     ->p1

   - *Attach Partitions*: To implement attach N partitions I have made some
   changes in GUI( Refer Attach Partition.png). Attach Partitions control
   will only be visible in "Edit" mode.

I have only modified the UI changes, there are lots of work needs to be
done to complete that.
Please review the design. Suggestions/Comments are welcome.

On Tue, Jun 6, 2017 at 4:30 PM, Robert Eckhardt <[email-masked]>
wrote:

> No not at all. That was a very specific and consistent pattern described
> by users leveraging time based range partitions in Postgres. I'm not sure
> if that same use case will be supported with partitioning as implemented in
>>>>>>>>>    Apart from above there may be N number of use cases that
>>>>>>>>> depends on specific requirement of user.
>>>>>>>>>
>>>>>>>>
>>>>>>>> Given our new found understanding do you mind if we iterate a bit
>>>>>>>> on the UI/UX?  What we were suggesting with the daily/monthly/yearly drop
>>>>>>>> down was a specific example of an expression. Given that fact that doesn't
>>>>>>>> seem to be required in an MVP, however, I do think a more interactive
>>>>>>>> experience between the definition of the child partitions and the creation
>>>>>>>> of the partitions would be optimal.
>>>>>>>>

---

From: Robert Eckhardt <[email-masked]>
Date: Tue, 13 Jun 2017 09:46:09 -0700

Akshay,

Have you determined the minimum feature set you are shooting for before you
commit this? The reason I ask is that we were thinking that some level of
simple automation would probably be nice to make this super useful.

Basically if you consider a simple example of partitioning 90 days of data
by day the manual process of creating the names and to - from fields
becomes rather painful. If you couple that with potentially wanting to do
list subpartitioning if just multiplies the work.

If we could get something committed then we could more easily work to
define where simple automation makes sense and where it doesn't.

-- Rob

On Tue, Jun 13, 2017 at 6:59 AM, Akshay Joshi <[email-masked]

>    - *Support of sub partitioning*: To implement sub-partitioning,
>    specify the PARTITION BY clause in the commands used to create individual
>    partitions, for example:
>    -
>
>                  ->measurement_y2006(Partition of measurement and parent
> of p1)
>                    ->Partitions
>                      ->p1
>
>    - *Attach Partitions*: To implement attach N partitions I have made
>    some changes in GUI( Refer Attach Partition.png). Attach Partitions
>    control will only be visible in "Edit" mode.
>
>>>>>>>>    - Ability to create N partitions and detach existing
>>>>>>>>    partitions. Refer (Create_Detach_Partition.png), in this
>>>>>>>>    example I have detach two existing partition and create two new partitions.
>>>>>>>>    - Added "Detach Partition" menu to partitions node only and
>>>>>>>>    user will be able to detach from there as well. Refer (Detach.
>>>>>>>>    png)
>>>>>>>>
>>>>>>>>>>    Taking average of two columns is just an
>>>>>>>>>> example/representation of expression, there is no use case of that. As I am
>>>>>>>>>> also in learning phase. Below are some use case that I can think of:
>>>>>>>>>>
>>>>>>>>>>    -
>>>>>>>>>>

---

From: Shirley Wang <[email-masked]>
Date: Tue, 13 Jun 2017 17:28:57 +0000

To add some interview context - from talking to DBAs, partitioning is the
most frequent use case we should account for first. Breaking down child
partitions into smaller pieces wasn't something we heard as a need. This is
probably not the highest priority work needed now.

We should think about when it would be good to release partitioning for
some feedback. Did you already have this planned out?

On Tue, Jun 13, 2017 at 9:46 AM Robert Eckhardt <[email-masked]>
wrote:

> Akshay,
>
> Have you determined the minimum feature set you are shooting for before
> you commit this? The reason I ask is that we were thinking that some level
> of simple automation would probably be nice to make this super useful.
>

> On Tue, Jun 13, 2017 at 6:59 AM, Akshay Joshi <
>>>> You're thinking Greenplum :-). There is no roll up in PostgreSQL,
>>>> unless you're thinking we should create such a feature in pgAdmin.
>>>>
>>>>>>> For the former, this can be addressed by enabling users to modify
>>>>>>> one or more child partitions at the same time. For the latter, that is a
>>>>>>>>>> I'm not sure where you are with respect to implementing the UI
>>>>>>>>>> but I'd love to float some ideas and mock ups past you.
>>>>>>>>>>
>>>>>>>>>> -- Rob
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> --
>>>>>>>>> *Akshay Joshi*

---

From: Shirley Wang <[email-masked]>
Date: Tue, 13 Jun 2017 23:59:46 +0000

Some questions/comments/sketches:

- The constraints tab as is implemented now enables constraints for the
parent table, correct? If so, and if someone wants to apply constraints to
partitions, as I understand it they would need to apply that to each child
partition. If that is true, then the tab structure along the top would need
to change to show constraints only after child partitions are created to
avoid confusion. OR perhaps at the menu level, have an option for creating
a partitioned table rather than table.

- The steps to create a table with partitions seems to have very clear
steps. I think it would benefit from a more 'wizard-like' step by step flow.

Here's a sketch of a workflow. Just to note, the ellipses are for other
tabs that might be necessary. I wasn't sure if there were any.

Also, at this point don't worry about buttons versus a dropdown - those can
change.

*01 fill out general info. User would click next.*

*02 User fills out info for columns. Since inheriting columns is not
possible, we should remove that field entirely.*

*03 User selects partition type from drop down menu.*

*04 Options specific to Range partitions appear (ex. columns and width of
dates)*

*05 User selects the column they want to partition by from the drop down,
which should have columns prepopulated as options to choose from.*

*06 Once the user selects 'Day', 'month', or 'year' partitions, they see a
loading screen as the app generates partitions*

*07 Partitions appear below as editable fields if users need to modify
certain partition names. *
*Note: We're assuming that the best naming convention here is the
[tablename_partitionrange], but this could change. Given that a user might
be creating tens or hundreds of partitions, this would be valuable to those
who need a memorable name quickly.*

*08 Constraints*
Not sure what would need to go on this screen or if it's necessary at this
level to apply triggers or keys.

Also, here's a link to a clickable mockup if you'd like to try going
through the app.
https://pivotal.invisionapp.com/share/6ZC5B0JEM#/238756157_01_General

Shirley

On Tue, Jun 13, 2017 at 10:28 AM Shirley Wang <[email-masked]> wrote:

>> Basically if you consider a simple example of partitioning 90 days of
>> data by day the manual process of creating the names and to - from fields
>>>    - How to parse and show partitions keys. For example user has
>>>    created below partitioned table
>>>
>>>> No not at all. That was a very specific and consistent pattern
>>>> described by users leveraging time based range partitions in Postgres. I'm
>>>> not sure if that same use case will be supported with partitioning as
>>>> implemented in Postgres 10 but it is a Postgres pattern.
>>>>
>>>> -- Rob
>>>>
>>>>
>>>>>
>>>>>
>>>>>>
>>>>>> For other patterns such as creating indexes and such it requires a
>>>>>> bit more thought. Generally users described wanting to treat all of the
>>>>>>>>>>> Given our new found understanding do you mind if we iterate a
>>>>>>>>>>> bit on the UI/UX?  What we were suggesting with the daily/monthly/yearly
>>>>>>>>>>> drop down was a specific example of an expression. Given that fact that
>>>>>>>>>>> doesn't seem to be required in an MVP, however, I do think a more
>>>>>>>>>>> interactive experience between the definition of the child partitions and
>>>>>>>>>>> the creation of the partitions would be optimal.
>>>>>>>>>>>

---

From: Dave Page <[email-masked]>
Date: Wed, 14 Jun 2017 09:29:30 +0100

On Tue, Jun 13, 2017 at 2:59 PM, Akshay Joshi <[email-masked]

> (Refer Attach Partition.png)
I assume psql with display that info with \dt or similar? What does it do?
Failing that, look at pg_dump?

>                  ->measurement_y2006(Partition of measurement and parent

Urgh. But yeah. I think that makes logical sense.

> I have only modified the UI changes, there are lots of work needs to be

I don't think I'd include Attach on the dialog. I think it should be a
separate menu option, with a simple dialogue to let the user choose the
table to attach.

The reason for that is that Attach is an action not a property. On the
Properties panels we expect any changes we make to be the same the next
time the dialogue is opened - e.g. if you toggle "Enable Trigger" to
disabled and hit OK, then next time you open the dialogue you see the
switch in the same position. With Attach, that's not the case - you'll list
one or more tables to attach, hit OK, and when you next open the Properties
dialogue, those partitions will be listed in the partition list, not the
Attach list.

---

From: Ashesh Vashi <[email-masked]>
Date: Wed, 14 Jun 2017 16:11:36 +0530

Hi Shirley,

Please see my reply inline..

> a partitioned table rather than table.
Couple of observation about the table constraints:

- PostgreSQL allow to create CHECK constraint on both partition (parent)
table, and its children partitions.
- Rest of the constraints may not be applicable on the parent table.

So - the question is: whether we should allow to create constraints for the
child partitions from the parent node, or not.

If yes - then:
We will need a switch control (flag) in each of the constraints (in the
subnode control) to distinguish whether the constraint will be applied on
parent/child table.

It think - we can also implement bulk triggers, constraints, indexes from
the parent table, which can be applied to all the children partitions.
We can give these functionality in the

> steps. I think it would benefit from a more 'wizard-like' step by step
Hmm.
Are you saying - we should switch to use the Wizard for table creation?
I think - that should be done in as a separate module.

Any way - we will need to properties dialog for editing the properties of
an existing partition table.

> Also, at this point don't worry about buttons versus a dropdown - those
> can change.
>

I think - there are some details missing.

Here - we're limiting the range/list partition only on one column, which is
clearly wrong.
partition can be any number of columns.

Also - column can be of any type (not just date).

Current implementation (WIP) patch, shared by Akshay, has consider all
those possibilities.

> level to apply triggers or keys.
A parent table can have CHECK constraints.
So - we need the constraints tab.

-- Thanks, Ashesh

>>>> When user open the properties dialog I am not able to figure out how
>>>> to parse keys(displayed in bold in above example) and show them in our
>>>> control that we used. For the time being I have hide that control in 'Edit'
>>>> mode (Refer Attach Partition.png)
>>>>
>>>>
>>>>            *Complex and challenging part here is "measurement_y2006"
>>>> is partition of "measurement" and parent table for other partitions too
>>>> which user can create later. How we will going to show this in browser
>>>> tree? *
>>>>            One option could be
>>>>            Tables
>>>>              ->measurement(table)
>>>>                ->Partitions
>>>>                  ->measurement_y2006(Partition of measurement and
>>>> parent of p1)
>>>>                    ->Partitions
>>>>                      ->p1
>>>>
>>>> I have only modified the UI changes, there are lots of work needs to
>>>> be done to complete that.
>>>>>> Of course, I have no objection to extending what we do in PG to add
>>>>>> GP feature support, but let's start with PG.
>>>>>>
>>>>>

---

From: Ashesh Vashi <[email-masked]>
Date: Wed, 14 Jun 2017 17:36:16 +0530

On Wed, Jun 14, 2017 at 1:59 PM, Dave Page <[email-masked]> wrote:

>
>
> On Tue, Jun 13, 2017 at 2:59 PM, Akshay Joshi <
psql, and pg_dump use 'pg_get_partkeydef' function for reverse engineering,
and we too.
They don't need particular key information.

In properties dialog, we need to find out - what individual partition key
is? (column/expression).

Let me give an example.
I have a partition table with the following definition (with two partition
keys).

*CREATE TABLE public.sales*
*(*
*    country character varying COLLATE pg_catalog."default" NOT NULL,*
*    sales bigint,*
*    saledate date*
*) PARTITION BY RANGE (country, EXTRACT(year from saledate));*

And, the following query will give as:
*SELECT relname, pg_get_partkeydef(oid) FROM pg_catalog.pg_class WHERE
relname like 'sal%';*

 relname |                 *pg_get_partkeydef*
---------+----------------------------------------------------
 sales   | *RANGE (country, date_part('year'::text, saledate))*

Here - we have two option in edit mode.
1. Parse the output of the  '*pg_get_partkeydef'*, and identify all
individual keys, and its detailed information (i.e. column/expression)
2. Show that output about the partition keys in static control, and hide
the Partition type, partition keys controls.

I prefer the second option, as user can not modify the partition keys/type
(RANGE/LIST), and we will not have to write logic to parse the keys from
that output.

What do you say?

-- Thanks, Ashesh

---

From: Dave Page <[email-masked]>
Date: Wed, 14 Jun 2017 13:09:15 +0100

On Wed, Jun 14, 2017 at 1:06 PM, Ashesh Vashi <[email-masked]

>> I assume psql with display that info with \dt or similar? What does it
>> do? Failing that, look at pg_dump?
>>
> psql, and pg_dump use 'pg_get_partkeydef' function for reverse
> engineering, and we too.

I agree.

---

From: Shirley Wang <[email-masked]>
Date: Wed, 14 Jun 2017 22:20:51 +0000

On Wed, Jun 14, 2017 at 3:42 AM Ashesh Vashi <[email-masked]>
wrote:

> So - the question is: whether we should allow to create constraints for
> the child partitions from the parent node, or not.
>
> If yes - then:

> We can give these functionality in the

I agree with you, potentially, as long as users are only seeing relevant
info on that screen. For example, if only CHECK constraints work, if a user
is creating a partitioned table they shouldn't be able to see non-working
functionality. And if a user is creating a regular table, they should not
see any additional UI elements that only relate to partitioning,

With what we have now, is there a way to do that gracefully? I feel like
it'll be kind of difficult to navigate, especially with constraints. What
are your thoughts on having 'Create -table' and 'Create-partitioned table'
as two separate options?

> an existing partition table.

yes definitely. I don't think they need to be separate, the properties
dialog could be a part of 'wizard'. Also, what do you mean by done in a
separate module

---

From: Akshay Joshi <[email-masked]>
Date: Thu, 15 Jun 2017 14:42:45 +0530

On Wed, Jun 14, 2017 at 5:39 PM, Dave Page <[email-masked]> wrote:

>
>
> On Wed, Jun 14, 2017 at 1:06 PM, Ashesh Vashi <
> [email-masked]> wrote:
>
>> I have a partition table with the following definition (with two
>> partition keys).
>>
>> I prefer the second option, as user can not modify the partition
>> keys/type (RANGE/LIST), and we will not have to write logic to parse the
>> keys from that output.
>>
>> What do you say?
>>
>
> I agree.
>

   I have modified the logic as per above suggestion. In create mode we
will show "Partition Type" and "Partition Keys", so that user will be able
to create partitioned table and in edit mode we will show "Partition
Scheme" in NoteControl, as it's been difficult to parse and identify
whether it is a column or expression. Please refer Create_Table.png.

  Apart from that as per discussion with Dave yesterday I have remove the
"Attach Partition" control and merge that functionality into "Partitions"
control. I have added one switch control with text (Attach/Create). By
default this control is disabled in create mode, while in edit mode user
can create/attach partitions. When user select create then "Name" is input
control and when user selects attach then "Name" is select2 control
containing list of all the suitable(with same columns, datatype and oids )
tables to be attached. Refer Edit_Table.png

 I have also added one NoteControl at the bottom which will give
information about the Partitions control how to use that. Please correct
the string if it looks wrong.

---

From: Shirley Wang <[email-masked]>
Date: Fri, 16 Jun 2017 17:46:44 +0000

Looks good. I noticed people clicking back and forth to the columns tab to
remember which columns they've created while filling out the Expressions
column. It might be better to have a list of the columns and the datatype
above the 'Partition Keys' subnode and have columns as a type field rather
than a drop down.

Also, I think the fields someone sees after selecting the Key type needs to
depend on what they select. Seeing both Column and Expressions type field
might lead someone to think they need to fill out both fields.

When is the 'In' column in the Partitions subnode enabled?

For the NoteControl on the bottom, what do 'Mode Control' or 'Attach Mode'
refer to? And how can I tell the difference between 'Create Mode' and 'Edit
Mode'?

On Thu, Jun 15, 2017 at 2:12 AM Akshay Joshi <[email-masked]>
wrote:

>>> In properties dialog, we need to find out - what individual partition
>>> key is? (column/expression).
>>>
>>> Let me give an example.
> containing list of all the suitable(with same columns, datatype and oids
> ) tables to be attached. Refer Edit_Table.png
>
>>>>>    - *Attach Partitions*: To implement attach N partitions I have
>>>>>    made some changes in GUI( Refer Attach Partition.png). Attach
>>>>>    Partitions control will only be visible in "Edit" mode.
>>>>>
>>>>>>> For roll up this pattern seems obvious, identify the n partitions
>>>>>>>> you need/want to combine and then run a job to combine them.
>>>>>>>>
>>>>>>>

---

From: Akshay Joshi <[email-masked]>
Date: Mon, 19 Jun 2017 11:29:01 +0530

> above the 'Partition Keys' subnode and have columns as a type field rather

   I think we should not duplicate that data as we already have all the
information on "Columns" tab and by providing drop down user can select
columns from there only.

>
> Also, I think the fields someone sees after selecting the Key type needs
> to depend on what they select. Seeing both Column and Expressions type
> field might lead someone to think they need to fill out both fields.
>

   We can't, because user can select one column and provide an expression
as partition key in this case we will have to show both the columns in
subnode control. Anyways when user select columns I have disabled the
expression cell and if user selects expression column cell is disabled.

> When is the 'In' column in the Partitions subnode enabled?

    In case of 'List' Partition.

> refer to? And how can I tell the difference between 'Create Mode' and 'Edit

   'Mode control' is a switch control in subnode control that should be
"Mode switch control". 'Create Mode' is when user creates the new table by
clicking create-> table and 'Edit Mode' is when user open the properties
dialog for the existing table. In case of 'Edit Mode' there are two ways
user can create/attach partitions. In Attach mode we will identify and list
down the suitable tables to be attached.

>
>
>
> On Thu, Jun 15, 2017 at 2:12 AM Akshay Joshi <
>>>> 2. Show that output about the partition keys in static control, and
>>>> hide the Partition type, partition keys controls.
>>>>
>>>>>>            To achieve above I have made some changes in GUI (Refer
>>>>>> Sub Partition.png).
>>>>>> On Tue, Jun 6, 2017 at 4:30 PM, Robert Eckhardt <[email-masked]
>>>>>> > wrote:
>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>>>>> For the former, this can be addressed by enabling users to
>>>>>>>>>>> modify one or more child partitions at the same time. For the latter, that
>>>>>>>>>>> is a workflow that might be addressed outside of the create table with
>>>>>>>>>>> partition workflow we're working on currently.
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>>>    - Added "Detach Partition" menu to partitions node only
>>>>>>>>>>>>>    and user will be able to detach from there as well. Refer (Detach.
>>>>>>>>>>>>>    png)
>>>>>>>>>>>>>
>>>>>>>>>>>>>> I'm not sure where you are with respect to implementing the
>>>>>>>>>>>>>> UI but I'd love to float some ideas and mock ups past you.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> -- Rob
>>>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> --
>>>>>>>>>>>>> *Akshay Joshi*

---

From: Akshay Joshi <[email-masked]>
Date: Mon, 19 Jun 2017 15:01:32 +0530

Hi All

Attached is the latest WIP patch. Following task is completed

   - User will be able to create partitioned table (Range and List) using
   columns and expression as partition key.
   - User will be able to create N partitions while creating the
   partitioned table itself.
   - User will be able to see SQL of all partitions (child tables) along
   with the partitioned table in SQL pane when select parent node.
   - Controls are disabled/hide from table dialog which are not supported
   by Partitioning.
   - User will be able to create/attach/detach N no of partitions from the
   parent table dialog.
   - User will be able to detach partition by selecting and clicking on
   "Detach Partition" menu. Visible only on partitions.

Following tasks are remaining:

   - Refresh Tables/Partitions collection when any new node is created or
   removed from the collection. For example user will detach/create/attach N
   no of partitions from parent table in that case we will have to refresh the
   complete Tables/Partitions collection. *Need some suggestions how we can
   achieve it*.
   - Table dialog for child tables(Partitions). (Harshal working on it.)
   - Displaying Constraints, Indexes, Rules, Triggers collection/node when
   expanding partition node. (Harshal working on it.)

Please review/run the latest patch and let me know your
thoughts/suggestions on it.

On Mon, Jun 19, 2017 at 11:29 AM, Akshay Joshi <
[email-masked]> wrote:

>> Looks good. I noticed people clicking back and forth to the columns tab
>> to remember which columns they've created while filling out the Expressions
>> For the NoteControl on the bottom, what do 'Mode Control' or 'Attach
>> Mode' refer to? And how can I tell the difference between 'Create Mode' and
>> 'Edit Mode'?
>>
>
>>>>>>> When user open the properties dialog I am not able to figure out
>>>>>>> how to parse keys(displayed in bold in above example) and show them in our
>>>>>> I assume psql with display that info with \dt or similar? What does
>>>>>> it do? Failing that, look at pg_dump?
>>>>>>
>>>   Apart from that as per discussion with Dave yesterday I have remove
>>> the "Attach Partition" control and merge that functionality into
>>> "Partitions" control. I have added one switch control with text
>>> (Attach/Create). By default this control is disabled in create mode, while
>>> in edit mode user can create/attach partitions. When user select create
>>> then "Name" is input control and when user selects attach then "Name" is
>>> select2 control containing list of all the suitable(with same columns,
>>> datatype and oids ) tables to be attached. Refer Edit_Table.png
>>>
>>>>>>>            *Complex and challenging part here is
>>>>>>> "measurement_y2006" is partition of "measurement" and parent table for
>>>>>>> other partitions too which user can create later. How we will going to show
>>>>>>> this in browser tree? *
>>>>>>>            One option could be
>>>>>>>            Tables
>>>>>>>              ->measurement(table)
>>>>>>>                ->Partitions
>>>>>>> I have only modified the UI changes, there are lots of work needs
>>>>>>> to be done to complete that.
>>>>>>>
>>>>>>
>>>>>> I don't think I'd include Attach on the dialog. I think it should be
>>>>>> a separate menu option, with a simple dialogue to let the user choose the
>>>>>> table to attach.
>>>>>>
>>>>>> The reason for that is that Attach is an action not a property. On
>>>>>> the Properties panels we expect any changes we make to be the same the next
>>>>>>> On Tue, Jun 6, 2017 at 4:30 PM, Robert Eckhardt <reckhardt@pivotal.
>>>>>>> io> wrote:
>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>> Of course, I have no objection to extending what we do in PG to
>>>>>>>>> add GP feature support, but let's start with PG.
>>>>>>>>>
>>>>>>>>
>>>>>>>>>> For other patterns such as creating indexes and such it requires
>>>>>>>>>> a bit more thought. Generally users described wanting to treat all of the
>>>>>>>>>>>>>>> Given our new found understanding do you mind if we iterate
>>>>>>>>>>>>>>> a bit on the UI/UX?  What we were suggesting with the daily/monthly/yearly

---

From: Dave Page <[email-masked]>
Date: Mon, 19 Jun 2017 11:58:26 +0100

Hi

On Mon, Jun 19, 2017 at 10:31 AM, Akshay Joshi <
[email-masked]> wrote:

>    - User will be able to create/attach/detach N no of partitions from
>    the parent table dialog.
>    complete Tables/Partitions collection. *Need some suggestions how we
>    can achieve it*.
>    - Displaying Constraints, Indexes, Rules, Triggers collection/node
>    when expanding partition node. (Harshal working on it.)
>

I did some quick testing, and the main issue for me is the fact that you're
automatically quoting values in the from/to/in lists. I think that should
be left to the user (as is the case for default values), as it's easy to
get wrong. I tried to create a table partitioned by date for example, and
it quoted my dates which created incorrect SQL. I also found that it was
displaying lists incorrectly as well - for example, given:

CREATE TABLE cities (
    city_id         bigserial not null,
    name         text not null,
    population   int
) PARTITION BY LIST (initcap(name));

CREATE TABLE cities_west
    PARTITION OF cities (
    CONSTRAINT city_id_nonzero CHECK (city_id != 0)
) FOR VALUES IN ('Los Angeles', 'San Francisco');

The "in" list is shown as

'Los

Otherwise, I think it's looking good. We'll need to do some string review,
but that's minor.

>>>>>> *SELECT relname, pg_get_partkeydef(oid) FROM pg_catalog.pg_class
>>>>>> WHERE relname like 'sal%';*
>>>>>>
>>>>>>>>>>>>>>>    - Show all the existing partitions of the parent table
>>>>>>>>>>>>>>>    in Partitions tab (Refer Existing_Partitions.png)
>>>>>>>>>>>>>>>>> ) PARTITION BY RANGE (country, (extract (YEAR FROM
>>>>>>>>>>>>>>>>> saledate)), (extract(MONTH FROM saledate)))
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>    Apart from above there may be N number of use cases
>>>>>>>>>>>>>>>>> that depends on specific requirement of user.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>

---

From: Akshay Joshi <[email-masked]>
Date: Mon, 19 Jun 2017 18:37:15 +0530

On Mon, Jun 19, 2017 at 4:28 PM, Dave Page <[email-masked]> wrote:

>>    - User will be able to create partitioned table (Range and List)
>>    using columns and expression as partition key.
>>    - Controls are disabled/hide from table dialog which are not
>>    supported by Partitioning.
>>    - Refresh Tables/Partitions collection when any new node is created
>>    or removed from the collection. For example user will detach/create/attach
>>    N no of partitions from parent table in that case we will have to refresh
>>    the complete Tables/Partitions collection. *Need some suggestions how
>>    we can achieve it*.
> I did some quick testing, and the main issue for me is the fact that
> you're automatically quoting values in the from/to/in lists. I think that
> should be left to the user (as is the case for default values), as it's
> easy to get wrong. I tried to create a table partitioned by date for
> example, and it quoted my dates which created incorrect SQL. I also found
> that it was displaying lists incorrectly as well - for example, given:
>

    Should I remove the quotes from displaying it for existing partitions
too? If I'll remove quoting then user will have to add quote for each comma
separated value for the "from/to/in" field. For example '100','200','300'

>>>> Also, I think the fields someone sees after selecting the Key type
>>>> needs to depend on what they select. Seeing both Column and Expressions
>>>> type field might lead someone to think they need to fill out both fields.
>>>>
>>>
>>>    We can't, because user can select one column and provide an
>>> expression as partition key in this case we will have to show both the
>>> columns in subnode control. Anyways when user select columns I have
>>> disabled the expression cell and if user selects expression column cell is
>>> disabled.
>>>
>>>>
>>>>>>> In properties dialog, we need to find out - what individual
>>>>>>> partition key is? (column/expression).
>>>>>>>
>>>>>>> Let me give an example.
>>>>>    I have modified the logic as per above suggestion. In create mode
>>>>> we will show "Partition Type" and "Partition Keys", so that user will be
>>>>> able to create partitioned table and in edit mode we will show "Partition
>>>>>>>>>    - *Support of sub partitioning*: To implement
>>>>>>>>>    sub-partitioning, specify the PARTITION BY clause in the commands used to
>>>>>>>>>    create individual partitions, for example:
>>>>>>>>>    -
>>>>>>>>>
>>>>>>>> I don't think I'd include Attach on the dialog. I think it should
>>>>>>>> be a separate menu option, with a simple dialogue to let the user choose
>>>>>>>> the table to attach.
>>>>>>>>
>>>>>>>>> On Tue, Jun 6, 2017 at 4:30 PM, Robert Eckhardt <reckhardt
>>>>>>>>> @pivotal.io> wrote:
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>> For roll up this pattern seems obvious, identify the n
>>>>>>>>>>>> partitions you need/want to combine and then run a job to combine them.
>>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> You're thinking Greenplum :-). There is no roll up in
>>>>>>>>>>> PostgreSQL, unless you're thinking we should create such a feature in
>>>>>>>>>>> pgAdmin.
>>>>>>>>>>>
>>>>>>>>>>>> For other patterns such as creating indexes and such it
>>>>>>>>>>>> requires a bit more thought. Generally users described wanting to treat all
>>>>>>>>>>>> of the children like a single table (just like Oracle), however, other
>>>>>>>>>>>> users described potentially modifying chunks of partitions differently
>>>>>>>>>>>> depending on some criterion. This means that users will need to identify
>>>>>>>>>>>> the subset they want to optimize and then ideally be able to act on them
>>>>>>>>>>>> all at once.
>>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> Right.
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> -- Rob
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>    partitions. Refer (Create_Detach_Partition.png), in
>>>>>>>>>>>>>>>>    this example I have detach two existing partition and create two new
>>>>>>>>>>>>>>>>    partitions.
>>>>>>>>>>>>>>>>> Given our new found understanding do you mind if we
>>>>>>>>>>>>>>>>> iterate a bit on the UI/UX?  What we were suggesting with the
>>>>>>>>>>>>>>>>> daily/monthly/yearly drop down was a specific example of an expression.
>>>>>>>>>>>>>>>>> Given that fact that doesn't seem to be required in an MVP, however, I do
>>>>>>>>>>>>>>>>> think a more interactive experience between the definition of the child
>>>>>>>>>>>>>>>>> partitions and the creation of the partitions would be optimal.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> I'm not sure where you are with respect to implementing
>>>>>>>>>>>>>>>>> the UI but I'd love to float some ideas and mock ups past you.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> -- Rob
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> --
>>>>>>>>>>>>>>>> *Akshay Joshi*
>>>>>>>>>>>>>>>> *Phone: +91 20-3058-9517 <+91%2020%203058%209517>Mobile:
>>>>>>>>>>>>>>>> +91 976-788-8246 <+91%2097678%2088246>*
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> --

---

From: Dave Page <[email-masked]>
Date: Mon, 19 Jun 2017 14:12:32 +0100

On Mon, Jun 19, 2017 at 2:07 PM, Akshay Joshi <[email-masked]

>>>    - User will be able to see SQL of all partitions (child tables)
>>>    along with the partitioned table in SQL pane when select parent node.
>>>    the complete Tables/Partitions collection. *Need some suggestions
>>>    how we can achieve it*.

Yes, I think so. That confused me when I added a partition, as I first
typed:

'New York', 'Orlando'

then

New York, Orlando

and finally:

New York,Orlando

Before I finally got what I wanted. Perhaps more importantly, what if I
wanted to partition based on 'New York, NY', 'Orlando, FL'. That wouldn't
have worked at all!

I think we should just leave it to the user to quote/list as needed, as we
do in a few other similar places.

>> Otherwise, I think it's looking good. We'll need to do some string
>> review, but that's minor.
>>
>>
>>>>> Looks good. I noticed people clicking back and forth to the columns
>>>>> tab to remember which columns they've created while filling out the
>>>>> Expressions column. It might be better to have a list of the columns and
>>>>> the datatype above the 'Partition Keys' subnode and have columns as a type
>>>>> field rather than a drop down.
>>>>>
>>>>
>>>>>>>>> I assume psql with display that info with \dt or similar? What
>>>>>>>>> does it do? Failing that, look at pg_dump?
>>>>>>>>>
>>>>>>>> 1. Parse the output of the  '*pg_get_partkeydef'*, and identify
>>>>>>>> all individual keys, and its detailed information (i.e. column/expression)
>>>>>>>>>>            To achieve above I have made some changes in GUI
>>>>>>>>>> (Refer Sub Partition.png).
>>>>>>>>>>    - *Attach Partitions*: To implement attach N partitions I
>>>>>>>>>>    have made some changes in GUI( Refer Attach Partition.png).
>>>>>>>>>>    Attach Partitions control will only be visible in "Edit" mode.
>>>>>>>>>>
>>>>>>>>>> I have only modified the UI changes, there are lots of work
>>>>>>>>>> needs to be done to complete that.
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>>>>>>>>>> Following are the further implementation updates to
>>>>>>>>>>>>>>>>> support Declarative Partitioning:
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>    - Added "Detach Partition" menu to partitions node
>>>>>>>>>>>>>>>>>    only and user will be able to detach from there as well. Refer (Detach.
>>>>>>>>>>>>>>>>>    png)
>>>>>>>>>>>>>>>>>

---

From: Harshal Dhumal <[email-masked]>
Date: Mon, 19 Jun 2017 18:49:57 +0530

Hi,

On Mon, Jun 19, 2017 at 3:01 PM, Akshay Joshi <[email-masked]

> Listing and creation of constraints, index, rules, triggers on table
partition is completed.
Also listing of table partition under table partition (upto n levels) is
also completed.

​

---

From: Shirley Wang <[email-masked]>
Date: Mon, 19 Jun 2017 18:46:01 +0000

> expression cell and if user selects expression column cell is disabled.

Ah I see what you mean. What I meant was that the column would change
depending on if someone selects Column or Expressions from the dropdown
Can a user select more than one key type? The use case I can see where
hiding 'Columns' or 'Expressions' would fail is if someone can create an
expression key type and a column key type.

Disabling a feature is one way to guide user behavior, but it doesn't
provide enough context for someone to understand why it's disabled. It's
better to only display what is absolutely necessary and hide fields that
are unrelated to the workflow.

Typically, disabling a UI element is useful when that disabled UI element
also provides some context or value while disabled. In this case, I'm not
sure it is.

If hiding options isn't possible, providing some text in the fields (like
N/A or --) would be helpful.

>     In case of 'List' Partition.

It would improve the experience if the 'In' column was removed when a user
selects 'Range' partitions then. And then if a user is creating a 'List'
partition, 'From/To' should be hidden. In this case, 'From/To' and 'In' are
dependent on that first drop down step, so seeing 'In' while on 'Range
partitions' (and 'From/To' on 'List partitions') is not providing any value.

> down the suitable tables to be attached.

I see. Describing these various states is great in case a user needs it.
What are your thoughts on having it live in the documentation of pgAdmin4
rather than in the dialog? This seems to be the established pattern for all
other explanations.

---

From: Harshal Dhumal <[email-masked]>
Date: Fri, 23 Jun 2017 18:55:04 +0530

Hi,

Please find attached patch for partition support.

This patch includes all the work done by Akashy and support for child nodes
(constraints, rules, index, triggers and partition itself ) under partition
node.

Thanks,

-- 
*Harshal Dhumal*
*Sr. Software Engineer*

EnterpriseDB India: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

On Tue, Jun 20, 2017 at 12:16 AM, Shirley Wang <[email-masked]> wrote:

>
>
> On Mon, Jun 19, 2017 at 1:59 AM Akshay Joshi <
diff --git a/web/pgadmin/browser/server_groups/servers/databases/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/__init__.py
index e40c787..a5f5119 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/__init__.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/__init__.py
@@ -14,7 +14,8 @@ import re
 from functools import wraps
 
 import pgadmin.browser.server_groups.servers as servers
-from flask import render_template, current_app, request, jsonify
+from flask import render_template, make_response, current_app, request,\
+    jsonify, url_for
 from flask_babel import gettext as _
 from pgadmin.browser.collection import CollectionNodeModule
 from pgadmin.browser.server_groups.servers.databases.utils import \
@@ -86,6 +87,20 @@ class DatabaseModule(CollectionNodeModule):
         """
         return False
 
+    def get_own_javascripts(self):
+        scripts = []
+
+        scripts.append({
+            'name': 'pgadmin.browser.table.partition.utils',
+            'path': url_for('browser.index') + 'table/static/js/partition.utils',
+            'when': self.node_type, 'is_template': False
+        })
+
+        scripts += CollectionNodeModule.get_own_javascripts(self)
+
+        return scripts
+
+
 blueprint = DatabaseModule(__name__)

diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/schema.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/schema.js
index 0e90997..ef496e7 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/schema.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/schema.js
@@ -145,6 +145,16 @@ define('pgadmin.node.schema', [
     group: gettext('Table'), mode: ['edit', 'create'],
     type: 'switch',
     disabled: function(m) {
+      // If table is partitioned table then disabled it.
+      if (m.top && m.top.get('is_partitioned')) {
+        // We also need to unset rest of all
+        setTimeout(function() {
+          m.set('autovacuum_custom', false);
+        }, 10);
+
+        return true;
+      }
+
       if(!m.top.inSchema.apply(this, [m])) {
         return false;
       }
@@ -459,6 +469,32 @@ define('pgadmin.node.schema', [
         return true;
       }
     });
+
+    pgBrowser.tableChildTreeNodeHierarchy = function(i) {
+      var idx = 0,
+          res = {},
+          t = pgBrowser.tree;
+
+      do {
+        d = t.itemData(i);
+        if (
+          d._type in pgBrowser.Nodes && pgBrowser.Nodes[d._type].hasId
+        ) {
+          if (d._type === 'partition' || d._type === 'table') {
+            if (!('table' in res)) {
+              res['table'] = _.extend({}, d, {'priority': idx});
+              idx -= 1;
+            }
+          } else {
+            res[d._type] = _.extend({}, d, {'priority': idx});
+            idx -= 1;
+          }
+        }
+        i = t.hasParent(i) ? t.parent(i) : null;
+      } while (i);
+
+      return res;
+    };
   }
 
   // Switch Cell with Deps (specifically for table children)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py
index f6fa65e..b7f122f 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py
@@ -11,22 +11,16 @@
 
 import simplejson as json
 import re
-from functools import wraps
 
 import pgadmin.browser.server_groups.servers.databases as database
 from flask import render_template, request, jsonify
 from flask_babel import gettext
 from pgadmin.browser.server_groups.servers.databases.schemas.utils \
-    import SchemaChildModule, DataTypeReader, VacuumSettings, \
-    trigger_definition, parse_rule_definition
-from pgadmin.browser.server_groups.servers.utils import parse_priv_from_db, \
-    parse_priv_to_db
-from pgadmin.browser.utils import PGChildNodeView
+    import SchemaChildModule, DataTypeReader, VacuumSettings
+from pgadmin.browser.server_groups.servers.utils import parse_priv_to_db
 from pgadmin.utils.ajax import make_json_response, internal_server_error, \
     make_response as ajax_response, gone
-from pgadmin.utils.driver import get_driver
-
-from config import PG_DEFAULT_DRIVER
+from .utils import BaseTableView

 class TableModule(SchemaChildModule):
@@ -83,7 +77,7 @@ class TableModule(SchemaChildModule):
 blueprint = TableModule(__name__)

-class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
+class TableView(BaseTableView, DataTypeReader, VacuumSettings):
     """
     This class is responsible for generating routes for Table node
 
@@ -96,11 +90,6 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
       - This property defines (if javascript) exists for this node.
         Override this property for your own logic
 
-    * check_precondition()
-      - This function will behave as a decorator which will checks
-        database connection before running view, it will also attaches
-        manager,conn & template_path properties to self
-
     * list()
       - This function is used to list all the Table nodes within that
       collection.
@@ -149,10 +138,6 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
       - This function will generate dependent list to show it in dependent
         pane for the selected node.
 
-    * _formatter(data, tid)
-      - It will return formatted output of query result
-        as per client model format
-
     * get_types(self, gid, sid, did, scid)
       - This function will return list of types available for columns node
         via AJAX response
@@ -178,19 +163,6 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
     * get_toast_table_vacuum(gid, sid, did, scid=None, tid=None)
       - Fetch the default values for toast table auto-vacuum
 
-    * _columns_formatter(tid, data):
-      - It will return formatted output of query result
-        as per client model format for column node
-
-    * _index_constraints_formatter(self, did, tid, data):
-      - It will return formatted output of query result
-        as per client model format for index constraint node
-
-    * _cltype_formatter(type): (staticmethod)
-      - We need to remove [] from type and append it
-        after length/precision so we will send flag for
-        sql template
-
     * _parse_format_columns(self, data, mode=None):
        - This function will parse and return formatted list of columns
          added by user
@@ -252,71 +224,16 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         'get_access_methods': [{}, {'get': 'get_access_methods'}],
         'get_oper_class': [{}, {'get': 'get_oper_class'}],
         'get_operator': [{}, {'get': 'get_operator'}],
+        'get_attach_tables': [
+            {'get': 'get_attach_tables'},
+            {'get': 'get_attach_tables'}],
         'select_sql': [{'get': 'select_sql'}],
         'insert_sql': [{'get': 'insert_sql'}],
         'update_sql': [{'get': 'update_sql'}],
         'delete_sql': [{'get': 'delete_sql'}]
-
     })
 
-    def check_precondition(f):
-        """
-        This function will behave as a decorator which will checks
-        database connection before running view, it will also attaches
-        manager,conn & template_path properties to self
-        """
-
-        @wraps(f)
-        def wrap(*args, **kwargs):
-            # Here args[0] will hold self & kwargs will hold gid,sid,did
-            self = args[0]
-            driver = get_driver(PG_DEFAULT_DRIVER)
-            did = kwargs['did']
-            self.manager = driver.connection_manager(kwargs['sid'])
-            self.conn = self.manager.connection(did=kwargs['did'])
-            self.qtIdent = driver.qtIdent
-            self.qtTypeIdent = driver.qtTypeIdent
-            # We need datlastsysoid to check if current table is system table
-            self.datlastsysoid = self.manager.db_info[
-                did
-            ]['datlastsysoid'] if self.manager.db_info is not None and \
-                did in self.manager.db_info else 0
-
-            ver = self.manager.version
-            # Set the template path for the SQL scripts
-            self.template_path = 'table/sql/#{0}#'.format(ver)
-
-            # Template for Column ,check constraint and exclusion constraint node
-            self.column_template_path = 'column/sql/#{0}#'.format(ver)
-            self.check_constraint_template_path = 'check_constraint/sql/#{0}#'.format(ver)
-            self.exclusion_constraint_template_path = 'exclusion_constraint/sql/#{0}#'.format(ver)
-
-            # Template for PK & Unique constraint node
-            self.index_constraint_template_path = 'index_constraint/sql'
-
-            # Template for foreign key constraint node
-            self.foreign_key_template_path = 'foreign_key/sql/#{0}#'.format(ver)
-
-            # Template for index node
-            self.index_template_path = 'index/sql/#{0}#'.format(ver)
-
-            # Template for trigger node
-            self.trigger_template_path = 'trigger/sql/#{0}#'.format(ver)
-
-            # Template for rules node
-            self.rules_template_path = 'rules/sql'
-
-            # Supported ACL for table
-            self.acl = ['a', 'r', 'w', 'd', 'D', 'x', 't']
-
-            # Supported ACL for columns
-            self.column_acl = ['a', 'r', 'w', 'x']
-
-            return f(*args, **kwargs)
-
-        return wrap
-
-    @check_precondition
+    @BaseTableView.check_precondition
     def list(self, gid, sid, did, scid):
         """
         This function is used to list all the table nodes within that collection.
@@ -330,7 +247,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         Returns:
             JSON of available table nodes
         """
-        SQL = render_template("/".join([self.template_path,
+        SQL = render_template("/".join([self.table_template_path,
                                         'properties.sql']),
                               did=did, scid=scid,
                               datlastsysoid=self.datlastsysoid)
@@ -343,7 +260,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
             status=200
         )
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def node(self, gid, sid, did, scid, tid):
         """
         This function is used to list all the table nodes within that collection.
@@ -359,7 +276,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
             JSON of available table nodes
         """
         res = []
-        SQL = render_template("/".join([self.template_path,
+        SQL = render_template("/".join([self.table_template_path,
                                         'nodes.sql']),
                               scid=scid, tid=tid)
         status, rset = self.conn.execute_2darray(SQL)
@@ -372,9 +289,10 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
                 rset['rows'][0]['oid'],
                 scid,
                 rset['rows'][0]['name'],
-                icon="icon-table",
+                icon="icon-partition" if 'is_partitioned' in rset['rows'][0] and rset['rows'][0]['is_partitioned'] else "icon-table",
                 tigger_count=rset['rows'][0]['triggercount'],
-                has_enable_triggers=rset['rows'][0]['has_enable_triggers']
+                has_enable_triggers=rset['rows'][0]['has_enable_triggers'],
+                is_partitioned=rset['rows'][0]['is_partitioned'] if 'is_partitioned' in rset['rows'][0] else False
             )
 
         return make_json_response(
@@ -382,8 +300,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
             status=200
         )
 
-
-    @check_precondition
+    @BaseTableView.check_precondition
     def nodes(self, gid, sid, did, scid):
         """
         This function is used to list all the table nodes within that collection.
@@ -398,7 +315,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
             JSON of available table nodes
         """
         res = []
-        SQL = render_template("/".join([self.template_path,
+        SQL = render_template("/".join([self.table_template_path,
                                         'nodes.sql']),
                               scid=scid)
         status, rset = self.conn.execute_2darray(SQL)
@@ -411,9 +328,10 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
                     row['oid'],
                     scid,
                     row['name'],
-                    icon="icon-table",
+                    icon="icon-partition" if 'is_partitioned' in row and row['is_partitioned'] else "icon-table",
                     tigger_count=row['triggercount'],
-                    has_enable_triggers=row['has_enable_triggers']
+                    has_enable_triggers=row['has_enable_triggers'],
+                    is_partitioned=row['is_partitioned'] if 'is_partitioned' in row else False
                 ))
 
         return make_json_response(
@@ -421,7 +339,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
             status=200
         )
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def get_all_tables(self, gid, sid, did, scid, tid=None):
         """
         Args:
@@ -435,7 +353,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
             Returns the lits of tables required for constraints.
         """
         try:
-            SQL = render_template("/".join([self.template_path,
+            SQL = render_template("/".join([self.table_template_path,
                                             'get_tables_for_constraints.sql']),
                                   show_sysobj=self.blueprint.show_system_objects)
 
@@ -452,7 +370,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         except Exception as e:
             return internal_server_error(errormsg=str(e))
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def get_table_vacuum(self, gid, sid, did, scid=None, tid=None):
         """
         Fetch the default values for table auto-vacuum
@@ -468,7 +386,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
             status=200
         )
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def get_toast_table_vacuum(self, gid, sid, did, scid=None, tid=None):
         """
         Fetch the default values for toast table auto-vacuum
@@ -484,7 +402,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
             status=200
         )
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def get_access_methods(self, gid, sid, did, scid, tid=None):
         """
         This function returns access methods.
@@ -518,7 +436,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
             status=200
         )
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def get_oper_class(self, gid, sid, did, scid, tid=None):
         """
 
@@ -555,7 +473,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         except Exception as e:
             return internal_server_error(errormsg=str(e))
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def get_operator(self, gid, sid, did, scid, tid=None):
         """
 
@@ -593,506 +511,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         except Exception as e:
             return internal_server_error(errormsg=str(e))
 
-    def _columns_formatter(self, tid, data):
-        """
-        Args:
-            tid: Table OID
-            data: dict of query result
-
-        Returns:
-            It will return formatted output of query result
-            as per client model format for column node
-        """
-        for column in data['columns']:
-
-            # We need to format variables according to client js collection
-            if 'attoptions' in column and column['attoptions'] is not None:
-                spcoptions = []
-                for spcoption in column['attoptions']:
-                    k, v = spcoption.split('=')
-                    spcoptions.append({'name': k, 'value': v})
-
-                column['attoptions'] = spcoptions
-
-            # Need to format security labels according to client js collection
-            if 'seclabels' in column and column['seclabels'] is not None:
-                seclabels = []
-                for seclbls in column['seclabels']:
-                    k, v = seclbls.split('=')
-                    seclabels.append({'provider': k, 'label': v})
-
-                column['seclabels'] = seclabels
-
-            if 'attnum' in column and column['attnum'] is not None and \
-                            column['attnum'] > 0:
-                # We need to parse & convert ACL coming from database to json format
-                SQL = render_template("/".join([self.column_template_path, 'acl.sql']),
-                                      tid=tid, clid=column['attnum'])
-                status, acl = self.conn.execute_dict(SQL)
-
-                if not status:
-                    return internal_server_error(errormsg=acl)
-
-                # We will set get privileges from acl sql so we don't need
-                # it from properties sql
-                column['attacl'] = []
-
-                for row in acl['rows']:
-                    priv = parse_priv_from_db(row)
-                    column.setdefault(row['deftype'], []).append(priv)
-
-                # we are receiving request when in edit mode
-                # we will send filtered types related to current type
-                present_type = column['cltype']
-
-                type_id = column['atttypid']
-
-                fulltype = self.get_full_type(
-                    column['typnspname'], column['typname'],
-                    column['isdup'], column['attndims'], column['atttypmod']
-                )
-
-                length = False
-                precision = False
-                if 'elemoid' in column:
-                    length, precision, typeval = self.get_length_precision(column['elemoid'])
-
-                # Set length and precision to None
-                column['attlen'] = None
-                column['attprecision'] = None
-
-                # If we have length & precision both
-                if length and precision:
-                    matchObj = re.search(r'(\d+),(\d+)', fulltype)
-                    if matchObj:
-                        column['attlen'] = matchObj.group(1)
-                        column['attprecision'] = matchObj.group(2)
-                elif length:
-                    # If we have length only
-                    matchObj = re.search(r'(\d+)', fulltype)
-                    if matchObj:
-                        column['attlen'] = matchObj.group(1)
-                        column['attprecision'] = None
-
-
-                SQL = render_template("/".join([self.column_template_path,
-                                                'is_referenced.sql']),
-                                      tid=tid, clid=column['attnum'])
-
-                status, is_reference = self.conn.execute_scalar(SQL)
-
-                edit_types_list = list()
-                # We will need present type in edit mode
-
-                if column['typnspname'] == "pg_catalog" or column['typnspname'] == "public":
-                    edit_types_list.append(present_type)
-                else:
-                    t = self.qtTypeIdent(self.conn, column['typnspname'], present_type)
-                    edit_types_list.append(t)
-                    column['cltype'] = t
-
-                if int(is_reference) == 0:
-                    SQL = render_template("/".join([self.column_template_path,
-                                                    'edit_mode_types.sql']),
-                                          type_id=type_id)
-                    status, rset = self.conn.execute_2darray(SQL)
-
-                    for row in rset['rows']:
-                        edit_types_list.append(row['typname'])
-                else:
-                    edit_types_list.append(present_type)
-
-                column['edit_types'] = edit_types_list
-                column['cltype'] = DataTypeReader.parse_type_name(column['cltype'])
-
-                if 'indkey' in column:
-                    # Current column
-                    attnum = str(column['attnum'])
-
-                    # Single/List of primary key column(s)
-                    indkey = str(column['indkey'])
-
-                    # We will check if column is in primary column(s)
-                    if attnum in indkey.split(" "):
-                        column['is_primary_key'] = True
-                    else:
-                        column['is_primary_key'] = False
-
-        return data
-
-    def _index_constraints_formatter(self, did, tid, data):
-        """
-        Args:
-            tid: Table OID
-            data: dict of query result
-
-        Returns:
-            It will return formatted output of query result
-            as per client model format for index constraint node
-        """
-
-        # We will fetch all the index constraints for the table
-        index_constraints = {
-            'p': 'primary_key', 'u': 'unique_constraint'
-        }
-
-        for ctype in index_constraints.keys():
-            data[index_constraints[ctype]] = []
-
-            sql = render_template("/".join([self.index_constraint_template_path,
-                                            'properties.sql']),
-                                  did=did, tid=tid,
-                                  constraint_type=ctype)
-            status, res = self.conn.execute_dict(sql)
-
-            if not status:
-                return internal_server_error(errormsg=res)
-
-            for row in res['rows']:
-                result = row
-                sql = render_template(
-                    "/".join([self.index_constraint_template_path,
-                              'get_constraint_cols.sql']),
-                    cid=row['oid'],
-                    colcnt=row['indnatts'])
-                status, res = self.conn.execute_dict(sql)
-
-                if not status:
-                    return internal_server_error(errormsg=res)
-
-                columns = []
-                for r in res['rows']:
-                    columns.append({"column": r['column'].strip('"')})
-
-                result['columns'] = columns
-
-                # If not exists then create list and/or append into
-                # existing list [ Adding into main data dict]
-                data.setdefault(index_constraints[ctype], []).append(result)
-
-        return data
-
-    def _foreign_key_formatter(self, tid, data):
-        """
-        Args:
-            tid: Table OID
-            data: dict of query result
-
-        Returns:
-            It will return formatted output of query result
-            as per client model format for foreign key constraint node
-        """
-
-        # We will fetch all the index constraints for the table
-        sql = render_template("/".join([self.foreign_key_template_path,
-                                        'properties.sql']),
-                              tid=tid)
-
-        status, result = self.conn.execute_dict(sql)
-
-        if not status:
-            return internal_server_error(errormsg=result)
-
-        for fk in result['rows']:
-
-            sql = render_template("/".join([self.foreign_key_template_path,
-                                            'get_constraint_cols.sql']),
-                                  tid=tid,
-                                  keys=zip(fk['confkey'], fk['conkey']),
-                                  confrelid=fk['confrelid'])
-
-            status, res = self.conn.execute_dict(sql)
-
-            if not status:
-                return internal_server_error(errormsg=res)
-
-            columns = []
-            cols = []
-            for row in res['rows']:
-                columns.append({"local_column": row['conattname'],
-                                "references": fk['confrelid'],
-                                "referenced": row['confattname']})
-                cols.append(row['conattname'])
-
-            fk['columns'] = columns
-
-            SQL = render_template("/".join([self.foreign_key_template_path,
-                                            'get_parent.sql']),
-                                  tid=fk['columns'][0]['references'])
-
-            status, rset = self.conn.execute_2darray(SQL)
-            if not status:
-                return internal_server_error(errormsg=rset)
-
-            fk['remote_schema'] = rset['rows'][0]['schema']
-            fk['remote_table'] = rset['rows'][0]['table']
-
-            coveringindex = self.search_coveringindex(tid, cols)
-
-            fk['coveringindex'] = coveringindex
-            if coveringindex:
-                fk['autoindex'] = True
-                fk['hasindex'] = True
-            else:
-                fk['autoindex'] = False
-                fk['hasindex'] = False
-            # If not exists then create list and/or append into
-            # existing list [ Adding into main data dict]
-            data.setdefault('foreign_key', []).append(fk)
-
-        return data
-
-    def _check_constraint_formatter(self, tid, data):
-        """
-        Args:
-            tid: Table OID
-            data: dict of query result
-
-        Returns:
-            It will return formatted output of query result
-            as per client model format for check constraint node
-        """
-
-        # We will fetch all the index constraints for the table
-        SQL = render_template("/".join([self.check_constraint_template_path,
-                                        'properties.sql']),
-                              tid=tid)
-
-        status, res = self.conn.execute_dict(SQL)
-
-        if not status:
-            return internal_server_error(errormsg=res)
-        # If not exists then create list and/or append into
-        # existing list [ Adding into main data dict]
-
-        data['check_constraint'] = res['rows']
-
-        return data
-
-    def _exclusion_constraint_formatter(self, did, tid, data):
-        """
-        Args:
-            tid: Table OID
-            data: dict of query result
-
-        Returns:
-            It will return formatted output of query result
-            as per client model format for exclusion constraint node
-        """
-
-        # We will fetch all the index constraints for the table
-        sql = render_template("/".join([self.exclusion_constraint_template_path,
-                                        'properties.sql']),
-                              did=did, tid=tid)
-
-        status, result = self.conn.execute_dict(sql)
-
-        if not status:
-            return internal_server_error(errormsg=result)
-
-        for ex in result['rows']:
-
-            sql = render_template("/".join([self.exclusion_constraint_template_path,
-                                            'get_constraint_cols.sql']),
-                                  cid=ex['oid'],
-                                  colcnt=ex['indnatts'])
-
-            status, res = self.conn.execute_dict(sql)
-
-            if not status:
-                return internal_server_error(errormsg=res)
-
-            columns = []
-            for row in res['rows']:
-                if row['options'] & 1:
-                    order = False
-                    nulls_order = True if (row['options'] & 2) else False
-                else:
-                    order = True
-                    nulls_order = True if (row['options'] & 2) else False
-
-                columns.append({"column": row['coldef'].strip('"'),
-                                "oper_class": row['opcname'],
-                                "order": order,
-                                "nulls_order": nulls_order,
-                                "operator": row['oprname'],
-                                "col_type": row['datatype']
-                                })
-
-            ex['columns'] = columns
-            # If not exists then create list and/or append into
-            # existing list [ Adding into main data dict]
-            data.setdefault('exclude_constraint', []).append(ex)
-
-        return data
-
-    def search_coveringindex(self, tid, cols):
-        """
-
-        Args:
-          tid: Table id
-          cols: column list
-
-        Returns:
-
-        """
-
-        cols = set(cols)
-        SQL = render_template("/".join([self.foreign_key_template_path,
-                                        'get_constraints.sql']),
-                              tid=tid)
-        status, constraints = self.conn.execute_dict(SQL)
-
-        if not status:
-            raise Exception(constraints)
-
-        for costrnt in constraints['rows']:
-
-            sql = render_template(
-                "/".join([self.foreign_key_template_path, 'get_cols.sql']),
-                cid=costrnt['oid'],
-                colcnt=costrnt['indnatts'])
-            status, rest = self.conn.execute_dict(sql)
-
-            if not status:
-                return internal_server_error(errormsg=rest)
-
-            indexcols = set()
-            for r in rest['rows']:
-                indexcols.add(r['column'].strip('"'))
-
-            if len(cols - indexcols) == len(indexcols - cols) == 0:
-                return costrnt["idxname"]
-
-        return None
-
-    def _formatter(self, did, scid, tid, data):
-        """
-        Args:
-            data: dict of query result
-            scid: schema oid
-            tid: table oid
-
-        Returns:
-            It will return formatted output of query result
-            as per client model format
-        """
-        # Need to format security labels according to client js collection
-        if 'seclabels' in data and data['seclabels'] is not None:
-            seclabels = []
-            for seclbls in data['seclabels']:
-                k, v = seclbls.split('=')
-                seclabels.append({'provider': k, 'label': v})
-
-            data['seclabels'] = seclabels
-
-        # We need to parse & convert ACL coming from database to json format
-        SQL = render_template("/".join([self.template_path, 'acl.sql']),
-                              tid=tid, scid=scid)
-        status, acl = self.conn.execute_dict(SQL)
-        if not status:
-            return internal_server_error(errormsg=acl)
-
-        # We will set get privileges from acl sql so we don't need
-        # it from properties sql
-        for row in acl['rows']:
-            priv = parse_priv_from_db(row)
-            if row['deftype'] in data:
-                data[row['deftype']].append(priv)
-            else:
-                data[row['deftype']] = [priv]
-
-        # We will add Auto vacuum defaults with out result for grid
-        data['vacuum_table'] = self.parse_vacuum_data(self.conn, data, 'table')
-        data['vacuum_toast'] = self.parse_vacuum_data(self.conn, data, 'toast')
-
-        # Fetch columns for the table logic
-        #
-        # 1) Check if of_type and inherited tables are present?
-        # 2) If yes then Fetch all the columns for of_type and inherited tables
-        # 3) Add columns in columns collection
-        # 4) Find all the columns for tables and filter out columns which are
-        #   not inherited from any table & format them one by one
-
-        # Get of_type table columns and add it into columns dict
-        if data['typname']:
-            SQL = render_template("/".join([self.template_path,
-                                            'get_columns_for_table.sql']),
-                                  tname=data['typname'])
-
-            status, res = self.conn.execute_dict(SQL)
-            if not status:
-                return internal_server_error(errormsg=res)
-            data['columns'] = res['rows']
-
-        # Get inherited table(s) columns and add it into columns dict
-        elif data['coll_inherits'] and len(data['coll_inherits']) > 0:
-            columns = []
-            # Return all tables which can be inherited & do not show
-            # system columns
-            SQL = render_template("/".join([self.template_path, 'get_inherits.sql']),
-                                  show_system_objects=False
-                                  )
-            status, rset = self.conn.execute_2darray(SQL)
-            if not status:
-                return internal_server_error(errormsg=rset)
-
-            for row in rset['rows']:
-                if row['inherits'] in data['coll_inherits']:
-                    # Fetch columns using inherited table OID
-                    SQL = render_template("/".join([self.template_path,
-                                                    'get_columns_for_table.sql']),
-                                          tid=row['oid'])
-                    status, res = self.conn.execute_dict(SQL)
-                    if not status:
-                        return internal_server_error(errormsg=res)
-                    columns.extend(res['rows'][:])
-            data['columns'] = columns
-
-        # We will fetch all the columns for the table using
-        # columns properties.sql, so we need to set template path
-        SQL = render_template("/".join([self.column_template_path,
-                                        'properties.sql']),
-                              tid=tid,
-                              show_sys_objects=False
-                              )
-
-        status, res = self.conn.execute_dict(SQL)
-        if not status:
-            return internal_server_error(errormsg=res)
-        all_columns = res['rows']
-
-        # Filter inherited columns from all columns
-        if 'columns' in data and len(data['columns']) > 0 \
-                and len(all_columns) > 0:
-            for row in data['columns']:
-                for i, col in enumerate(all_columns):
-                    # If both name are same then remove it
-                    # as it is inherited from other table
-                    if col['name'] == row['name']:
-                        # Remove same column from all_columns as
-                        # already have it columns collection
-                        del all_columns[i]
-
-            # If any column is added then update columns collection
-            if len(all_columns) > 0:
-                data['columns'] += all_columns
-        # If no inherited columns found then add all columns
-        elif len(all_columns) > 0:
-            data['columns'] = all_columns
-
-        if 'columns' in data and len(data['columns']) > 0:
-            data = self._columns_formatter(tid, data)
-
-        # Here we will add constraint in our output
-        data = self._index_constraints_formatter(did, tid, data)
-        data = self._foreign_key_formatter(tid, data)
-        data = self._check_constraint_formatter(tid, data)
-        data = self._exclusion_constraint_formatter(did, tid, data)
-
-        return data
-
-    @check_precondition
+    @BaseTableView.check_precondition
     def properties(self, gid, sid, did, scid, tid):
         """
         This function will show the properties of the selected table node.
@@ -1109,7 +528,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
             JSON of selected table node
         """
 
-        SQL = render_template("/".join([self.template_path,
+        SQL = render_template("/".join([self.table_template_path,
                                         'properties.sql']),
                               did=did, scid=scid, tid=tid,
                               datlastsysoid=self.datlastsysoid)
@@ -1120,41 +539,19 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         if len(res['rows']) == 0:
                 return gone(gettext("The specified table could not be found."))
 
-        data = res['rows'][0]
-
-        data['vacuum_settings_str'] = ""
-
-        if data['table_vacuum_settings_str'] is not None:
-            data['vacuum_settings_str'] += data[
-                'table_vacuum_settings_str'].replace(',', '\n')
-
-        if data['toast_table_vacuum_settings_str'] is not None:
-            data['vacuum_settings_str'] += '\n' + '\n'.join(
-                ['toast_' + setting for setting in data[
-                    'toast_table_vacuum_settings_str'
-                ].split(',')]
-            )
-        data['vacuum_settings_str'] = data[
-            'vacuum_settings_str'
-        ].replace("=", " = ")
-
-        data = self._formatter(did, scid, tid, data)
-
-        return ajax_response(
-            response=data,
-            status=200
-        )
+        return super(TableView, self).properties(
+            gid, sid, did, scid, tid, res)
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def types(self, gid, sid, did, scid, tid=None, clid=None):
         """
         Returns:
             This function will return list of types available for column node
             for node-ajax-control
         """
-        condition = render_template("/".join([self.template_path,
-                                              'get_types_where_condition.sql']),
-                                    show_system_objects=self.blueprint.show_system_objects)
+        condition = render_template("/".join(
+            [self.table_template_path, 'get_types_where_condition.sql']),
+            show_system_objects=self.blueprint.show_system_objects)
 
         status, types = self.get_types(self.conn, condition, True)
 
@@ -1166,7 +563,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
             status=200
         )
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def get_columns(self, gid, sid, did, scid, tid=None):
         """
         Returns the Table Columns.
@@ -1189,13 +586,14 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         data = request.args if request.args else None
         try:
             if data and 'tid' in data:
-                SQL = render_template("/".join([self.template_path,
+                SQL = render_template("/".join([self.table_template_path,
                                                 'get_columns_for_table.sql']),
                                       tid=data['tid'])
             elif data and 'tname' in data:
-                SQL = render_template("/".join([self.template_path,
-                                                'get_columns_for_table.sql']),
-                                      tname=data['tname'])
+                SQL = render_template("/".join(
+                    [self.table_template_path, 'get_columns_for_table.sql']),
+                    tname=data['tname']
+                )
 
             if SQL:
                 status, res = self.conn.execute_dict(SQL)
@@ -1211,7 +609,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         except Exception as e:
             return internal_server_error(errormsg=str(e))
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def get_oftype(self, gid, sid, did, scid, tid=None):
         """
         Returns:
@@ -1220,10 +618,11 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         """
         res = [{'label': '', 'value': ''}]
         try:
-            SQL = render_template("/".join([self.template_path,
-                                            'get_oftype.sql']), scid=scid,
-                                  server_type=self.manager.server_type,
-                                  show_sys_objects=self.blueprint.show_system_objects)
+            SQL = render_template("/".join(
+                [self.table_template_path, 'get_oftype.sql']),
+                scid=scid,
+                server_type=self.manager.server_type,
+                show_sys_objects=self.blueprint.show_system_objects)
             status, rset = self.conn.execute_2darray(SQL)
             if not status:
                 return internal_server_error(errormsg=res)
@@ -1241,7 +640,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         except Exception as e:
             return internal_server_error(errormsg=str(e))
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def get_inherits(self, gid, sid, did, scid, tid=None):
         """
         Returns:
@@ -1250,10 +649,11 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         """
         try:
             res = []
-            SQL = render_template("/".join([self.template_path, 'get_inherits.sql']),
-                                  show_system_objects=self.blueprint.show_system_objects,
-                                  tid=tid,
-                                  server_type=self.manager.server_type)
+            SQL = render_template("/".join(
+                [self.table_template_path, 'get_inherits.sql']),
+                show_system_objects=self.blueprint.show_system_objects,
+                tid=tid,
+                server_type=self.manager.server_type)
             status, rset = self.conn.execute_2darray(SQL)
             if not status:
                 return internal_server_error(errormsg=res)
@@ -1271,25 +671,29 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         except Exception as e:
             return internal_server_error(errormsg=str(e))
 
-    @check_precondition
-    def get_relations(self, gid, sid, did, scid, tid=None):
+    @BaseTableView.check_precondition
+    def get_attach_tables(self, gid, sid, did, scid, tid=None):
         """
         Returns:
-            This function will return list of tables available for like/relation
-            combobox while creating new table
+            This function will return list of tables available to be attached
+            to the partitioned table.
         """
-        res = [{'label': '', 'value': ''}]
         try:
-            SQL = render_template("/".join([self.template_path, 'get_relations.sql']),
-                                  show_sys_objects=self.blueprint.show_system_objects,
-                                  server_type=self.manager.server_type)
+            res = []
+            SQL = render_template("/".join(
+                [self.partition_template_path, 'get_attach_tables.sql']),
+                tid=tid
+            )
+
             status, rset = self.conn.execute_2darray(SQL)
             if not status:
                 return internal_server_error(errormsg=res)
+
             for row in rset['rows']:
                 res.append(
-                    {'label': row['like_relation'], 'value': row['like_relation']}
+                    {'label': row['table_name'], 'value': row['oid']}
                 )
+
             return make_json_response(
                 data=res,
                 status=200
@@ -1298,40 +702,33 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         except Exception as e:
             return internal_server_error(errormsg=str(e))
 
-    @staticmethod
-    def _cltype_formatter(data_type):
+    @BaseTableView.check_precondition
+    def get_relations(self, gid, sid, did, scid, tid=None):
         """
-
-        Args:
-            data_type: Type string
-
         Returns:
-            We need to remove [] from type and append it
-            after length/precision so we will send flag for
-            sql template
-        """
-        if '[]' in data_type:
-            return data_type[:-2], True
-        else:
-            return data_type, False
-
-    @staticmethod
-    def convert_length_precision_to_string(data):
+            This function will return list of tables available for like/relation
+            combobox while creating new table
         """
-        This function is used to convert length & precision to string
-        to handle case like when user gives 0 as length
-
-        Args:
-            data: Data from client
+        res = [{'label': '', 'value': ''}]
+        try:
+            SQL = render_template("/".join([
+                self.table_template_path, 'get_relations.sql']),
+                show_sys_objects=self.blueprint.show_system_objects,
+                server_type=self.manager.server_type)
+            status, rset = self.conn.execute_2darray(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+            for row in rset['rows']:
+                res.append(
+                    {'label': row['like_relation'], 'value': row['like_relation']}
+                )
+            return make_json_response(
+                data=res,
+                status=200
+            )
 
-        Returns:
-            Converted data
-        """
-        if 'attlen' in data and data['attlen'] is not None:
-            data['attlen'] = str(data['attlen'])
-        if 'attprecision' in data and data['attprecision'] is not None:
-            data['attprecision'] = str(data['attprecision'])
-        return data
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
 
     def _parse_format_columns(self, data, mode=None):
         """
@@ -1360,12 +757,12 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
                             # check type for '[]' in it
                             c['cltype'], c['hasSqrBracket'] = self._cltype_formatter(c['cltype'])
 
-                        c = self.convert_length_precision_to_string(c)
+                        c = TableView.convert_length_precision_to_string(c)
 
                     data['columns'][action] = final_columns
         else:
-            # We need to exclude all the columns which are inherited from other tables
-            # 'CREATE' mode
+            # We need to exclude all the columns which are inherited from other
+            # tables 'CREATE' mode
             final_columns = []
 
             for c in columns:
@@ -1382,35 +779,13 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
                     # check type for '[]' in it
                     c['cltype'], c['hasSqrBracket'] = self._cltype_formatter(c['cltype'])
 
-                c = self.convert_length_precision_to_string(c)
+                c = TableView.convert_length_precision_to_string(c)
 
             data['columns'] = final_columns
 
         return data
 
-    def check_and_convert_name_to_string(self, data):
-        """
-        This function will check and covert table to string incase
-        it is numeric
-
-        Args:
-            data: data dict
-
-        Returns:
-            Updated data dict
-        """
-        # For Python2, it can be int, long, float
-        if hasattr(str, 'decode'):
-            if isinstance(data['name'], (int, long, float)):
-                data['name'] = str(data['name'])
-        else:
-            # For Python3, it can be int, float
-            if isinstance(data['name'], (int, float)):
-                data['name'] = str(data['name'])
-        return data
-
-
-    @check_precondition
+    @BaseTableView.check_precondition
     def create(self, gid, sid, did, scid):
         """
         This function will creates new the table object
@@ -1451,7 +826,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
 
         # Parse & format columns
         data = self._parse_format_columns(data)
-        data = self.check_and_convert_name_to_string(data)
+        data = TableView.check_and_convert_name_to_string(data)
 
         # 'coll_inherits' is Array but it comes as string from browser
         # We will convert it again to list
@@ -1474,9 +849,22 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
                 c['remote_table'] = rset['rows'][0]['table']
 
         try:
-            SQL = render_template("/".join([self.template_path,
+            partitions_sql = ''
+            partitioned = False
+            if 'is_partitioned' in data and data['is_partitioned']:
+                data['relkind'] = 'p'
+                # create partition scheme
+                data['partition_scheme'] = self.get_partition_scheme(data)
+                partitions_sql = self.get_partitions_sql(data)
+                partitioned = True
+
+            SQL = render_template("/".join([self.table_template_path,
                                             'create.sql']),
                                   data=data, conn=self.conn)
+
+            # Append SQL for partitions
+            SQL += '\n' + partitions_sql
+
             status, res = self.conn.execute_scalar(SQL)
             if not status:
                 return internal_server_error(errormsg=res)
@@ -1490,7 +878,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
                 data['name'] = data['name'][0:CONST_MAX_CHAR_COUNT]
 
             # Get updated schema oid
-            SQL = render_template("/".join([self.template_path,
+            SQL = render_template("/".join([self.table_template_path,
                                   'get_schema_oid.sql']), tname=data['name'])
 
             status, scid = self.conn.execute_scalar(SQL)
@@ -1498,7 +886,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
                 return internal_server_error(errormsg=scid)
 
             # we need oid to to add object in tree at browser
-            SQL = render_template("/".join([self.template_path,
+            SQL = render_template("/".join([self.table_template_path,
                                   'get_oid.sql']), scid=scid, data=data)
             status, tid = self.conn.execute_scalar(SQL)
             if not status:
@@ -1509,13 +897,14 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
                     tid,
                     scid,
                     data['name'],
-                    icon="icon-table"
+                    icon="icon-partition" if partitioned else "icon-table",
+                    is_partitioned=partitioned
                 )
             )
         except Exception as e:
             return internal_server_error(errormsg=str(e))
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def update(self, gid, sid, did, scid, tid):
         """
         This function will update an existing table object
@@ -1538,34 +927,20 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
                 data[k] = v
 
         try:
-            SQL, name = self.get_sql(did, scid, tid, data)
-
-            SQL = SQL.strip('\n').strip(' ')
-            status, res = self.conn.execute_scalar(SQL)
-            if not status:
-                return internal_server_error(errormsg=res)
-
-            SQL = render_template("/".join([self.template_path,
-                                  'get_schema_oid.sql']), tid=tid)
-            status, res = self.conn.execute_2darray(SQL)
+            SQL = render_template("/".join([self.table_template_path,
+                                            'properties.sql']),
+                                  did=did, scid=scid, tid=tid,
+                                  datlastsysoid=self.datlastsysoid)
+            status, res = self.conn.execute_dict(SQL)
             if not status:
                 return internal_server_error(errormsg=res)
 
-            # new schema id
-            scid = res['rows'][0]['scid']
-
-            return jsonify(
-                node=self.blueprint.generate_browser_node(
-                    tid,
-                    scid,
-                    name,
-                    icon="icon-%s" % self.node_type
-                )
-            )
+            return super(TableView, self).update(
+                gid, sid, did, scid, tid, data, res)
         except Exception as e:
             return internal_server_error(errormsg=str(e))
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def delete(self, gid, sid, did, scid, tid):
         """
         This function will deletes the table object
@@ -1585,7 +960,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
             cascade = False
 
         try:
-            SQL = render_template("/".join([self.template_path,
+            SQL = render_template("/".join([self.table_template_path,
                                             'properties.sql']),
                                   did=did, scid=scid, tid=tid,
                                   datlastsysoid=self.datlastsysoid)
@@ -1606,7 +981,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
 
             data = res['rows'][0]
 
-            SQL = render_template("/".join([self.template_path,
+            SQL = render_template("/".join([self.table_template_path,
                                             'delete.sql']),
                                   data=data, cascade=cascade,
                                   conn=self.conn)
@@ -1626,7 +1001,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         except Exception as e:
             return internal_server_error(errormsg=str(e))
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def truncate(self, gid, sid, did, scid, tid):
         """
         This function will truncate the table object
@@ -1638,43 +1013,22 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
            scid: Schema ID
            tid: Table ID
         """
-        # Below will decide if it's simple drop or drop with cascade call
-        data = request.form if request.form else json.loads(
-            request.data, encoding='utf-8'
-        )
-        # Convert str 'true' to boolean type
-        is_cascade = json.loads(data['cascade'])
 
         try:
-            SQL = render_template("/".join([self.template_path,
+            SQL = render_template("/".join([self.table_template_path,
                                             'properties.sql']),
                                   did=did, scid=scid, tid=tid,
                                   datlastsysoid=self.datlastsysoid)
             status, res = self.conn.execute_dict(SQL)
             if not status:
                 return internal_server_error(errormsg=res)
-            data = res['rows'][0]
 
-            SQL = render_template("/".join([self.template_path,
-                                            'truncate.sql']),
-                                  data=data, cascade=is_cascade)
-            status, res = self.conn.execute_scalar(SQL)
-            if not status:
-                return internal_server_error(errormsg=res)
-
-            return make_json_response(
-                success=1,
-                info=gettext("Table truncated"),
-                data={
-                    'id': tid,
-                    'scid': scid
-                }
-            )
+            return super(TableView, self).truncate(gid, sid, did, scid, tid, res)
 
         except Exception as e:
             return internal_server_error(errormsg=str(e))
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def enable_disable_triggers(self, gid, sid, did, scid, tid):
         """
         This function will enable/disable trigger(s) on the table object
@@ -1694,7 +1048,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         is_enable = json.loads(data['enable'])
 
         try:
-            SQL = render_template("/".join([self.template_path,
+            SQL = render_template("/".join([self.table_template_path,
                                             'properties.sql']),
                                   did=did, scid=scid, tid=tid,
                                   datlastsysoid=self.datlastsysoid)
@@ -1703,7 +1057,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
                 return internal_server_error(errormsg=res)
             data = res['rows'][0]
 
-            SQL = render_template("/".join([self.template_path,
+            SQL = render_template("/".join([self.table_template_path,
                                             'enable_disable_trigger.sql']),
                                   data=data, is_enable_trigger=is_enable)
             status, res = self.conn.execute_scalar(SQL)
@@ -1723,7 +1077,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         except Exception as e:
             return internal_server_error(errormsg=str(e))
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def reset(self, gid, sid, did, scid, tid):
         """
         This function will reset statistics of table
@@ -1735,27 +1089,9 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
            scid: Schema ID
            tid: Table ID
         """
-        try:
-            SQL = render_template("/".join([self.template_path,
-                                            'reset_stats.sql']),
-                                  tid=tid)
-            status, res = self.conn.execute_scalar(SQL)
-            if not status:
-                return internal_server_error(errormsg=res)
+        return BaseTableView.reset_statistics(self, scid, tid)
 
-            return make_json_response(
-                success=1,
-                info=gettext("Table statistics have been reset"),
-                data={
-                    'id': tid,
-                    'scid': scid
-                }
-            )
-
-        except Exception as e:
-            return internal_server_error(errormsg=str(e))
-
-    @check_precondition
+    @BaseTableView.check_precondition
     def msql(self, gid, sid, did, scid, tid=None):
         """
         This function will create modified sql for table object
@@ -1768,427 +1104,15 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
            tid: Table ID
         """
         data = dict()
+        res = None
         for k, v in request.args.items():
             try:
                 data[k] = json.loads(v, encoding='utf-8')
             except (ValueError, TypeError, KeyError):
                 data[k] = v
 
-        SQL, name = self.get_sql(did, scid, tid, data)
-        SQL = re.sub('\n{2,}', '\n\n', SQL)
-        SQL = SQL.strip('\n')
-        if SQL == '':
-            SQL = "--modified SQL"
-        return make_json_response(
-            data=SQL,
-            status=200
-        )
-
-    def get_index_constraint_sql(self, did, tid, data):
-        """
-         Args:
-           tid: Table ID
-           data: data dict coming from the client
-
-        Returns:
-        This function will generate modified sql for index constraints
-        (Primary Key & Unique)
-        """
-        sql = []
-        # We will fetch all the index constraints for the table
-        index_constraints = {
-            'p': 'primary_key', 'u': 'unique_constraint'
-        }
-
-        for ctype in index_constraints.keys():
-            # Check if constraint is in data
-            # If yes then we need to check for add/change/delete
-            if index_constraints[ctype] in data:
-                constraint = data[index_constraints[ctype]]
-                # If constraint(s) is/are deleted
-                if 'deleted' in constraint:
-                    for c in constraint['deleted']:
-                        c['schema'] = data['schema']
-                        c['table'] = data['name']
-
-                        # Sql for drop
-                        sql.append(
-                            render_template("/".join(
-                                [self.index_constraint_template_path,
-                                 'delete.sql']),
-                                data=c, conn=self.conn).strip('\n')
-                        )
-
-                if 'changed' in constraint:
-                    for c in constraint['changed']:
-                        c['schema'] = data['schema']
-                        c['table'] = data['name']
-
-                        properties_sql = render_template("/".join(
-                            [self.index_constraint_template_path, 'properties.sql']),
-                            did=did, tid=tid, cid=c['oid'], constraint_type=ctype)
-                        status, res = self.conn.execute_dict(properties_sql)
-                        if not status:
-                            return internal_server_error(errormsg=res)
-
-                        old_data = res['rows'][0]
-                        # Sql to update object
-                        sql.append(
-                            render_template("/".join([
-                                self.index_constraint_template_path,
-                                'update.sql']), data=c, o_data=old_data,
-                                conn=self.conn).strip('\n')
-                        )
-
-                if 'added' in constraint:
-                    for c in constraint['added']:
-                        c['schema'] = data['schema']
-                        c['table'] = data['name']
-
-                        # Sql to add object
-                        if self.validate_constrains(index_constraints[ctype], c):
-                            sql.append(
-                                render_template(
-                                    "/".join([self.index_constraint_template_path,
-                                              'create.sql']),
-                                    data=c, conn=self.conn,
-                                    constraint_name='PRIMARY KEY'
-                                    if ctype == 'p' else 'UNIQUE'
-                                ).strip('\n')
-                            )
-                        else:
-                            sql.append(
-                                gettext(
-                                    '-- definition incomplete for {0} constraint'.format(index_constraints[ctype])
-                                )
-                            )
-        if len(sql) > 0:
-            # Join all the sql(s) as single string
-            return '\n\n'.join(sql)
-        else:
-            return None
-
-    def get_foreign_key_sql(self, tid, data):
-        """
-         Args:
-           tid: Table ID
-           data: data dict coming from the client
-
-        Returns:
-        This function will generate modified sql for foreign key
-        """
-        sql = []
-        # Check if constraint is in data
-        # If yes then we need to check for add/change/delete
-        if 'foreign_key' in data:
-            constraint = data['foreign_key']
-            # If constraint(s) is/are deleted
-            if 'deleted' in constraint:
-                for c in constraint['deleted']:
-                    c['schema'] = data['schema']
-                    c['table'] = data['name']
-
-                    # Sql for drop
-                    sql.append(
-                        render_template("/".join(
-                            [self.foreign_key_template_path,
-                             'delete.sql']),
-                            data=c, conn=self.conn).strip('\n')
-                    )
-
-            if 'changed' in constraint:
-                for c in constraint['changed']:
-                    c['schema'] = data['schema']
-                    c['table'] = data['name']
-
-                    properties_sql = render_template("/".join(
-                        [self.foreign_key_template_path, 'properties.sql']),
-                        tid=tid, cid=c['oid'])
-                    status, res = self.conn.execute_dict(properties_sql)
-                    if not status:
-                        return internal_server_error(errormsg=res)
-
-                    old_data = res['rows'][0]
-                    # Sql to update object
-                    sql.append(
-                        render_template("/".join([
-                            self.foreign_key_template_path,
-                            'update.sql']), data=c, o_data=old_data,
-                            conn=self.conn).strip('\n')
-                    )
-
-                    if not self.validate_constrains('foreign_key', c):
-                        sql.append(
-                            gettext(
-                                '-- definition incomplete for foreign_key constraint'
-                            )
-                        )
-                        return '\n\n'.join(sql)
-
-                    if 'columns' in c:
-                        cols = []
-                        for col in c['columns']:
-                            cols.append(col['local_column'])
-
-                        coveringindex = self.search_coveringindex(tid, cols)
-
-                        if coveringindex is None and 'autoindex' in c and c['autoindex'] and \
-                                ('coveringindex' in c and
-                                         c['coveringindex'] != ''):
-                            sql.append(render_template(
-                                "/".join([self.foreign_key_template_path, 'create_index.sql']),
-                                data=c, conn=self.conn).strip('\n')
-                                       )
-
-            if 'added' in constraint:
-                for c in constraint['added']:
-                    c['schema'] = data['schema']
-                    c['table'] = data['name']
-
-                    # Sql to add object
-                    # Columns
-
-                    if not self.validate_constrains('foreign_key', c):
-                        sql.append(
-                            gettext(
-                                '-- definition incomplete for foreign_key constraint'
-                            )
-                        )
-                        return '\n\n'.join(sql)
-
-                    SQL = render_template("/".join([self.foreign_key_template_path,
-                                                    'get_parent.sql']),
-                                          tid=c['columns'][0]['references'])
-                    status, rset = self.conn.execute_2darray(SQL)
-                    if not status:
-                        return internal_server_error(errormsg=rset)
-
-                    c['remote_schema'] = rset['rows'][0]['schema']
-                    c['remote_table'] = rset['rows'][0]['table']
-
-                    sql.append(
-                        render_template(
-                            "/".join([self.foreign_key_template_path,
-                                      'create.sql']),
-                            data=c, conn=self.conn
-                        ).strip('\n')
-                    )
-
-                    if c['autoindex']:
-                        sql.append(
-                            render_template(
-                                "/".join([self.foreign_key_template_path,
-                                          'create_index.sql']),
-                                data=c, conn=self.conn).strip('\n')
-                        )
-
-        if len(sql) > 0:
-            # Join all the sql(s) as single string
-            return '\n\n'.join(sql)
-        else:
-            return None
-
-    def get_check_constraint_sql(self, tid, data):
-        """
-         Args:
-           tid: Table ID
-           data: data dict coming from the client
-
-        Returns:
-          This function will generate modified sql for check constraint
-        """
-        sql = []
-        # Check if constraint is in data
-        # If yes then we need to check for add/change/delete
-        if 'check_constraint' in data:
-            constraint = data['check_constraint']
-            # If constraint(s) is/are deleted
-            if 'deleted' in constraint:
-                for c in constraint['deleted']:
-                    c['schema'] = data['schema']
-                    c['table'] = data['name']
-
-                    # Sql for drop
-                    sql.append(
-                        render_template("/".join(
-                            [self.check_constraint_template_path,
-                             'delete.sql']),
-                            data=c, conn=self.conn).strip('\n')
-                    )
-
-            if 'changed' in constraint:
-                for c in constraint['changed']:
-                    c['schema'] = data['schema']
-                    c['table'] = data['name']
-
-                    properties_sql = render_template("/".join(
-                        [self.check_constraint_template_path, 'properties.sql']),
-                        tid=tid, cid=c['oid'])
-                    status, res = self.conn.execute_dict(properties_sql)
-                    if not status:
-                        return internal_server_error(errormsg=res)
-
-                    old_data = res['rows'][0]
-                    # Sql to update object
-                    sql.append(
-                        render_template("/".join([
-                            self.check_constraint_template_path,
-                            'update.sql']), data=c, o_data=old_data,
-                            conn=self.conn).strip('\n')
-                    )
-
-            if 'added' in constraint:
-                for c in constraint['added']:
-                    c['schema'] = data['schema']
-                    c['table'] = data['name']
-
-                    if not self.validate_constrains('check_constraint', c):
-                        sql.append(
-                            gettext(
-                                '-- definition incomplete for check_constraint'
-                            )
-                        )
-                        return '\n\n'.join(sql)
-
-                    sql.append(
-                        render_template(
-                            "/".join([self.check_constraint_template_path,
-                                      'create.sql']),
-                            data=c, conn=self.conn
-                        ).strip('\n')
-                    )
-
-        if len(sql) > 0:
-            # Join all the sql(s) as single string
-            return '\n\n'.join(sql)
-        else:
-            return None
-
-    def get_exclusion_constraint_sql(self, did, tid, data):
-        """
-         Args:
-           tid: Table ID
-           data: data dict coming from the client
-
-        Returns:
-          This function will generate modified sql for exclusion constraint
-        """
-        sql = []
-        # Check if constraint is in data
-        # If yes then we need to check for add/change/delete
-        if 'exclude_constraint' in data:
-            constraint = data['exclude_constraint']
-            # If constraint(s) is/are deleted
-            if 'deleted' in constraint:
-                for c in constraint['deleted']:
-                    c['schema'] = data['schema']
-                    c['table'] = data['name']
-
-                    # Sql for drop
-                    sql.append(
-                        render_template("/".join(
-                            [self.exclusion_constraint_template_path,
-                             'delete.sql']),
-                            data=c, conn=self.conn).strip('\n')
-                    )
-
-            if 'changed' in constraint:
-                for c in constraint['changed']:
-                    c['schema'] = data['schema']
-                    c['table'] = data['name']
-
-                    properties_sql = render_template("/".join(
-                        [self.exclusion_constraint_template_path, 'properties.sql']),
-                        did=did, tid=tid, cid=c['oid'])
-                    status, res = self.conn.execute_dict(properties_sql)
-                    if not status:
-                        return internal_server_error(errormsg=res)
-
-                    old_data = res['rows'][0]
-                    # Sql to update object
-                    sql.append(
-                        render_template("/".join([
-                            self.exclusion_constraint_template_path,
-                            'update.sql']), data=c, o_data=old_data,
-                            conn=self.conn).strip('\n')
-                    )
-
-            if 'added' in constraint:
-                for c in constraint['added']:
-                    c['schema'] = data['schema']
-                    c['table'] = data['name']
-
-                    if not self.validate_constrains('exclude_constraint', c):
-                        sql.append(
-                            gettext(
-                                '-- definition incomplete for exclusion_constraint'
-                            )
-                        )
-                        return '\n\n'.join(sql)
-
-                    sql.append(
-                        render_template(
-                            "/".join([self.exclusion_constraint_template_path,
-                                      'create.sql']),
-                            data=c, conn=self.conn
-                        ).strip('\n')
-                    )
-
-        if len(sql) > 0:
-            # Join all the sql(s) as single string
-            return u'\n\n'.join(sql)
-        else:
-            return None
-
-    def get_trigger_function_schema(self, data):
-        """
-        This function will return trigger function with schema name
-        """
-        # If language is 'edbspl' then trigger function should be
-        # 'Inline EDB-SPL' else we will find the trigger function
-        # with schema name.
-        if data['lanname'] == 'edbspl':
-            data['tfunction'] = 'Inline EDB-SPL'
-        else:
-            SQL = render_template(
-                "/".join(
-                    [self.trigger_template_path,'get_triggerfunctions.sql']
-                ),
-                tgfoid=data['tgfoid'],
-                show_system_objects=self.blueprint.show_system_objects
-            )
-
-            status, result = self.conn.execute_dict(SQL)
-            if not status:
-                return internal_server_error(errormsg=res)
-
-            # Update the trigger function which we have fetched with
-            # schema name
-            if 'rows' in result and len(result['rows']) > 0 and \
-                            'tfunctions' in result['rows'][0]:
-                data['tfunction'] = result['rows'][0]['tfunctions']
-        return data
-
-    def _format_args(self, args):
-        """
-        This function will format arguments.
-
-        Args:
-            args: Arguments
-
-        Returns:
-            Formated arguments for function
-        """
-        formatted_args = ["'{0}'".format(arg) for arg in args]
-        return ', '.join(formatted_args)
-
-    def get_sql(self, did, scid, tid, data):
-        """
-        This function will generate create/update sql from model data
-        coming from client
-        """
         if tid is not None:
-            SQL = render_template("/".join([self.template_path,
+            SQL = render_template("/".join([self.table_template_path,
                                             'properties.sql']),
                                   did=did, scid=scid, tid=tid,
                                   datlastsysoid=self.datlastsysoid)
@@ -2196,258 +1120,17 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
             if not status:
                 return internal_server_error(errormsg=res)
 
-            old_data = res['rows'][0]
-            old_data = self._formatter(did, scid, tid, old_data)
-
-            # We will convert privileges coming from client required
-            if 'relacl' in data:
-                for mode in ['added', 'changed', 'deleted']:
-                    if mode in data['relacl']:
-                        data['relacl'][mode] = parse_priv_to_db(
-                            data['relacl'][mode], self.acl
-                        )
-
-            # If name is not present in request data
-            if 'name' not in data:
-                data['name'] = old_data['name']
-
-            data = self.check_and_convert_name_to_string(data)
-
-            # If name if not present
-            if 'schema' not in data:
-                data['schema'] = old_data['schema']
-
-            # Filter out new tables from list, we will send complete list
-            # and not newly added tables in the list from client
-            # so we will filter new tables here
-            if 'coll_inherits' in data:
-                p_len = len(old_data['coll_inherits'])
-                c_len = len(data['coll_inherits'])
-                # If table(s) added
-                if c_len > p_len:
-                    data['coll_inherits_added'] = list(
-                        set(data['coll_inherits']) - set(old_data['coll_inherits'])
-                    )
-                # If table(s)removed
-                elif c_len < p_len:
-                    data['coll_inherits_removed'] = list(
-                        set(old_data['coll_inherits']) - set(data['coll_inherits'])
-                    )
-                # Safe side verification,In case it happens..
-                # If user removes and adds same number of table
-                # eg removed one table and added one new table
-                elif c_len == p_len:
-                    data['coll_inherits_added'] = list(
-                        set(data['coll_inherits']) - set(old_data['coll_inherits'])
-                    )
-                    data['coll_inherits_removed'] = list(
-                        set(old_data['coll_inherits']) - set(data['coll_inherits'])
-                    )
-
-            SQL = render_template("/".join([self.template_path, 'update.sql']),
-                                  o_data=old_data, data=data, conn=self.conn)
-            # Removes training new lines
-            SQL = SQL.strip('\n') + '\n\n'
-
-            # Parse/Format columns & create sql
-            if 'columns' in data:
-                # Parse the data coming from client
-                data = self._parse_format_columns(data, mode='edit')
-
-                columns = data['columns']
-                column_sql = '\n'
-
-                # If column(s) is/are deleted
-                if 'deleted' in columns:
-                    for c in columns['deleted']:
-                        c['schema'] = data['schema']
-                        c['table'] = data['name']
-                        # Sql for drop column
-                        if 'inheritedfrom' not in c:
-                            column_sql += render_template("/".join(
-                                [self.column_template_path, 'delete.sql']),
-                                data=c, conn=self.conn).strip('\n') + '\n\n'
-
-                # If column(s) is/are changed
-                # Here we will be needing previous properties of column
-                # so that we can compare & update it
-                if 'changed' in columns:
-                    for c in columns['changed']:
-                        c['schema'] = data['schema']
-                        c['table'] = data['name']
-                        if 'attacl' in c:
-                            c['attacl'] = parse_priv_to_db(c['attacl'],
-                                                           self.column_acl)
-
-                        properties_sql = render_template("/".join([self.column_template_path,
-                                                                   'properties.sql']),
-                                                         tid=tid,
-                                                         clid=c['attnum'],
-                                                         show_sys_objects=self.blueprint.show_system_objects
-                                                         )
-
-                        status, res = self.conn.execute_dict(properties_sql)
-                        if not status:
-                            return internal_server_error(errormsg=res)
-                        old_data = res['rows'][0]
-
-                        old_data['cltype'], old_data['hasSqrBracket'] = self._cltype_formatter(old_data['cltype'])
-                        old_data = self.convert_length_precision_to_string(old_data)
-
-                        fulltype = self.get_full_type(
-                            old_data['typnspname'], old_data['typname'],
-                            old_data['isdup'], old_data['attndims'], old_data['atttypmod']
-                        )
-
-                        # If we have length & precision both
-                        matchObj = re.search(r'(\d+),(\d+)', fulltype)
-                        if matchObj:
-                            old_data['attlen'] = int(matchObj.group(1))
-                            old_data['attprecision'] = int(matchObj.group(2))
-                        else:
-                            # If we have length only
-                            matchObj = re.search(r'(\d+)', fulltype)
-                            if matchObj:
-                                old_data['attlen'] = int(matchObj.group(1))
-                                old_data['attprecision'] = None
-                            else:
-                                old_data['attlen'] = None
-                                old_data['attprecision'] = None
-
-                        old_data['cltype'] = DataTypeReader.parse_type_name(old_data['cltype'])
-
-                        # Sql for alter column
-                        if 'inheritedfrom' not in c:
-                            column_sql += render_template("/".join(
-                                [self.column_template_path, 'update.sql']),
-                                data=c, o_data=old_data, conn=self.conn).strip('\n') + '\n\n'
-
-                # If column(s) is/are added
-                if 'added' in columns:
-                    for c in columns['added']:
-                        c['schema'] = data['schema']
-                        c['table'] = data['name']
-                        # Sql for create column
-                        if 'attacl' in c:
-                            c['attacl'] = parse_priv_to_db(c['attacl'],
-                                                           self.column_acl)
-
-                        c = self.convert_length_precision_to_string(c)
-
-                        if 'inheritedfrom' not in c:
-                            column_sql += render_template("/".join(
-                                [self.column_template_path, 'create.sql']),
-                                data=c, conn=self.conn).strip('\n') + '\n\n'
-
-                # Combine all the SQL together
-                SQL += column_sql.strip('\n')
-
-            # Check if index constraints are added/changed/deleted
-            index_constraint_sql = self.get_index_constraint_sql(did, tid, data)
-            # If we have index constraint sql then ad it in main sql
-            if index_constraint_sql is not None:
-                SQL += '\n' + index_constraint_sql
-
-            # Check if foreign key(s) is/are added/changed/deleted
-            foreign_key_sql = self.get_foreign_key_sql(tid, data)
-            # If we have foreign key sql then ad it in main sql
-            if foreign_key_sql is not None:
-                SQL += '\n' + foreign_key_sql
-
-            # Check if check constraint(s) is/are added/changed/deleted
-            check_constraint_sql = self.get_check_constraint_sql(tid, data)
-            # If we have check constraint sql then ad it in main sql
-            if check_constraint_sql is not None:
-                SQL += '\n' + check_constraint_sql
-
-            # Check if exclusion constraint(s) is/are added/changed/deleted
-            exclusion_constraint_sql = self.get_exclusion_constraint_sql(did, tid, data)
-            # If we have check constraint sql then ad it in main sql
-            if exclusion_constraint_sql is not None:
-                SQL += '\n' + exclusion_constraint_sql
-
-        else:
-            required_args = [
-                'name'
-            ]
-
-            for arg in required_args:
-                if arg not in data:
-                    return gettext('-- definition incomplete')
-
-            # validate constraint data.
-            for key in ['primary_key', 'unique_constraint',
-                        'foreign_key', 'check_constraint',
-                        'exclude_constraint']:
-                if key in data and len(data[key]) > 0:
-                    for constraint in data[key]:
-                        if not self.validate_constrains(key, constraint):
-                            return gettext('-- definition incomplete for {0}'.format(key))
-
-            # We will convert privileges coming from client required
-            # in server side format
-            if 'relacl' in data:
-                data['relacl'] = parse_priv_to_db(data['relacl'], self.acl)
-
-            # Parse & format columns
-            data = self._parse_format_columns(data)
-            data = self.check_and_convert_name_to_string(data)
-
-            if 'foreign_key' in data:
-                for c in data['foreign_key']:
-                    SQL = render_template("/".join([self.foreign_key_template_path,
-                                                    'get_parent.sql']),
-                                          tid=c['columns'][0]['references'])
-                    status, rset = self.conn.execute_2darray(SQL)
-                    if not status:
-                        return internal_server_error(errormsg=rset)
-
-                    c['remote_schema'] = rset['rows'][0]['schema']
-                    c['remote_table'] = rset['rows'][0]['table']
-
-            # If the request for new object which do not have did
-            SQL = render_template("/".join([self.template_path, 'create.sql']),
-                                  data=data, conn=self.conn)
+        SQL, name = self.get_sql(did, scid, tid, data, res)
         SQL = re.sub('\n{2,}', '\n\n', SQL)
         SQL = SQL.strip('\n')
+        if SQL == '':
+            SQL = "--modified SQL"
+        return make_json_response(
+            data=SQL,
+            status=200
+        )
 
-        return SQL, data['name'] if 'name' in data else old_data['name']
-
-    @staticmethod
-    def validate_constrains(key, data):
-
-        if key == 'primary_key' or key == 'unique_constraint':
-            if 'columns' in data and len(data['columns']) > 0:
-                return True
-            else:
-                return False
-        elif key == 'foreign_key':
-            if 'oid' not in data:
-                for arg in ['columns']:
-                    if arg not in data:
-                        return False
-                    elif isinstance(data[arg], list) and len(data[arg]) < 1:
-                        return False
-
-                if 'autoindex' in data and data['autoindex'] and \
-                        ('coveringindex' not in data or
-                                                  data['coveringindex'] == ''):
-                    return False
-
-            return True
-
-        elif key == 'check_constraint':
-            for arg in ['consrc']:
-                if arg not in data or data[arg] == '':
-                    return False
-            return True
-
-        elif key == 'exclude_constraint':
-            pass
-
-        return True
-
-    @check_precondition
+    @BaseTableView.check_precondition
     def dependents(self, gid, sid, did, scid, tid):
         """
         This function get the dependents and return ajax response
@@ -2460,43 +1143,9 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
             scid: Schema ID
             tid: Table ID
         """
-        # Specific condition for column which we need to append
-        where = "WHERE dep.refobjid={0}::OID".format(tid)
+        return BaseTableView.get_table_dependents(self, tid)
 
-        dependents_result = self.get_dependents(
-            self.conn, tid
-        )
-
-        # Specific sql to run againt column to fetch dependents
-        SQL = render_template("/".join([self.template_path,
-                                        'depend.sql']), where=where)
-
-        status, res = self.conn.execute_dict(SQL)
-        if not status:
-            return internal_server_error(errormsg=res)
-
-        for row in res['rows']:
-            ref_name = row['refname']
-            if ref_name is None:
-                continue
-
-            dep_type = ''
-            dep_str = row['deptype']
-            if dep_str == 'a':
-                dep_type = 'auto'
-            elif dep_str == 'n':
-                dep_type = 'normal'
-            elif dep_str == 'i':
-                dep_type = 'internal'
-
-            dependents_result.append({'type': 'sequence', 'name': ref_name, 'field': dep_type})
-
-        return ajax_response(
-            response=dependents_result,
-            status=200
-        )
-
-    @check_precondition
+    @BaseTableView.check_precondition
     def dependencies(self, gid, sid, did, scid, tid):
         """
         This function get the dependencies and return ajax response
@@ -2508,18 +1157,10 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
             did: Database ID
             scid: Schema ID
             tid: Table ID
-
         """
-        dependencies_result = self.get_dependencies(
-            self.conn, tid
-        )
-
-        return ajax_response(
-            response=dependencies_result,
-            status=200
-        )
+        return BaseTableView.get_table_dependencies(self, tid)
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def sql(self, gid, sid, did, scid, tid):
         """
         This function will creates reverse engineered sql for
@@ -2534,12 +1175,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         """
         main_sql = []
 
-        """
-        #####################################
-        # 1) Reverse engineered sql for TABLE
-        #####################################
-        """
-        SQL = render_template("/".join([self.template_path,
+        SQL = render_template("/".join([self.table_template_path,
                                         'properties.sql']),
                               did=did, scid=scid, tid=tid,
                               datlastsysoid=self.datlastsysoid)
@@ -2552,250 +1188,10 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
 
         data = res['rows'][0]
 
-        # Table & Schema declaration so that we can use them in child nodes
-        schema = data['schema']
-        table = data['name']
-
-        data = self._formatter(did, scid, tid, data)
-
-        # Now we have all lis of columns which we need
-        # to include in our create definition, Let's format them
-        if 'columns' in data:
-            for c in data['columns']:
-                if 'attacl' in c:
-                    c['attacl'] = parse_priv_to_db(c['attacl'], self.column_acl)
-
-                # check type for '[]' in it
-                if 'cltype' in c:
-                    c['cltype'], c['hasSqrBracket'] = self._cltype_formatter(c['cltype'])
-
-        sql_header = u"-- Table: {0}\n\n-- ".format(self.qtIdent(self.conn,
-                                                                data['schema'],
-                                                                data['name']))
-
-        sql_header += render_template("/".join([self.template_path,
-                                                'delete.sql']),
-                                      data=data, conn=self.conn)
-
-        sql_header = sql_header.strip('\n')
-        sql_header += '\n'
+        return BaseTableView.get_reverse_engineered_sql(
+            self, did, scid, tid, main_sql, data)
 
-        # Add into main sql
-        main_sql.append(sql_header)
-
-        # Parse privilege data
-        if 'relacl' in data:
-            data['relacl'] = parse_priv_to_db(data['relacl'], self.acl)
-
-        # If the request for new object which do not have did
-        table_sql = render_template("/".join([self.template_path,
-                                              'create.sql']),
-                                    data=data, conn=self.conn, is_sql=True)
-
-        # Add into main sql
-        table_sql = re.sub('\n{2,}', '\n\n', table_sql)
-        main_sql.append(table_sql.strip('\n'))
-
-        """
-        ######################################
-        # 2) Reverse engineered sql for INDEX
-        ######################################
-        """
-
-        SQL = render_template("/".join([self.index_template_path,
-                                        'nodes.sql']), tid=tid)
-        status, rset = self.conn.execute_2darray(SQL)
-        if not status:
-            return internal_server_error(errormsg=rset)
-
-        for row in rset['rows']:
-
-            SQL = render_template("/".join([self.index_template_path,
-                                            'properties.sql']),
-                                  did=did, tid=tid, idx=row['oid'],
-                                  datlastsysoid=self.datlastsysoid)
-
-            status, res = self.conn.execute_dict(SQL)
-            if not status:
-                return internal_server_error(errormsg=res)
-
-            data = dict(res['rows'][0])
-            # Adding parent into data dict, will be using it while creating sql
-            data['schema'] = schema
-            data['table'] = table
-            # We also need to fecth columns of index
-            SQL = render_template("/".join([self.index_template_path,
-                                            'column_details.sql']),
-                                  idx=row['oid'])
-            status, rset = self.conn.execute_2darray(SQL)
-            if not status:
-                return internal_server_error(errormsg=rset)
-
-            # 'attdef' comes with quotes from query so we need to strip them
-            # 'options' we need true/false to render switch ASC(false)/DESC(true)
-            columns = []
-            cols = []
-            for col_row in rset['rows']:
-                # We need all data as collection for ColumnsModel
-                cols_data = {
-                    'colname': col_row['attdef'].strip('"'),
-                    'collspcname': col_row['collnspname'],
-                    'op_class': col_row['opcname'],
-                }
-                if col_row['options'][0] == 'DESC':
-                    cols_data['sort_order'] = True
-                columns.append(cols_data)
-
-                # We need same data as string to display in properties window
-                # If multiple column then separate it by colon
-                cols_str = col_row['attdef']
-                if col_row['collnspname']:
-                    cols_str += ' COLLATE ' + col_row['collnspname']
-                if col_row['opcname']:
-                    cols_str += ' ' + col_row['opcname']
-                if col_row['options'][0] == 'DESC':
-                    cols_str += ' DESC'
-                cols.append(cols_str)
-
-            # Push as collection
-            data['columns'] = columns
-            # Push as string
-            data['cols'] = ', '.join(cols)
-
-            sql_header = u"\n-- Index: {0}\n\n-- ".format(data['name'])
-
-            sql_header += render_template("/".join([self.index_template_path,
-                                                    'delete.sql']),
-                                          data=data, conn=self.conn)
-
-            index_sql = render_template("/".join([self.index_template_path,
-                                                  'create.sql']),
-                                        data=data, conn=self.conn)
-            index_sql += "\n"
-            index_sql += render_template("/".join([self.index_template_path,
-                                                   'alter.sql']),
-                                         data=data, conn=self.conn)
-
-            # Add into main sql
-            index_sql = re.sub('\n{2,}', '\n\n', index_sql)
-            main_sql.append(sql_header + '\n\n' + index_sql.strip('\n'))
-
-        """
-        ########################################
-        # 3) Reverse engineered sql for TRIGGERS
-        ########################################
-        """
-        SQL = render_template("/".join([self.trigger_template_path,
-                                        'nodes.sql']), tid=tid)
-        status, rset = self.conn.execute_2darray(SQL)
-        if not status:
-            return internal_server_error(errormsg=rset)
-
-        for row in rset['rows']:
-            trigger_sql = ''
-
-            SQL = render_template("/".join([self.trigger_template_path,
-                                            'properties.sql']),
-                                  tid=tid, trid=row['oid'],
-                                  datlastsysoid=self.datlastsysoid)
-
-            status, res = self.conn.execute_dict(SQL)
-            if not status:
-                return internal_server_error(errormsg=res)
-
-            data = dict(res['rows'][0])
-            # Adding parent into data dict, will be using it while creating sql
-            data['schema'] = schema
-            data['table'] = table
-
-            data = self.get_trigger_function_schema(data)
-
-            if len(data['custom_tgargs']) > 1:
-                # We know that trigger has more than 1 argument, let's join them
-                data['tgargs'] = self._format_args(data['custom_tgargs'])
-
-            if len(data['tgattr']) > 1:
-                columns = ', '.join(data['tgattr'].split(' '))
-
-                SQL = render_template("/".join([self.trigger_template_path,
-                                                'get_columns.sql']),
-                                      tid=tid, clist=columns)
-
-                status, rset = self.conn.execute_2darray(SQL)
-                if not status:
-                    return internal_server_error(errormsg=rset)
-                # 'tgattr' contains list of columns from table used in trigger
-                columns = []
-
-                for col_row in rset['rows']:
-                    columns.append({'column': col_row['name']})
-
-                data['columns'] = columns
-
-            data = trigger_definition(data)
-
-            sql_header = u"\n-- Trigger: {0}\n\n-- ".format(data['name'])
-
-            sql_header += render_template("/".join([self.trigger_template_path,
-                                                    'delete.sql']),
-                                          data=data, conn=self.conn)
-
-            # If the request for new object which do not have did
-            trigger_sql = render_template("/".join([self.trigger_template_path,
-                                                    'create.sql']),
-                                          data=data, conn=self.conn)
-
-            trigger_sql = sql_header + '\n\n' + trigger_sql.strip('\n')
-
-            # If trigger is disabled then add sql code for the same
-            if not data['is_enable_trigger']:
-                trigger_sql += '\n\n'
-                trigger_sql += render_template("/".join([
-                    self.trigger_template_path,
-                    'enable_disable_trigger.sql']),
-                    data=data, conn=self.conn)
-
-            # Add into main sql
-            trigger_sql = re.sub('\n{2,}', '\n\n', trigger_sql)
-            main_sql.append(trigger_sql)
-
-        """
-        #####################################
-        # 4) Reverse engineered sql for RULES
-        #####################################
-        """
-
-        SQL = render_template("/".join(
-            [self.rules_template_path, 'properties.sql']), tid=tid)
-
-        status, rset = self.conn.execute_2darray(SQL)
-        if not status:
-            return internal_server_error(errormsg=rset)
-
-        for row in rset['rows']:
-            rules_sql = '\n'
-            SQL = render_template("/".join(
-                [self.rules_template_path, 'properties.sql']
-            ), rid=row['oid'], datlastsysoid=self.datlastsysoid)
-
-            status, res = self.conn.execute_dict(SQL)
-            if not status:
-                return internal_server_error(errormsg=res)
-
-            res_data = parse_rule_definition(res)
-            rules_sql += render_template("/".join(
-                [self.rules_template_path, 'create.sql']),
-                data=res_data, display_comments=True)
-
-            # Add into main sql
-            rules_sql = re.sub('\n{2,}', '\n\n', rules_sql)
-            main_sql.append(rules_sql)
-
-        sql = '\n'.join(main_sql)
-
-        return ajax_response(response=sql.strip('\n'))
-
-    @check_precondition
+    @BaseTableView.check_precondition
     def select_sql(self, gid, sid, did, scid, tid):
         """
         SELECT script sql for the object
@@ -2810,7 +1206,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         Returns:
             SELECT Script sql for the object
         """
-        SQL = render_template("/".join([self.template_path,
+        SQL = render_template("/".join([self.table_template_path,
                                         'properties.sql']),
                               did=did, scid=scid, tid=tid,
                               datlastsysoid=self.datlastsysoid)
@@ -2839,7 +1235,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         )
         return ajax_response(response=sql)
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def insert_sql(self, gid, sid, did, scid, tid):
         """
         INSERT script sql for the object
@@ -2854,7 +1250,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         Returns:
             INSERT Script sql for the object
         """
-        SQL = render_template("/".join([self.template_path,
+        SQL = render_template("/".join([self.table_template_path,
                                         'properties.sql']),
                               did=did, scid=scid, tid=tid,
                               datlastsysoid=self.datlastsysoid)
@@ -2886,7 +1282,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
 
         return ajax_response(response=sql)
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def update_sql(self, gid, sid, did, scid, tid):
         """
         UPDATE script sql for the object
@@ -2901,7 +1297,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         Returns:
             UPDATE Script sql for the object
         """
-        SQL = render_template("/".join([self.template_path,
+        SQL = render_template("/".join([self.table_template_path,
                                         'properties.sql']),
                               did=did, scid=scid, tid=tid,
                               datlastsysoid=self.datlastsysoid)
@@ -2935,7 +1331,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
 
         return ajax_response(response=sql)
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def delete_sql(self, gid, sid, did, scid, tid):
         """
         DELETE script sql for the object
@@ -2950,7 +1346,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         Returns:
             DELETE Script sql for the object
         """
-        SQL = render_template("/".join([self.template_path,
+        SQL = render_template("/".join([self.table_template_path,
                                         'properties.sql']),
                               did=did, scid=scid, tid=tid,
                               datlastsysoid=self.datlastsysoid)
@@ -2966,7 +1362,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
 
         return ajax_response(response=sql)
 
-    @check_precondition
+    @BaseTableView.check_precondition
     def statistics(self, gid, sid, did, scid, tid=None):
         """
         Statistics
@@ -2982,63 +1378,6 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
         otherwise it will return statistics for all the tables in that
         schema.
         """
-
-        # Fetch schema name
-        status, schema_name = self.conn.execute_scalar(
-            render_template(
-                "/".join([self.template_path, 'get_schema.sql']),
-                conn=self.conn, scid=scid
-            )
-        )
-        if not status:
-            return internal_server_error(errormsg=schema_name)
-
-        if tid is None:
-            status, res = self.conn.execute_dict(
-                render_template(
-                    "/".join([self.template_path, 'coll_table_stats.sql']),
-                    conn=self.conn, schema_name=schema_name
-                )
-            )
-        else:
-            # For Individual table stats
-
-            # Check if pgstattuple extension is already created?
-            # if created then only add extended stats
-            status, is_pgstattuple = self.conn.execute_scalar("""
-            SELECT (count(extname) > 0) AS is_pgstattuple
-            FROM pg_extension
-            WHERE extname='pgstattuple'
-            """)
-            if not status:
-                return internal_server_error(errormsg=is_pgstattuple)
-
-            # Fetch Table name
-            status, table_name = self.conn.execute_scalar(
-                render_template(
-                    "/".join([self.template_path, 'get_table.sql']),
-                    conn=self.conn, scid=scid, tid=tid
-                )
-            )
-            if not status:
-                return internal_server_error(errormsg=table_name)
-
-            status, res = self.conn.execute_dict(
-                render_template(
-                    "/".join([self.template_path, 'stats.sql']),
-                    conn=self.conn, schema_name=schema_name,
-                    table_name=table_name,
-                    is_pgstattuple=is_pgstattuple, tid=tid
-                )
-            )
-
-        if not status:
-            return internal_server_error(errormsg=res)
-
-        return make_json_response(
-            data=res,
-            status=200
-        )
-
+        return BaseTableView.get_table_statistics(self, scid, tid)
 
 TableView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/templates/column/js/column.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/templates/column/js/column.js
index 179b961..70ffbd2 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/templates/column/js/column.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/templates/column/js/column.js
@@ -86,6 +86,7 @@ define('pgadmin.node.column', [
 
   if (!pgBrowser.Nodes['column']) {
     pgBrowser.Nodes['column'] = pgBrowser.Node.extend({
+      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
       parent_type: ['table', 'view', 'mview'],
       collection_type: ['coll-table', 'coll-view', 'coll-mview'],
       type: 'column',
@@ -228,6 +229,17 @@ define('pgadmin.node.column', [
               return false;
             }
 
+            // If table is partitioned table then disable
+            if (m.top && !_.isUndefined(m.top.get('is_partitioned')) &&
+              m.top.get('is_partitioned'))
+            {
+              setTimeout(function () {
+                m.set('is_primary_key', false);
+              }, 10);
+
+              return false;
+            }
+
             if(!m.inSchemaWithColumnCheck.apply(this, [m]) &&
               !_.isUndefined(name) && !_.isNull(name) && name !== '') {
               return true;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/templates/check_constraint/js/check_constraint.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/templates/check_constraint/js/check_constraint.js
index 82bb829..68378fb 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/templates/check_constraint/js/check_constraint.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/templates/check_constraint/js/check_constraint.js
@@ -8,6 +8,7 @@ define('pgadmin.node.check_constraints', [
   // Check Constraint Node
   if (!pgBrowser.Nodes['check_constraints']) {
     pgAdmin.Browser.Nodes['check_constraints'] = pgBrowser.Node.extend({
+      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
       type: 'check_constraints',
       label: gettext('Check'),
       collection_type: 'coll-constraints',
@@ -16,7 +17,7 @@ define('pgadmin.node.check_constraints', [
       dialogHelp: url_for('help.static', {'filename': 'check_dialog.html'}),
       hasSQL: true,
       hasDepends: true,
-      parent_type: ['table'],
+      parent_type: ['table','partition'],
       Init: function() {
         // Avoid mulitple registration of menus
         if (this.initialized)
@@ -133,6 +134,18 @@ define('pgadmin.node.check_constraints', [
           'switch', cell: 'boolean', group: gettext('Definition'), mode:
           ['properties', 'create', 'edit'], min_version: 90200,
           disabled: function(m) {
+            // Disabled if table is a partitioned table.
+            if ((_.has(m , 'top') && !_.isUndefined(m.top) && m.top.get('is_partitioned')) ||
+                (_.has(m, 'node_info') && _.has(m.node_info, 'table') &&
+                _.has(m.node_info.table, 'is_partitioned') && m.node_info.table.is_partitioned)
+            ){
+              setTimeout(function(){
+                  m.set('connoinherit', false);
+              },10);
+
+              return true;
+            }
+
             return ((_.has(m, 'handler') &&
               !_.isUndefined(m.handler) &&
               !_.isUndefined(m.get('oid'))) || (_.isFunction(m.isNew) && !m.isNew()));
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/templates/exclusion_constraint/js/exclusion_constraint.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/templates/exclusion_constraint/js/exclusion_constraint.js
index 814edce..605495d 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/templates/exclusion_constraint/js/exclusion_constraint.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/templates/exclusion_constraint/js/exclusion_constraint.js
@@ -602,6 +602,7 @@ define('pgadmin.node.exclusion_constraint', [
   // Extend the browser's node class for exclusion constraint node
   if (!pgBrowser.Nodes['exclusion_constraint']) {
     pgAdmin.Browser.Nodes['exclusion_constraint'] = pgBrowser.Node.extend({
+      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
       type: 'exclusion_constraint',
       label: gettext('Exclusion constraint'),
       collection_type: 'coll-constraints',
@@ -609,7 +610,7 @@ define('pgadmin.node.exclusion_constraint', [
       sqlCreateHelp: 'ddl-constraints.html',
       dialogHelp: url_for('help.static', {'filename': 'exclusion_constraint_dialog.html'}),
       hasSQL: true,
-      parent_type: 'table',
+      parent_type: ['table','partition'],
       canDrop: true,
       canDropCascade: true,
       hasDepends: true,
@@ -916,12 +917,22 @@ define('pgadmin.node.exclusion_constraint', [
           if (data && data.check == false)
             return true;
 
-          var t = pgBrowser.tree, i = item, d = itemData, parents = [];
+          var t = pgBrowser.tree, i = item, d = itemData, parents = [],
+            immediate_parent_table_found = false,
+            is_immediate_parent_table_partitioned = false;
           // To iterate over tree to check parent node
           while (i) {
+            // If table is partitioned table then return false
+            if (!immediate_parent_table_found && (d._type == 'table' || d._type == 'partition')) {
+              immediate_parent_table_found = true;
+              if ('is_partitioned' in d && d.is_partitioned) {
+                is_immediate_parent_table_partitioned = true;
+              }
+            }
+
             // If it is schema then allow user to create table
             if (_.indexOf(['schema'], d._type) > -1)
-              return true;
+              return !is_immediate_parent_table_partitioned;
             parents.push(d._type);
             i = t.hasParent(i) ? t.parent(i) : null;
             d = i ? t.itemData(i) : null;
@@ -930,7 +941,7 @@ define('pgadmin.node.exclusion_constraint', [
           if (_.indexOf(parents, 'catalog') > -1) {
             return false;
           } else {
-            return true;
+            return !is_immediate_parent_table_partitioned;
           }
       }
     });
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/templates/foreign_key/js/foreign_key.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/templates/foreign_key/js/foreign_key.js
index 84b7adf..f7b352a 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/templates/foreign_key/js/foreign_key.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/templates/foreign_key/js/foreign_key.js
@@ -600,6 +600,7 @@ define('pgadmin.node.foreign_key', [
   // Extend the browser's node class for foreign key node
   if (!pgBrowser.Nodes['foreign_key']) {
     pgAdmin.Browser.Nodes['foreign_key'] = pgBrowser.Node.extend({
+      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
       type: 'foreign_key',
       label: gettext('Foreign key'),
       collection_type: 'coll-constraints',
@@ -608,7 +609,7 @@ define('pgadmin.node.foreign_key', [
       dialogHelp: url_for('help.static', {'filename': 'foreign_key_dialog.html'}),
       hasSQL: true,
       hasDepends: false,
-      parent_type: 'table',
+      parent_type: ['table','partition'],
       canDrop: true,
       canDropCascade: true,
       hasDepends: true,
@@ -1064,12 +1065,22 @@ define('pgadmin.node.foreign_key', [
           if (data && data.check == false)
             return true;
 
-          var t = pgBrowser.tree, i = item, d = itemData, parents = [];
+          var t = pgBrowser.tree, i = item, d = itemData, parents = [],
+            immediate_parent_table_found = false,
+            is_immediate_parent_table_partitioned = false;
           // To iterate over tree to check parent node
           while (i) {
+            // If table is partitioned table then return false
+            if (!immediate_parent_table_found && (d._type == 'table' || d._type == 'partition')) {
+              immediate_parent_table_found = true;
+              if ('is_partitioned' in d && d.is_partitioned) {
+                is_immediate_parent_table_partitioned = true;
+              }
+            }
+
             // If it is schema then allow user to c reate table
             if (_.indexOf(['schema'], d._type) > -1)
-              return true;
+              return !is_immediate_parent_table_partitioned;
             parents.push(d._type);
             i = t.hasParent(i) ? t.parent(i) : null;
             d = i ? t.itemData(i) : null;
@@ -1078,7 +1089,7 @@ define('pgadmin.node.foreign_key', [
           if (_.indexOf(parents, 'catalog') > -1) {
             return false;
           } else {
-            return true;
+            return !is_immediate_parent_table_partitioned;
           }
       }
     });
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/templates/index_constraint/js/index_constraint.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/templates/index_constraint/js/index_constraint.js
index 97a404a..9479acd 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/templates/index_constraint/js/index_constraint.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/templates/index_constraint/js/index_constraint.js
@@ -6,6 +6,7 @@ define('pgadmin.node.{{node_type}}', [
   // Extend the browser's node class for index constraint node
   if (!pgBrowser.Nodes['{{node_type}}']) {
     pgAdmin.Browser.Nodes['{{node_type}}'] = pgBrowser.Node.extend({
+      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
       type: '{{node_type}}',
       label: '{{ node_label }}',
       collection_type: 'coll-constraints',
@@ -20,7 +21,7 @@ define('pgadmin.node.{{node_type}}', [
       hasDepends: true,
       hasStatistics: true,
       statsPrettifyFields: ['Index size'],
-      parent_type: 'table',
+      parent_type: ['table','partition'],
       canDrop: true,
       canDropCascade: true,
       Init: function() {
@@ -45,12 +46,28 @@ define('pgadmin.node.{{node_type}}', [
         if (data && data.check == false)
           return true;
 
-        var t = pgBrowser.tree, i = item, d = itemData, parents = [];
+        var t = pgBrowser.tree, i = item, d = itemData, parents = [],
+          immediate_parent_table_found = false,
+          is_immediate_parent_table_partitioned = false;
+
         // To iterate over tree to check parent node
         while (i) {
-          // If it is schema then allow user to c reate table
+          // If table is partitioned table then return false
+          if (!immediate_parent_table_found && (d._type == 'table' || d._type == 'partition')) {
+            immediate_parent_table_found = true;
+            if ('is_partitioned' in d && d.is_partitioned) {
+                is_immediate_parent_table_partitioned = true;
+            }
+          }
+
+          // If it is schema then allow user to create table
           if (_.indexOf(['schema'], d._type) > -1) {
             {% if node_type == 'primary_key' %}
+
+            if (is_immediate_parent_table_partitioned) {
+              return false;
+            }
+
             // There should be only one primary key per table.
             var children = t.children(arguments[1], false),
               primary_key_found = false;
@@ -63,7 +80,7 @@ define('pgadmin.node.{{node_type}}', [
             });
             return !primary_key_found;
             {% else %}
-            return true;
+            return !is_immediate_parent_table_partitioned;
             {% endif %}
           }
           parents.push(d._type);
@@ -74,7 +91,7 @@ define('pgadmin.node.{{node_type}}', [
         if (_.indexOf(parents, 'catalog') > -1) {
           return false;
         } else {
-            return true;
+            return !is_immediate_parent_table_partitioned;
         }
       },
 
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/templates/constraints/js/constraints.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/templates/constraints/js/constraints.js
index a7346f1..e372b3c 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/templates/constraints/js/constraints.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/templates/constraints/js/constraints.js
@@ -9,16 +9,18 @@ define('pgadmin.node.constraints', [
         node: 'constraints',
         label: gettext('Constraints'),
         type: 'coll-constraints',
+        getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
         columns: ['name', 'comment']
       });
   };
 
   if (!pgBrowser.Nodes['constraints']) {
     pgAdmin.Browser.Nodes['constraints'] = pgBrowser.Node.extend({
+      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
       type: 'constraints',
       label: gettext('Constraints'),
       collection_type: 'coll-constraints',
-      parent_type: ['table'],
+      parent_type: ['table','partition'],
       Init: function() {
         /* Avoid mulitple registration of menus */
         if (this.initialized)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/__init__.py
index e52c15b..85da766 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/__init__.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/__init__.py
@@ -72,6 +72,18 @@ class IndexesModule(CollectionNodeModule):
         if super(IndexesModule, self).BackendSupported(manager, **kwargs):
             conn = manager.connection(did=kwargs['did'])
 
+            # In case of partitioned table return false.
+            if 'tid' in kwargs and manager.version >= 100000:
+                partition_path = 'partition/sql/#{0}#'.format(manager.version)
+                SQL = render_template("/".join(
+                    [partition_path, 'backend_support.sql']), tid=kwargs['tid'])
+                status, res = conn.execute_scalar(SQL)
+
+                # check if any errors
+                if not status:
+                    return internal_server_error(errormsg=res)
+                return not res
+
             if 'vid' not in kwargs:
                 return True
 
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/templates/index/js/index.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/templates/index/js/index.js
index 24f37f0..4fc1d54 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/templates/index/js/index.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/templates/index/js/index.js
@@ -10,6 +10,7 @@ define('pgadmin.node.index', [
         node: 'index',
         label: gettext('Indexes'),
         type: 'coll-index',
+        getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
         sqlAlterHelp: 'sql-alterindex.html',
         sqlCreateHelp: 'sql-createindex.html',
         dialogHelp: url_for('help.static', {'filename': 'index_dialog.html'}),
@@ -210,8 +211,9 @@ define('pgadmin.node.index', [
     });
 
   if (!pgBrowser.Nodes['index']) {
-    pgAdmin.Browser.Nodes['index'] = pgAdmin.Browser.Node.extend({
-      parent_type: ['table', 'view', 'mview'],
+    pgAdmin.Browser.Nodes['index'] = pgBrowser.Node.extend({
+      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
+      parent_type: ['table', 'view', 'mview', 'partition'],
       collection_type: ['coll-table', 'coll-view'],
       sqlAlterHelp: 'sql-alterindex.html',
       sqlCreateHelp: 'sql-createindex.html',
@@ -247,6 +249,12 @@ define('pgadmin.node.index', [
           icon: 'wcTabIcon icon-index', data: {action: 'create', check: true},
           enable: 'canCreate'
         },{
+          name: 'create_index_onPartition', node: 'partition', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: gettext('Index...'),
+          icon: 'wcTabIcon icon-index', data: {action: 'create', check: true},
+          enable: 'canCreate'
+        },{
           name: 'create_index_onMatView', node: 'mview', module: this,
           applies: ['object', 'context'], callback: 'show_obj_properties',
           category: 'create', priority: 5, label: gettext('Index...'),
@@ -472,12 +480,23 @@ define('pgadmin.node.index', [
           if (data && data.check == false)
             return true;
 
-          var t = pgBrowser.tree, i = item, d = itemData, parents = [];
+          var t = pgBrowser.tree, i = item, d = itemData, parents = [],
+            immediate_parent_table_found = false,
+            is_immediate_parent_table_partitioned = false;
           // To iterate over tree to check parent node
           while (i) {
-            // If it is schema then allow user to c reate table
+            // Do not allow creating index on partitioned tables.
+            if (!immediate_parent_table_found &&
+                _.indexOf(['table', 'partition'], d._type) > -1) {
+              immediate_parent_table_found = true;
+              if ('is_partitioned' in d && d.is_partitioned) {
+                is_immediate_parent_table_partitioned = true;
+              }
+            }
+
+            // If it is schema then allow user to create index
             if (_.indexOf(['schema'], d._type) > -1)
-              return true;
+              return !is_immediate_parent_table_partitioned;
             parents.push(d._type);
             i = t.hasParent(i) ? t.parent(i) : null;
             d = i ? t.itemData(i) : null;
@@ -486,7 +505,7 @@ define('pgadmin.node.index', [
           if (_.indexOf(parents, 'catalog') > -1) {
             return false;
           } else {
-            return true;
+            return !is_immediate_parent_table_partitioned;
           }
       }
   });
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/__init__.py
new file mode 100644
index 0000000..ce20b98
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/__init__.py
@@ -0,0 +1,574 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2017, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+""" Implements Partitions Node """
+
+import re
+import simplejson as json
+import pgadmin.browser.server_groups.servers.databases.schemas as schema
+from flask import render_template, request
+from flask_babel import gettext
+from pgadmin.browser.server_groups.servers.databases.schemas.utils \
+    import DataTypeReader, VacuumSettings
+from pgadmin.utils.ajax import internal_server_error, \
+    make_response as ajax_response, gone
+from pgadmin.browser.server_groups.servers.databases.schemas.tables.utils \
+    import BaseTableView
+from pgadmin.browser.collection import CollectionNodeModule
+from pgadmin.utils.ajax import make_json_response, precondition_required
+from config import PG_DEFAULT_DRIVER
+from pgadmin.browser.utils import PGChildModule
+
+
+class PartitionsModule(CollectionNodeModule):
+    """
+     class PartitionsModule(CollectionNodeModule)
+
+        A module class for Partition node derived from CollectionNodeModule.
+
+    Methods:
+    -------
+    * __init__(*args, **kwargs)
+      - Method is used to initialize the Partition and it's base module.
+
+    * get_nodes(gid, sid, did, scid, tid)
+      - Method is used to generate the browser collection node.
+
+    * node_inode()
+      - Method is overridden from its base class to make the node as leaf node.
+
+    * script_load()
+      - Load the module script for schema, when any of the server node is
+        initialized.
+    """
+
+    NODE_TYPE = 'partition'
+    COLLECTION_LABEL = gettext("Partitions")
+
+    def __init__(self, *args, **kwargs):
+        """
+        Method is used to initialize the PartitionsModule and it's base module.
+
+        Args:
+            *args:
+            **kwargs:
+        """
+        super(PartitionsModule, self).__init__(*args, **kwargs)
+        self.min_ver = 100000
+        self.max_ver = None
+
+    def get_nodes(self, gid, sid, did, scid, **kwargs):
+        """
+        Generate the collection node
+        """
+        yield self.generate_browser_collection_node(kwargs['tid'])
+
+    @property
+    def script_load(self):
+        """
+        Load the module script for server, when any of the server-group node is
+        initialized.
+        """
+        return schema.SchemaModule.NODE_TYPE
+
+    @property
+    def node_inode(self):
+        """
+        Load the module node as a leaf node
+        """
+        return True
+
+    def BackendSupported(self, manager, **kwargs):
+        """
+        Load this module if it is a partition table
+        """
+        if 'tid' in kwargs and CollectionNodeModule.BackendSupported(self, manager, **kwargs):
+            conn = manager.connection(did=kwargs['did'])
+
+            template_path = 'partition/sql/#{0}#'.format(manager.version)
+            SQL = render_template("/".join(
+                [template_path, 'backend_support.sql']), tid=kwargs['tid'])
+            status, res = conn.execute_scalar(SQL)
+
+            # check if any errors
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            return res
+
+    def register(self, app, options, first_registration=False):
+        """
+        Override the default register function to automatically register
+        sub-modules of table node under partition table node.
+        """
+
+        if first_registration:
+            self.submodules = list(app.find_submodules(self.import_name))
+
+        super(CollectionNodeModule, self).register(app, options, first_registration)
+
+        for module in self.submodules:
+            if first_registration:
+                module.parentmodules.append(self)
+            app.register_blueprint(module)
+
+        # Now add sub modules of table node to partition table node.
+        if first_registration:
+            # Exclude 'partition' module for now to avoid cyclic import issue.
+            modules_to_skip = ['partition', 'column']
+            for parent in self.parentmodules:
+                if parent.NODE_TYPE == 'table':
+                    self.submodules += [
+                        submodule for submodule in parent.submodules
+                        if submodule.NODE_TYPE not in modules_to_skip]
+
+blueprint = PartitionsModule(__name__)
+
+
+class PartitionsView(BaseTableView, DataTypeReader, VacuumSettings):
+    """
+    This class is responsible for generating routes for Partition node
+
+    Methods:
+    -------
+
+    * list()
+      - This function is used to list all the Partition nodes within that
+      collection.
+
+    * nodes()
+      - This function will used to create all the child node within that
+        collection, Here it will create all the Partition node.
+
+    * properties(gid, sid, did, scid, tid, ptid)
+      - This function will show the properties of the selected Partition node
+
+    """
+
+    node_type = blueprint.node_type
+
+    parent_ids = [
+        {'type': 'int', 'id': 'gid'},
+        {'type': 'int', 'id': 'sid'},
+        {'type': 'int', 'id': 'did'},
+        {'type': 'int', 'id': 'scid'},
+        {'type': 'int', 'id': 'tid'}
+    ]
+    ids = [
+        {'type': 'int', 'id': 'ptid'}
+    ]
+
+    operations = dict({
+        'obj': [
+            {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+            {'get': 'list', 'post': 'create'}
+        ],
+        'nodes': [{'get': 'nodes'}, {'get': 'nodes'}],
+        'children': [{'get': 'children'}],
+        'sql': [{'get': 'sql'}],
+        'msql': [{'get': 'msql'}, {}],
+        'module.js': [{}, {}, {'get': 'module_js'}],
+        'detach': [{'put': 'detach'}],
+        'truncate': [{'put': 'truncate'}]
+
+    })
+
+    def children(self, **kwargs):
+        """Build a list of treeview nodes from the child nodes."""
+
+        if 'sid' not in kwargs:
+            return precondition_required(
+                gettext('Required properties are missing.')
+            )
+
+        from pgadmin.utils.driver import get_driver
+        manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(
+            sid=kwargs['sid']
+        )
+
+        did = None
+        if 'did' in kwargs:
+            did = kwargs['did']
+
+        conn = manager.connection(did=did)
+
+        if not conn.connected():
+            return precondition_required(
+                gettext(
+                    "Connection to the server has been lost."
+                )
+            )
+
+        nodes = []
+        for module in self.blueprint.submodules:
+            if isinstance(module, PGChildModule):
+                if manager is not None and \
+                        module.BackendSupported(manager, **kwargs):
+                    # treat partition table as normal table.
+                    # replace tid with ptid and pop ptid from kwargs
+                    if 'ptid' in kwargs:
+                        ptid = kwargs.pop('ptid')
+                        kwargs['tid'] = ptid
+                    nodes.extend(module.get_nodes(**kwargs))
+            else:
+                nodes.extend(module.get_nodes(**kwargs))
+
+        # Explicitly include 'partition' module as we had excluded it during
+        # registration.
+        nodes.extend(self.blueprint.get_nodes(**kwargs))
+
+        # Return sorted nodes based on label
+        return make_json_response(
+            data=sorted(
+                nodes, key=lambda c: c['label']
+            )
+        )
+
+    @BaseTableView.check_precondition
+    def list(self, gid, sid, did, scid, tid):
+        """
+        This function is used to list all the table nodes within that
+        collection.
+
+        Args:
+            gid: Server group ID
+            sid: Server ID
+            did: Database ID
+            scid: Schema ID
+            tid: Table ID
+
+        Returns:
+            JSON of available table nodes
+        """
+        SQL = render_template("/".join([self.partition_template_path,
+                                        'properties.sql']),
+                              did=did, scid=scid, tid=tid,
+                              datlastsysoid=self.datlastsysoid)
+        status, res = self.conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+        return ajax_response(
+            response=res['rows'],
+            status=200
+        )
+
+    @BaseTableView.check_precondition
+    def nodes(self, gid, sid, did, scid, tid, ptid=None):
+        """
+        This function is used to list all the table nodes within that
+        collection.
+
+        Args:
+            gid: Server group ID
+            sid: Server ID
+            did: Database ID
+            scid: Schema ID
+            tid: Parent Table ID
+            ptid: Partition Table ID
+
+        Returns:
+            JSON of available table nodes
+        """
+        SQL = render_template(
+                "/".join([self.partition_template_path, 'nodes.sql']),
+                scid=scid, tid=tid
+                )
+        status, rset = self.conn.execute_2darray(SQL)
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        def browser_node(row):
+            return self.blueprint.generate_browser_node(
+                row['oid'],
+                tid,
+                row['name'],
+                icon="icon-partition",
+                tigger_count=row['triggercount'],
+                has_enable_triggers=row['has_enable_triggers'],
+                is_partitioned=row['is_partitioned'],
+                parent_schema_id=scid,
+                schema_id=row['schema_id'],
+                schema_name=row['schema_name']
+            )
+
+        if ptid is not None:
+            if len(rset['rows']) == 0:
+                return gone(gettext(
+                    "The specified partitioned table could not be found."
+                ))
+
+            return make_json_response(
+                data=browser_node(rset['rows'][0]), status=200
+                )
+
+        res = []
+        for row in rset['rows']:
+            res.append(browser_node(row))
+
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @BaseTableView.check_precondition
+    def properties(self, gid, sid, did, scid, tid, ptid):
+        """
+        This function will show the properties of the selected table node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did:  Database ID
+            scid: Schema ID
+            scid: Schema ID
+            tid: Table ID
+            ptid: Partition Table ID
+
+        Returns:
+            JSON of selected table node
+        """
+
+        SQL = render_template("/".join([self.partition_template_path,
+                                        'properties.sql']),
+                              did=did, scid=scid, tid=tid,
+                              ptid=ptid, datlastsysoid=self.datlastsysoid)
+        status, res = self.conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        if len(res['rows']) == 0:
+                return gone(gettext(
+                    "The specified partitioned table could not be found."))
+
+        return super(PartitionsView, self).properties(
+            gid, sid, did, scid, ptid, res)
+
+    @BaseTableView.check_precondition
+    def sql(self, gid, sid, did, scid, tid, ptid):
+        """
+        This function will creates reverse engineered sql for
+        the table object
+
+         Args:
+           gid: Server Group ID
+           sid: Server ID
+           did: Database ID
+           scid: Schema ID
+           tid: Table ID
+           ptid: Partition Table ID
+        """
+        main_sql = []
+
+        SQL = render_template("/".join([self.partition_template_path,
+                                        'properties.sql']),
+                              did=did, scid=scid, tid=tid,
+                              ptid=ptid, datlastsysoid=self.datlastsysoid)
+        status, res = self.conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        if len(res['rows']) == 0:
+                return gone(gettext(
+                    "The specified partitioned table could not be found."))
+
+        data = res['rows'][0]
+
+        return BaseTableView.get_reverse_engineered_sql(self, did, scid, ptid,
+                                                        main_sql, data)
+
+    @BaseTableView.check_precondition
+    def detach(self, gid, sid, did, scid, tid, ptid):
+        """
+        This function will reset statistics of table
+
+         Args:
+           gid: Server Group ID
+           sid: Server ID
+           did: Database ID
+           scid: Schema ID
+           tid: Table ID
+           ptid: Partition Table ID
+        """
+        # Fetch schema name
+        status, parent_schema = self.conn.execute_scalar(
+            render_template(
+                "/".join([self.table_template_path, 'get_schema.sql']),
+                conn=self.conn, scid=scid
+            )
+        )
+        if not status:
+            return internal_server_error(errormsg=parent_schema)
+
+        # Fetch Parent Table name
+        status, partitioned_table_name = self.conn.execute_scalar(
+            render_template(
+                "/".join([self.table_template_path, 'get_table.sql']),
+                conn=self.conn, scid=scid, tid=tid
+            )
+        )
+        if not status:
+            return internal_server_error(errormsg=partitioned_table_name)
+
+        # Get schema oid of partition
+        status, pscid = self.conn.execute_scalar(
+            render_template("/".join([self.table_template_path,
+                                      'get_schema_oid.sql']), tid=ptid))
+        if not status:
+            return internal_server_error(errormsg=scid)
+
+        # Fetch schema name
+        status, partition_schema = self.conn.execute_scalar(
+            render_template("/".join([self.table_template_path,
+                                      'get_schema.sql']), conn=self.conn,
+                            scid=pscid)
+        )
+        if not status:
+            return internal_server_error(errormsg=partition_schema)
+
+        # Fetch Partition Table name
+        status, partition_name = self.conn.execute_scalar(
+            render_template(
+                "/".join([self.table_template_path, 'get_table.sql']),
+                conn=self.conn, scid=pscid, tid=ptid
+            )
+        )
+        if not status:
+            return internal_server_error(errormsg=partition_name)
+
+        try:
+            temp_data = dict()
+            temp_data['parent_schema'] = parent_schema
+            temp_data['partitioned_table_name'] = partitioned_table_name
+            temp_data['schema'] = partition_schema
+            temp_data['name'] = partition_name
+
+            SQL = render_template("/".join(
+                            [self.partition_template_path, 'detach.sql']),
+                            data=temp_data, conn=self.conn)
+
+            status, res = self.conn.execute_scalar(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            return make_json_response(
+                success=1,
+                info=gettext("Partition detached."),
+                data={
+                    'id': ptid,
+                    'scid': scid
+                }
+            )
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @BaseTableView.check_precondition
+    def msql(self, gid, sid, did, scid, tid, ptid=None):
+        """
+        This function will create modified sql for table object
+
+         Args:
+           gid: Server Group ID
+           sid: Server ID
+           did: Database ID
+           scid: Schema ID
+           tid: Table ID
+        """
+        data = dict()
+        for k, v in request.args.items():
+            try:
+                data[k] = json.loads(v, encoding='utf-8')
+            except (ValueError, TypeError, KeyError):
+                data[k] = v
+
+        if ptid is not None:
+            SQL = render_template("/".join([self.partition_template_path,
+                                            'properties.sql']),
+                                  did=did, scid=scid, tid=tid,
+                                  ptid=ptid, datlastsysoid=self.datlastsysoid)
+            status, res = self.conn.execute_dict(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+        SQL, name = self.get_sql(did, scid, ptid, data, res)
+        SQL = re.sub('\n{2,}', '\n\n', SQL)
+        SQL = SQL.strip('\n')
+        if SQL == '':
+            SQL = "--modified SQL"
+        return make_json_response(
+            data=SQL,
+            status=200
+        )
+
+    @BaseTableView.check_precondition
+    def update(self, gid, sid, did, scid, tid, ptid):
+        """
+        This function will update an existing table object
+
+         Args:
+           gid: Server Group ID
+           sid: Server ID
+           did: Database ID
+           scid: Schema ID
+           tid: Table ID
+           ptid: Partition Table ID
+        """
+        data = request.form if request.form else json.loads(
+            request.data, encoding='utf-8'
+        )
+
+        for k, v in data.items():
+            try:
+                data[k] = json.loads(v, encoding='utf-8')
+            except (ValueError, TypeError, KeyError):
+                data[k] = v
+
+        try:
+            SQL = render_template("/".join([self.partition_template_path,
+                                            'properties.sql']),
+                                  did=did, scid=scid, tid=tid,
+                                  ptid=ptid, datlastsysoid=self.datlastsysoid)
+            status, res = self.conn.execute_dict(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            return super(PartitionsView, self).update(
+                gid, sid, did, scid, ptid, data, res, parent_id=tid)
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @BaseTableView.check_precondition
+    def truncate(self, gid, sid, did, scid, tid, ptid):
+        """
+        This function will truncate the table object
+
+         Args:
+           gid: Server Group ID
+           sid: Server ID
+           did: Database ID
+           scid: Schema ID
+           tid: Table ID
+        """
+
+        try:
+            SQL = render_template("/".join([self.partition_template_path,
+                                            'properties.sql']),
+                                  did=did, scid=scid, tid=tid,
+                                  ptid=ptid, datlastsysoid=self.datlastsysoid)
+            status, res = self.conn.execute_dict(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            return super(PartitionsView, self).truncate(gid, sid, did, scid, ptid, res)
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+
+PartitionsView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/static/img/coll-partition.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/static/img/coll-partition.png
new file mode 100644
index 0000000000000000000000000000000000000000..8536c66afb1099e1898f3c6163a709e3e5375564
GIT binary patch
literal 1433
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk?
zp1FzXsX?iUDV2pMQ*9U+m@6_vB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+*
zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn
zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxOgGuk*h0bFQqR!T
z(!$6@N5ROz&`jUJQs2--*TB%qz|zXVPyq^*fVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj
z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5
zFD<cE0=g99h1>#PU%0_}#n6BP2AO_EVu8M)o`HUDF34YC)x{-2sR(CaRb3oXS&*t9
zlv<o$T9gcok2GbNW?<Oiw+N&V=o%~kqD)|<B<8r}Czs}?=9PH5*eU^K^incYtkTRZ
z5{*oal5`D?ObvBSOcO11lYl_iJk`X)FvY+m)g&nmW)3#}E*6d^PNt4dCN7RHhK8=@
zPHv{oF3yHVmX5}*ZjMd{Fuk66#U+V($*C~AGl6zP^*Z9!Yvo*&npl!w6q28x14{(~
z8Tlpo#Toep3eL`;ST|SD@J&q4%mWE(f+7myZ<o}f%(B!Jx1#)91#n1PWnyuKsfDSj
zMT%iE(96kbx+aOq2D+B!CaJo{7D+&<RLc}|lVl~R{uFXUP#@?heNb{iN<J_lV44Im
z;h6!*foG@GJYXIw0%n%qD-S6%FfeZSba4!+xHV_edF>WQiR1f?-$y8V26cBo;ukpW
z=+RYGK7G-gzl?l(e4Fal=E%q_nKMVmMN`v0tN7NF1u8|8pD6Kdd7GMhZQtXWmE{{H
zj~M%Yu3h%L@|p2@p3iOH%O^{U`B*nhQJ8EZ(aRtEZesQK;D2!vJ}aKMd}lkrZTQQg
z`{=361Cu0b1FGg^aZGV#Z~Ad8;^L7;pIfsXUQ4Z>W&6XJH@wmK<A$;#6VGbt!@9f`
zd~X!4PQM)O@pF5r&plCzt6OJhZ&~cew)}Hc_)V*eYtnvgHhCU#@PT#jqH`AuTGbR{
zPt;#1n0jkl%?q<5F3E)=)jE5%;<F#VTP<N2R`BW@2jh&49jXCYOAW;5vn*!vI)AzD
z*22e6RDT@m_Fwh#+fVJ@yH8vjCpW$QeCU46+gES)bqQ)|nq68v*~i4k>cN&MBj)b5
z)v^{(Ue9nkcz<KD%9p7((xr0Uue<3Uc^kPTn{}bfjkcN3(uMuA7ji~jZRvb=ovE^~
zvCX*c@n-2QzhAv@)j8s~?!wKW{x7SW`<7<ZF$$mc_x*CAJ|Sq9r*@lC@%pX33rpNs
qgTo`g&An@W?f;S|@032N9bhoZ__yrHkA@AP!qU^#&t;ucLK6VNc@r4`

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/static/img/partition.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/static/img/partition.png
new file mode 100644
index 0000000000000000000000000000000000000000..4d6dd8b35ad2da1c150dcfe5be8861c73cef72e6
GIT binary patch
literal 1264
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk?
zp1FzXsX?iUDV2pMQ*9U+m^Cs(B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+*
zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn
zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxOgGuk*h0bFQqR!T
z(!$6@N5ROz&`jUJQs2--*TB%qz|zXVPyq^*fVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj
z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5
zFD<cE0=g99h1>$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;Np<V
zf>iyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7I$DAddqG<*}2
zGxI=#nqXbNzE+-j#U+V($*G<$wn{*A^fEJ3tSsFO-He>fEln(q4Gj%l%`IKbot#Xa
zOx+BPU7XC#++ccL@{>z*Q}aq-dQ%X39dYUfC5YStpv^9+MVV!(DQ-pixe8#9TV>*Q
zi#bm7pn6kqyTt;hUVWfr^g+>!6x}c(U>X83;fWW>fhYgeJYbqH0w(S^3o~jM7#QO`
zT^vIyZmkKn^>#KCu{HNx{X&7O>4DOVM_O7I8cHDoRzI2laO*W);8VKNfAC=aL;ek1
zcSWpru3Wu(^5pJ-TTiMceb3^4bSeGNUX|x}izijT?>;m4-rc&&m1WEVBAFhaPHDba
z;TY+3=vPIIt9|UXW!+ECpW;n8)^U7-UZ35$Lepxdh_;_SW(Trkrmt8$BkOH=tc_%w
z<Fyo~EuEXrep9PGvwrI1KEpksA{{Z3>+iq*c+9A$^on3a-1W{y!a6O7W<9@pyjtsn
z2#3gyjQwu5NAKOLQIZzYdHe7sul4F~k*yr}(wQvdBIBEuXDo=_7XKhAFh*xOo5ZT<
zRnqU)Rz>)&R}{@_DE1e=+VY&U&n}{QnV3+3?velfB3Va+3MT%o*ztRH@9g?5h1Dy1
zqpz?15-*yeeCgog=l<)oZ(e*Iv59G>Wrj%=xAxa{{PyQ}u9v(2rl57{`=Xjpe}BG-
bZeV0EdB614s(jTpP#NUu>gTe~DWM4f22aJ%

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/templates/partition/js/partition.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/templates/partition/js/partition.js
new file mode 100644
index 0000000..aeeb236
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/templates/partition/js/partition.js
@@ -0,0 +1,1272 @@
+define(
+        ['sources/gettext', 'jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser',
+        'backform', 'alertify', 'pgadmin.browser.collection'],
+function(gettext, $, _, S, pgAdmin, pgBrowser, Backform, alertify) {
+
+  if (!pgBrowser.Nodes['coll-partition']) {
+    var databases = pgAdmin.Browser.Nodes['coll-partition'] =
+      pgAdmin.Browser.Collection.extend({
+        node: 'partition',
+        label: gettext('Partitions'),
+        getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
+        type: 'coll-partition',
+        columns: ['name', 'schema', 'partition_value', 'is_partitioned', 'description'],
+        hasStatistics: true
+      });
+  };
+
+  if (!pgBrowser.Nodes['partition']) {
+    pgAdmin.Browser.Nodes['partition'] = pgBrowser.Node.extend({
+      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
+      parent_type: 'table',
+      collection_type: 'coll-partition',
+      type: 'partition',
+      label: gettext('Partition'),
+      hasSQL: true,
+      hasDepends: true,
+      hasStatistics: true,
+      statsPrettifyFields: ['Size', 'Indexes size', 'Table size',
+                            'Toast table size', 'Tuple length',
+                            'Dead tuple length', 'Free space'],
+      sqlAlterHelp: 'sql-altertable.html',
+      sqlCreateHelp: 'sql-createtable.html',
+      dialogHelp: '{{ url_for('help.static', filename='table_dialog.html') }}',
+      hasScriptTypes: ['create'],
+      height: '95%',
+      width: '85%',
+      Init: function() {
+        /* Avoid mulitple registration of menus */
+        if (this.initialized)
+            return;
+
+        this.initialized = true;
+
+        pgBrowser.add_menus([{
+          name: 'truncate_table', node: 'partition', module: this,
+          applies: ['object', 'context'], callback: 'truncate_table',
+          category: 'Truncate', priority: 3, label: gettext('Truncate'),
+          icon: 'fa fa-eraser', enable : 'canCreate'
+        },{
+          name: 'truncate_table_cascade', node: 'partition', module: this,
+          applies: ['object', 'context'], callback: 'truncate_table_cascade',
+          category: 'Truncate', priority: 3, label: gettext('Truncate Cascade'),
+          icon: 'fa fa-eraser', enable : 'canCreate'
+        },{
+          // To enable/disable all triggers for the table
+          name: 'enable_all_triggers', node: 'partition', module: this,
+          applies: ['object', 'context'], callback: 'enable_triggers_on_table',
+          category: 'Trigger(s)', priority: 4, label: gettext('Enable All'),
+          icon: 'fa fa-check', enable : 'canCreate_with_trigger_enable'
+        },{
+          name: 'disable_all_triggers', node: 'partition', module: this,
+          applies: ['object', 'context'], callback: 'disable_triggers_on_table',
+          category: 'Trigger(s)', priority: 4, label: gettext('Disable All'),
+          icon: 'fa fa-times', enable : 'canCreate_with_trigger_disable'
+        },{
+          name: 'reset_table_stats', node: 'partition', module: this,
+          applies: ['object', 'context'], callback: 'reset_table_stats',
+          category: 'Reset', priority: 4, label: gettext('Reset Statistics'),
+          icon: 'fa fa-bar-chart', enable : 'canCreate'
+        },{
+          name: 'detach_partition', node: 'partition', module: this,
+          applies: ['object', 'context'], callback: 'detach_partition',
+          priority: 2, label: gettext('Detach Partition'),
+          icon: 'fa fa-remove'
+        }
+        ]);
+      },
+      getTreeNodeHierarchy: function(i) {
+        var idx = 0,
+            res = {},
+            t = pgBrowser.tree;
+
+        do {
+          d = t.itemData(i);
+          if (
+            d._type in pgBrowser.Nodes && pgBrowser.Nodes[d._type].hasId
+          ) {
+            if (d._type == 'partition' && 'partition' in res) {
+              if (!('table' in res)) {
+                res['table'] = _.extend({}, d, {'priority': idx});
+                idx -= 1;
+              }
+            } else if (d._type == 'table') {
+              if (!('table' in res)) {
+                res['table'] = _.extend({}, d, {'priority': idx});
+                idx -= 1;
+              }
+            } else {
+              res[d._type] = _.extend({}, d, {'priority': idx});
+              idx -= 1;
+            }
+          }
+          i = t.hasParent(i) ? t.parent(i) : null;
+        } while (i);
+
+        return res;
+      },
+      generate_url: function(item, type, d, with_id, info) {
+        if (_.indexOf([
+            'stats', 'statistics', 'dependency', 'dependent', 'reset',
+            'get_relations', 'get_oftype', 'get_attach_tables'
+            ], type) == -1) {
+          return pgBrowser.Node.generate_url.apply(this, arguments);
+        }
+
+        if (type == 'statistics') {
+          type = 'stats';
+        }
+
+        info = (_.isUndefined(item) || _.isNull(item)) ?
+          info || {} : this.getTreeNodeHierarchy(item);
+
+        return S('table/%s/%s/%s/%s/%s/%s').sprintf(
+            encodeURIComponent(type), encodeURIComponent(info['server-group']._id),
+            encodeURIComponent(info['server']._id),
+            encodeURIComponent(info['database']._id),
+            encodeURIComponent(info['partition'].schema_id),
+            encodeURIComponent(info['partition']._id)
+            ).value();
+      },
+      canDrop: pgBrowser.Nodes['schema'].canChildDrop,
+      canDropCascade: pgBrowser.Nodes['schema'].canChildDrop,
+      callbacks: {
+        /* Enable trigger(s) on table */
+        enable_triggers_on_table: function(args) {
+            var params = {'enable': true };
+            this.callbacks.set_triggers.apply(this, [args, params]);
+        },
+        /* Disable trigger(s) on table */
+        disable_triggers_on_table: function(args) {
+            var params = {'enable': false };
+            this.callbacks.set_triggers.apply(this, [args, params]);
+        },
+        set_triggers: function(args, params) {
+          // This function will send request to enable or
+          // disable triggers on table level
+          var input = args || {};
+          obj = this,
+          t = pgBrowser.tree,
+          i = input.item || t.selected(),
+          d = i && i.length == 1 ? t.itemData(i) : undefined;
+          if (!d)
+            return false;
+
+          $.ajax({
+            url: obj.generate_url(i, 'set_trigger' , d, true),
+            type:'PUT',
+            data: params,
+            dataType: "json",
+            success: function(res) {
+              if (res.success == 1) {
+                alertify.success("{{ _('" + res.info + "') }}");
+                t.unload(i);
+                t.setInode(i);
+                t.deselect(i);
+                setTimeout(function() {
+                  t.select(i);
+                }, 10);
+              }
+            },
+            error: function(xhr, status, error) {
+              try {
+                var err = $.parseJSON(xhr.responseText);
+                if (err.success == 0) {
+                  alertify.error(err.errormsg);
+                }
+              } catch (e) {}
+              t.unload(i);
+            }
+          });
+        },
+        /* Truncate table */
+        truncate_table: function(args) {
+            var params = {'cascade': false };
+            this.callbacks.truncate.apply(this, [args, params]);
+        },
+        /* Truncate table with cascade */
+        truncate_table_cascade: function(args) {
+            var params = {'cascade': true };
+            this.callbacks.truncate.apply(this, [args, params]);
+        },
+        truncate: function(args, params) {
+          var input = args || {};
+          obj = this,
+          t = pgBrowser.tree,
+          i = input.item || t.selected(),
+          d = i && i.length == 1 ? t.itemData(i) : undefined;
+
+          if (!d)
+            return false;
+
+          alertify.confirm(
+            gettext('Truncate Table'),
+            S(gettext('Are you sure you want to truncate table %s?')).sprintf(d.label).value(),
+            function (e) {
+            if (e) {
+              var data = d;
+              $.ajax({
+                url: obj.generate_url(i, 'truncate' , d, true),
+                type:'PUT',
+                data: params,
+                dataType: "json",
+                success: function(res) {
+                  if (res.success == 1) {
+                    alertify.success("{{ _('" + res.info + "') }}");
+                    t.removeIcon(i);
+                    data.icon = 'icon-table';
+                    t.addIcon(i, {icon: data.icon});
+                    t.unload(i);
+                    t.setInode(i);
+                    t.deselect(i);
+                    // Fetch updated data from server
+                    setTimeout(function() {
+                      t.select(i);
+                    }, 10);
+                  }
+                },
+                error: function(xhr, status, error) {
+                  try {
+                    var err = $.parseJSON(xhr.responseText);
+                    if (err.success == 0) {
+                      alertify.error(err.errormsg);
+                    }
+                  } catch (e) {}
+                  t.unload(i);
+                }
+              });
+            }},
+            function() {}
+          );
+        },
+        reset_table_stats: function(args) {
+          var input = args || {},
+            obj = this,
+            t = pgBrowser.tree,
+            i = input.item || t.selected(),
+            d = i && i.length == 1 ? t.itemData(i) : undefined;
+
+          if (!d)
+            return false;
+
+          alertify.confirm(
+            gettext('Reset statistics'),
+            S(gettext('Are you sure you want to reset the statistics for table %s?')).sprintf(d._label).value(),
+            function (e) {
+              if (e) {
+                var data = d;
+                $.ajax({
+                  url: obj.generate_url(i, 'reset' , d, true),
+                  type:'DELETE',
+                  success: function(res) {
+                    if (res.success == 1) {
+                      alertify.success("{{ _('" + res.info + "') }}");
+                      t.removeIcon(i);
+                      data.icon = 'icon-table';
+                      t.addIcon(i, {icon: data.icon});
+                      t.unload(i);
+                      t.setInode(i);
+                      t.deselect(i);
+                      // Fetch updated data from server
+                      setTimeout(function() {
+                        t.select(i);
+                      }, 10);
+                    }
+                  },
+                  error: function(xhr, status, error) {
+                    try {
+                      var err = $.parseJSON(xhr.responseText);
+                      if (err.success == 0) {
+                        alertify.error(err.errormsg);
+                      }
+                    } catch (e) {}
+                    t.unload(i);
+                  }
+                });
+              }
+            },
+            function() {}
+          );
+        },
+        detach_partition: function(args) {
+          var input = args || {},
+            obj = this,
+            t = pgBrowser.tree,
+            i = input.item || t.selected(),
+            d = i && i.length == 1 ? t.itemData(i) : undefined;
+
+          if (!d)
+            return false;
+
+          alertify.confirm(
+            gettext('Detach Partition'),
+            S(gettext('Are you sure you want to detach the partition %s?')).sprintf(d._label).value(),
+            function (e) {
+              if (e) {
+                var data = d;
+                $.ajax({
+                  url: obj.generate_url(i, 'detach' , d, true),
+                  type:'PUT',
+                  success: function(res) {
+                    if (res.success == 1) {
+                      alertify.success("{{ _('" + res.info + "') }}");
+                      var n = t.next(i);
+                      if (!n || !n.length) {
+                        n = t.prev(i);
+                        if (!n || !n.length) {
+                          n = t.parent(i);
+                          t.setInode(n, true);
+                        }
+                      }
+                      t.remove(i);
+                      if (n.length) {
+                        t.select(n);
+                      }
+                    }
+                  },
+                  error: function(xhr, status, error) {
+                    try {
+                      var err = $.parseJSON(xhr.responseText);
+                      if (err.success == 0) {
+                        alertify.error(err.errormsg);
+                      }
+                    } catch (e) {}
+                  }
+                });
+              }
+            },
+            function() {}
+          );
+       }
+      },
+      model: pgBrowser.Node.Model.extend({
+        defaults: {
+          name: undefined,
+          oid: undefined,
+          spcoid: undefined,
+          spcname: undefined,
+          relowner: undefined,
+          relacl: undefined,
+          relhasoids: undefined,
+          relhassubclass: undefined,
+          reltuples: undefined,
+          description: undefined,
+          conname: undefined,
+          conkey: undefined,
+          isrepl: undefined,
+          triggercount: undefined,
+          relpersistence: undefined,
+          fillfactor: undefined,
+          reloftype: undefined,
+          typname: undefined,
+          labels: undefined,
+          providers: undefined,
+          is_sys_table: undefined,
+          coll_inherits: [],
+          hastoasttable: true,
+          toast_autovacuum_enabled: false,
+          autovacuum_enabled: false,
+          primary_key: [],
+          partitions: [],
+          partition_type: 'range',
+          is_partitioned: false,
+          partition_value: undefined
+        },
+        // Default values!
+        initialize: function(attrs, args) {
+          var self = this;
+
+          if (_.size(attrs) === 0) {
+            var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user,
+                schemaInfo = args.node_info.schema;
+
+            this.set({
+              'relowner': userInfo.name, 'schema': schemaInfo._label
+            }, {silent: true});
+          }
+          pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
+
+        },
+        schema: [{
+          id: 'name', label: gettext('Name'), type: 'text',
+          mode: ['properties', 'create', 'edit'], disabled: 'inSchema'
+        },{
+          id: 'oid', label: gettext('OID'), type: 'text', mode: ['properties']
+        },{
+          id: 'relowner', label: gettext('Owner'), type: 'text', node: 'role',
+          mode: ['properties', 'create', 'edit'], select2: {allowClear: false},
+          disabled: 'inSchema', control: 'node-list-by-name'
+        },{
+          id: 'schema', label: gettext('Schema'), type: 'text', node: 'schema',
+          control: 'node-list-by-name', mode: ['create', 'edit', 'properties'],
+          disabled: 'inSchema', filter: function(d) {
+            // If schema name start with pg_* then we need to exclude them
+            if(d && d.label.match(/^pg_/))
+            {
+              return false;
+            }
+            return true;
+          }, cache_node: 'database', cache_level: 'database'
+        },{
+          id: 'spcname', label: gettext('Tablespace'), node: 'tablespace',
+          type: 'text', control: 'node-list-by-name', disabled: 'inSchema',
+          mode: ['properties', 'create', 'edit'],
+          filter: function(d) {
+            // If tablespace name is not "pg_global" then we need to exclude them
+            return (!(d && d.label.match(/pg_global/)))
+          }
+        },{
+          id: 'partition', type: 'group', label: gettext('Partition'),
+          mode: ['edit', 'create'], min_version: 100000,
+          visible: function(m) {
+            // Always show in case of create mode
+            if (m.isNew() || m.get('is_partitioned'))
+              return true;
+            return false;
+          }
+        },{
+          id: 'is_partitioned', label:gettext('Partitioned Table?'), cell: 'switch',
+          type: 'switch', mode: ['properties', 'create', 'edit'],
+          visible: function(m) {
+            if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
+              && !_.isUndefined(m.node_info.server.version) &&
+              m.node_info.server.version >= 100000)
+              return true;
+
+            return false;
+          },
+          disabled: function(m) {
+            if (!m.isNew())
+              return true;
+            return false;
+          }
+        },{
+          id: 'description', label: gettext('Comment'), type: 'multiline',
+          mode: ['properties', 'create', 'edit'], disabled: 'inSchema'
+        },
+        {
+          id: 'partition_value', label:gettext('Partition Scheme'),
+          type: 'text', visible: false
+        },{
+          id: 'coll_inherits', label: gettext('Inherited from table(s)'),
+          type: 'text', group: gettext('Advanced'), mode: ['properties']
+        },{
+          id: 'Columns', type: 'group', label: gettext('Columns'),
+          mode: ['edit', 'create'], min_version: 100000,
+          visible: function(m) {
+            // Always hide in case of partition table.
+            return false;
+          }
+        },{
+          // Tab control for columns
+          id: 'columns', label: gettext('Columns'), type: 'collection',
+          group: gettext('Columns'),
+          model: pgBrowser.Nodes['column'].model,
+          subnode: pgBrowser.Nodes['column'].model,
+          mode: ['create', 'edit'],
+          disabled: function(m) {
+            // In case of partitioned table remove inherited columns
+            if (m.isNew() && m.get('is_partitioned')) {
+              setTimeout(function() {
+                var coll = m.get('columns');
+                coll.remove(coll.filter(function(model) {
+                  if (_.isUndefined(model.get('inheritedfrom')))
+                    return false;
+                  return true;
+                }));
+              }, 10);
+            }
+
+            if(this.node_info &&  'catalog' in this.node_info)
+            {
+              return true;
+            }
+            return false;
+          },
+          deps: ['typname', 'is_partitioned'],
+          canAdd: 'check_grid_add_condition',
+          canEdit: true, canDelete: true,
+          // For each row edit/delete button enable/disable
+          canEditRow: 'check_grid_row_edit_delete',
+          canDeleteRow: 'check_grid_row_edit_delete',
+          uniqueCol : ['name'],
+          columns : ['name' , 'cltype', 'attlen', 'attprecision', 'attnotnull', 'is_primary_key'],
+          control: Backform.UniqueColCollectionControl.extend({
+            initialize: function() {
+              Backform.UniqueColCollectionControl.prototype.initialize.apply(this, arguments);
+              var self = this,
+                  collection = self.model.get(self.field.get('name'));
+
+              collection.on("change:is_primary_key", function(m) {
+                var primary_key_coll = self.model.get('primary_key'),
+                    column_name = m.get('name'),
+                    primary_key;
+
+                if(m.get('is_primary_key')) {
+                // Add column to primary key.
+                  if (primary_key_coll.length < 1) {
+                    primary_key = new (primary_key_coll.model)({}, {
+                      top: self.model,
+                      collection: primary_key_coll,
+                      handler: primary_key_coll
+                    });
+                    primary_key_coll.add(primary_key);
+                  } else {
+                    primary_key = primary_key_coll.first();
+                  }
+                  // Do not alter existing primary key columns.
+                  if (_.isUndefined(primary_key.get('oid'))) {
+                    var primary_key_column_coll = primary_key.get('columns'),
+                      primary_key_column_exist = primary_key_column_coll.where({column:column_name});
+
+                    if (primary_key_column_exist.length == 0) {
+                      var primary_key_column = new (primary_key_column_coll.model)(
+                          {column: column_name}, { silent: true,
+                          top: self.model,
+                          collection: primary_key_coll,
+                          handler: primary_key_coll
+                        });
+
+                      primary_key_column_coll.add(primary_key_column);
+                    }
+
+                    primary_key_column_coll.trigger('pgadmin:multicolumn:updated', primary_key_column_coll);
+                  }
+
+                } else {
+                // remove column from primary key.
+                  if (primary_key_coll.length > 0) {
+                    var primary_key = primary_key_coll.first();
+                    // Do not alter existing primary key columns.
+                    if (!_.isUndefined(primary_key.get('oid'))) {
+                      return;
+                    }
+
+                    var  primary_key_column_coll = primary_key.get('columns'),
+                        removedCols = primary_key_column_coll.where({column:column_name});
+                    if (removedCols.length > 0) {
+                      primary_key_column_coll.remove(removedCols);
+                      _.each(removedCols, function(m) {
+                        m.destroy();
+                      })
+                      if (primary_key_column_coll.length == 0) {
+                        setTimeout(function () {
+                          // There will be only on primary key so remove the first one.
+                          primary_key_coll.remove(primary_key_coll.first());
+                          /* Ideally above line of code should be "primary_key_coll.reset()".
+                           * But our custom DataCollection (extended from Backbone collection in datamodel.js)
+                           * does not respond to reset event, it only supports add, remove, change events.
+                           * And hence no custom event listeners/validators get called for reset event.
+                           */
+                        }, 10);
+                      }
+                    }
+                    primary_key_column_coll.trigger('pgadmin:multicolumn:updated', primary_key_column_coll);
+                  }
+                }
+              })
+            },
+            remove: function() {
+              var collection = this.model.get(this.field.get('name'));
+              if (collection) {
+                collection.off("change:is_primary_key");
+              }
+
+              Backform.UniqueColCollectionControl.prototype.remove.apply(this, arguments);
+            }
+          }),
+          allowMultipleEmptyRow: false
+        },{
+          id: 'inherited_tables_cnt', label: gettext('Inherited tables count'),
+          type: 'text', mode: ['properties'], group: gettext('Advanced'),
+          disabled: 'inSchema'
+        },{
+          // Here we will create tab control for constraints
+          type: 'nested', control: 'tab', group: gettext('Constraints'),
+          mode: ['edit', 'create'],
+          schema: [{
+              id: 'primary_key', label: gettext('Primary key'),
+              model: pgBrowser.Nodes['primary_key'].model,
+              subnode: pgBrowser.Nodes['primary_key'].model,
+              editable: false, type: 'collection',
+              group: gettext('Primary Key'), mode: ['edit', 'create'],
+              canEdit: true, canDelete: true, deps:['is_partitioned'],
+              control: 'unique-col-collection',
+              columns : ['name', 'columns'],
+              canAdd: function(m) {
+                if (m.get('is_partitioned')) {
+                  setTimeout(function() {
+                    var coll = m.get('primary_key');
+                    coll.remove(coll.filter(function(model) {
+                      return true;
+                    }));
+                  }, 10);
+                  return false;
+                }
+
+                return true;
+              },
+              canAddRow: function(m) {
+               // User can only add one primary key
+               var columns = m.get('columns');
+
+               return (m.get('primary_key') &&
+                        m.get('primary_key').length < 1 &&
+                        _.some(columns.pluck('name')));
+              }
+            },{
+              id: 'foreign_key', label: gettext('Foreign key'),
+              model: pgBrowser.Nodes['foreign_key'].model,
+              subnode: pgBrowser.Nodes['foreign_key'].model,
+              editable: false, type: 'collection',
+              group: gettext('Foreign Key'), mode: ['edit', 'create'],
+              canEdit: true, canDelete: true, deps:['is_partitioned'],
+              control: 'unique-col-collection',
+              canAdd: function(m) {
+                if (m.get('is_partitioned')) {
+                  setTimeout(function() {
+                    var coll = m.get('foreign_key');
+                    coll.remove(coll.filter(function(model) {
+                      return true;
+                    }));
+                  }, 10);
+                  return false;
+                }
+
+                return true;
+              },
+              columns : ['name', 'columns'],
+              canAddRow: function(m) {
+               // User can only add if there is at least one column with name.
+               var columns = m.get('columns');
+               return _.some(columns.pluck('name'));
+              }
+            },{
+              id: 'check_constraint', label: gettext('Check constraint'),
+              model: pgBrowser.Nodes['check_constraints'].model,
+              subnode: pgBrowser.Nodes['check_constraints'].model,
+              editable: false, type: 'collection',
+              group: gettext('Check'), mode: ['edit', 'create'],
+              canEdit: true, canDelete: true, deps:['is_partitioned'],
+              control: 'unique-col-collection',
+              canAdd: true,
+              columns : ['name', 'consrc']
+            },{
+              id: 'unique_constraint', label: gettext('Unique Constraint'),
+              model: pgBrowser.Nodes['unique_constraint'].model,
+              subnode: pgBrowser.Nodes['unique_constraint'].model,
+              editable: false, type: 'collection',
+              group: gettext('Unique'), mode: ['edit', 'create'],
+              canEdit: true, canDelete: true, deps:['is_partitioned'],
+              control: 'unique-col-collection',
+              columns : ['name', 'columns'],
+              canAdd: function(m) {
+                if (m.get('is_partitioned')) {
+                  setTimeout(function() {
+                    var coll = m.get('unique_constraint');
+                    coll.remove(coll.filter(function(model) {
+                      return true;
+                    }));
+                  }, 10);
+                  return false;
+                }
+
+                return true;
+              },
+              canAddRow: function(m) {
+               // User can only add if there is at least one column with name.
+               var columns = m.get('columns');
+               return _.some(columns.pluck('name'));
+              }
+            },{
+              id: 'exclude_constraint', label: gettext('Exclude constraint'),
+              model: pgBrowser.Nodes['exclusion_constraint'].model,
+              subnode: pgBrowser.Nodes['exclusion_constraint'].model,
+              editable: false, type: 'collection',
+              group: gettext('Exclude'), mode: ['edit', 'create'],
+              canEdit: true, canDelete: true, deps:['is_partitioned'],
+              control: 'unique-col-collection',
+              columns : ['name', 'columns', 'constraint'],
+              canAdd: function(m) {
+                if (m.get('is_partitioned')) {
+                  setTimeout(function() {
+                    var coll = m.get('exclude_constraint');
+                    coll.remove(coll.filter(function(model) {
+                      return true;
+                    }));
+                  }, 10);
+                  return false;
+                }
+
+                return true;
+              },
+              canAddRow: function(m) {
+               // User can only add if there is at least one column with name.
+               var columns = m.get('columns');
+               return _.some(columns.pluck('name'));
+              }
+          }]
+        },{
+          id: 'typname', label: gettext('Of type'), type: 'text',
+          control: 'node-ajax-options', mode: ['properties', 'create', 'edit'],
+          disabled: 'checkOfType', url: 'get_oftype', group: gettext('Advanced'),
+          deps: ['coll_inherits', 'is_partitioned'], transform: function(data, cell) {
+            var control = cell || this,
+              m = control.model;
+              m.of_types_tables = data;
+              return data;
+          },
+          control: Backform.NodeAjaxOptionsControl.extend({
+              // When of_types changes we need to clear columns collection
+              onChange: function() {
+                Backform.NodeAjaxOptionsControl.prototype.onChange.apply(this, arguments);
+                var self = this,
+                  tbl_oid = undefined,
+                  tbl_name = self.model.get('typname'),
+                  data = undefined,
+                  arg = undefined,
+                  column_collection = self.model.get('columns');
+
+                if (!_.isUndefined(tbl_name) &&
+                    tbl_name !== '' && column_collection.length !== 0) {
+                  var msg = gettext('Changing of type table will clear columns collection');
+                  alertify.confirm(msg, function (e) {
+                    if (e) {
+                      // User clicks Ok, lets clear columns collection
+                      column_collection.reset();
+                    } else {
+                      return this;
+                    }
+                  });
+                } else if (!_.isUndefined(tbl_name) && tbl_name === '') {
+                  column_collection.reset();
+                }
+
+                // Run Ajax now to fetch columns
+                if (!_.isUndefined(tbl_name) && tbl_name !== '') {
+                  arg = { 'tname': tbl_name }
+                  data = self.model.fetch_columns_ajax.apply(self, [arg]);
+                  // Add into column collection
+                  column_collection.set(data, { merge:false,remove:false });
+                }
+              }
+            })
+        },{
+          id: 'fillfactor', label: gettext('Fill factor'), type: 'int',
+          mode: ['create', 'edit'], min: 10, max: 100,
+          disabled: 'inSchema',group: gettext('Advanced')
+        },{
+          id: 'relhasoids', label: gettext('Has OIDs?'), cell: 'switch',
+          type: 'switch', mode: ['properties', 'create', 'edit'],
+          disabled: 'inSchema', group: gettext('Advanced')
+        },{
+          id: 'relpersistence', label: gettext('Unlogged?'), cell: 'switch',
+          type: 'switch', mode: ['properties', 'create', 'edit'],
+          disabled: 'inSchemaWithModelCheck',
+          group: gettext('Advanced')
+        },{
+          id: 'conname', label: gettext('Primary key'), cell: 'string',
+          type: 'text', mode: ['properties'], group: gettext('Advanced'),
+          disabled: 'inSchema'
+        },{
+          id: 'reltuples', label: gettext('Rows (estimated)'), cell: 'string',
+          type: 'text', mode: ['properties'], group: gettext('Advanced'),
+          disabled: 'inSchema'
+        },{
+          id: 'rows_cnt', label: gettext('Rows (counted)'), cell: 'string',
+          type: 'text', mode: ['properties'], group: gettext('Advanced'),
+          disabled: 'inSchema'
+        },{
+          id: 'relhassubclass', label: gettext('Inherits tables?'), cell: 'switch',
+          type: 'switch', mode: ['properties'], group: gettext('Advanced'),
+          disabled: 'inSchema'
+        },{
+          id: 'is_sys_table', label: gettext('System table?'), cell: 'switch',
+          type: 'switch', mode: ['properties'],
+          disabled: 'inSchema'
+        },{
+          type: 'nested', control: 'fieldset', label: gettext('Like'),
+          group: gettext('Advanced'),
+          schema:[{
+            id: 'like_relation', label: gettext('Relation'),
+            type: 'text', mode: ['create', 'edit'], deps: ['typname'],
+            control: 'node-ajax-options', url: 'get_relations',
+            disabled: 'isLikeDisable', group: gettext('Like')
+          },{
+            id: 'like_default_value', label: gettext('With default values?'),
+            type: 'switch', mode: ['create', 'edit'], deps: ['typname'],
+            disabled: 'isLikeDisable', group: gettext('Like')
+          },{
+            id: 'like_constraints', label: gettext('With constraints?'),
+            type: 'switch', mode: ['create', 'edit'], deps: ['typname'],
+            disabled: 'isLikeDisable', group: gettext('Like')
+          },{
+            id: 'like_indexes', label: gettext('With indexes?'),
+            type: 'switch', mode: ['create', 'edit'], deps: ['typname'],
+            disabled: 'isLikeDisable', group: gettext('Like')
+          },{
+            id: 'like_storage', label: gettext('With storage?'),
+            type: 'switch', mode: ['create', 'edit'], deps: ['typname'],
+            disabled: 'isLikeDisable', group: gettext('Like')
+          },{
+            id: 'like_comments', label: gettext('With comments?'),
+            type: 'switch', mode: ['create', 'edit'], deps: ['typname'],
+            disabled: 'isLikeDisable', group: gettext('Like')
+          }]
+        },{
+          id: 'partition_type', label:gettext('Partition Type'),
+          editable: false, type: 'select2', select2: {allowClear: false},
+          group: 'partition', deps: ['is_partitioned'],
+          options:[{
+            label: 'Range', value: 'range'
+          },{
+            label: 'List', value: 'list'
+          }],
+          mode:['create'],
+          visible: function(m) {
+            if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
+              && !_.isUndefined(m.node_info.server.version) &&
+              m.node_info.server.version >= 100000)
+              return true;
+
+            return false;
+          },
+          disabled: function(m) {
+            if (!m.isNew() || !m.get('is_partitioned'))
+              return true;
+            return false;
+          }
+        },{
+          id: 'partition_keys', label:gettext('Partition Keys'),
+          model: Backform.PartitionKeyModel,
+          subnode: Backform.PartitionKeyModel,
+          editable: true, type: 'collection',
+          group: 'partition', mode: ['create'],
+          deps: ['is_partitioned', 'partition_type'],
+          canEdit: false, canDelete: true,
+          control: 'sub-node-collection',
+          canAdd: function(m) {
+            if (m.isNew() && m.get('is_partitioned'))
+              return true;
+            return false;
+          },
+          canAddRow: function(m) {
+            var columns = m.get('columns');
+            var max_row_count = 1000;
+
+            if (m.get('partition_type') && m.get('partition_type') == 'list')
+              max_row_count = 1;
+
+            return (m.get('partition_keys') &&
+                m.get('partition_keys').length < max_row_count &&
+                _.some(columns.pluck('name'))
+            );
+          },
+          visible: function(m) {
+            if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
+              && !_.isUndefined(m.node_info.server.version) &&
+              m.node_info.server.version >= 100000)
+              return true;
+
+            return false;
+          },
+          disabled: function(m) {
+            if (m.get('partition_keys') && m.get('partition_keys').models.length > 0) {
+              setTimeout(function () {
+                var coll = m.get('partition_keys');
+                coll.remove(coll.filter(function(model) {
+                  return true;
+                }));
+              }, 10);
+            }
+          }
+        },{
+          id: 'partition_scheme', label: gettext('Partition Scheme'),
+          type: 'note', group: 'partition', mode: ['edit'],
+          visible: function(m) {
+            if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
+              && !_.isUndefined(m.node_info.server.version) &&
+              m.node_info.server.version >= 100000)
+              return true;
+
+            return false;
+          },
+          disabled: function(m) {
+            if (!m.isNew()) {
+              this.text = m.get('partition_scheme');
+            }
+          }
+        },{
+          id: 'partitions', label:gettext('Partitions'),
+          model: Backform.PartitionsModel,
+          subnode: Backform.PartitionsModel,
+          editable: true, type: 'collection',
+          group: 'partition', mode: ['edit', 'create'],
+          deps: ['is_partitioned', 'partition_type'],
+          canEdit: false, canDelete: true,
+          customDeleteTitle: gettext('Detach Partition'),
+          customDeleteMsg: gettext('Are you sure you wish to detach this partition?'),
+          columns:['is_attach', 'partition_name', 'values_from', 'values_to', 'values_in'],
+          control: Backform.SubNodeCollectionControl.extend({
+            row: Backgrid.PartitionRow,
+            initialize: function() {
+              Backform.SubNodeCollectionControl.prototype.initialize.apply(this, arguments);
+              var self = this;
+              if (!this.model.isNew()) {
+                var node = this.field.get('schema_node'),
+                    node_info = this.field.get('node_info');
+
+                // Make ajax call to get the tables to be attached
+                $.ajax({
+                  url: node.generate_url.apply(
+                      node, [
+                        null, 'get_attach_tables', this.field.get('node_data'),
+                        true, node_info
+                      ]),
+
+                  type: 'GET',
+                  async: false,
+                  success: function(res) {
+                    if (res.success == 1) {
+                      self.model.table_options = res.data;
+                    }
+                    else {
+                      alertify.alert(
+                        'Error fetching tables to be attached', res.data.result
+                      );
+                    }
+                  },
+                  error: function(e) {
+                    var errmsg = $.parseJSON(e.responseText);
+                    alertify.alert('Error fetching tables to be attached.', errmsg.errormsg);
+                  }
+                });
+              }
+            }
+          }
+          ),
+          canAdd: function(m) {
+            if (m.get('is_partitioned'))
+              return true;
+            return false;
+          },
+          visible: function(m) {
+            if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
+              && !_.isUndefined(m.node_info.server.version) &&
+              m.node_info.server.version >= 100000)
+              return true;
+
+            return false;
+          },
+          disabled: function(m) {
+            if (m.isNew() && m.get('partitions') && m.get('partitions').models.length > 0) {
+              setTimeout(function () {
+                var coll = m.get('partitions');
+                coll.remove(coll.filter(function(model) {
+                  return true;
+                }));
+              }, 10);
+            }
+          }
+        },{
+          id: 'partition_note', label: gettext('Partition'),
+          type: 'note', group: 'partition',
+          text: gettext('Above control will be used to Create/Attach/Detach partitions.<br>' +
+            '<ul><li>Create Mode: User will be able to create N number of partitions. Mode switch control is disabled in this scenario.</li>' +
+            '<li>Edit Mode: User will be able to create/attach/detach N number of partitions.' +
+            'In attach mode there will be list of suitable tables to be attached.</li></ul>'),
+          visible: function(m) {
+            if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
+              && !_.isUndefined(m.node_info.server.version) &&
+              m.node_info.server.version >= 100000)
+              return true;
+
+            return false;
+          }
+        },{
+          // Here - we will create tab control for storage parameters
+          // (auto vacuum).
+          type: 'nested', control: 'tab', group: gettext('Parameter'),
+          mode: ['edit', 'create'], deps: ['is_partitioned'],
+          schema: Backform.VacuumSettingsSchema
+        },{
+          id: 'relacl_str', label: gettext('Privileges'), disabled: 'inSchema',
+          type: 'text', mode: ['properties'], group: gettext('Security')
+        }, pgBrowser.SecurityGroupUnderSchema,{
+          id: 'relacl', label: gettext('Privileges'), type: 'collection',
+          group: 'security', control: 'unique-col-collection',
+          model: pgBrowser.Node.PrivilegeRoleModel.extend({
+          privileges: ['a','r','w','d','D','x','t']}),
+          mode: ['edit', 'create'], canAdd: true, canDelete: true,
+          uniqueCol : ['grantee']
+        },{
+          id: 'seclabels', label: gettext('Security labels'), canEdit: false,
+          model: pgBrowser.SecLabelModel, editable: false, canAdd: true,
+          type: 'collection', min_version: 90100, mode: ['edit', 'create'],
+          group: 'security', canDelete: true, control: 'unique-col-collection'
+        },{
+          id: 'vacuum_settings_str', label: gettext('Storage settings'),
+          type: 'multiline', group: gettext('Advanced'), mode: ['properties']
+        }],
+        validate: function(keys) {
+          var err = {},
+              changedAttrs = this.changed,
+              msg = undefined,
+              name = this.get('name'),
+              schema = this.get('schema'),
+              relowner = this.get('relowner'),
+              is_partitioned = this.get('is_partitioned'),
+              partition_keys = this.get('partition_keys');
+
+          // If nothing to validate or VacuumSetting keys then
+          // return from here
+          if ( keys && (keys.length == 0
+                        || _.indexOf(keys, 'autovacuum_enabled') != -1
+                        || _.indexOf(keys, 'toast_autovacuum_enabled') != -1) ) {
+            return null;
+          }
+
+          // Have to clear existing validation before initiating current state validation only
+          this.errorModel.clear();
+
+          if (_.isUndefined(name) || _.isNull(name) ||
+            String(name).replace(/^\s+|\s+$/g, '') == '') {
+            msg = gettext('Table name cannot be empty.');
+            this.errorModel.set('name', msg);
+            return msg;
+          } else if (_.isUndefined(schema) || _.isNull(schema) ||
+            String(schema).replace(/^\s+|\s+$/g, '') == '') {
+            msg = gettext('Table schema cannot be empty.');
+            this.errorModel.set('schema', msg);
+            return msg;
+          } else if (_.isUndefined(relowner) || _.isNull(relowner) ||
+            String(relowner).replace(/^\s+|\s+$/g, '') == '') {
+            msg = gettext('Table owner cannot be empty.');
+            this.errorModel.set('relowner', msg);
+            return msg;
+          } else if (is_partitioned && this.isNew() &&
+            !_.isNull(partition_keys) && partition_keys.length <= 0)
+          {
+            msg = gettext('Please specify at least one key for partitioned table.');
+            this.errorModel.set('partition_keys', msg);
+            return msg;
+          }
+          return null;
+        },
+        // We will disable everything if we are under catalog node
+        inSchema: function() {
+          if(this.node_info &&  'catalog' in this.node_info)
+          {
+            return true;
+          }
+          return false;
+        },
+        isInheritedTable: function(m) {
+          if(!m.inSchema.apply(this, [m])) {
+            if(
+              (!_.isUndefined(m.get('coll_inherits')) && m.get('coll_inherits').length != 0)
+                ||
+                  (!_.isUndefined(m.get('typname')) && String(m.get('typname')).replace(/^\s+|\s+$/g, '') !== '')
+            ) {
+              // Either of_types or coll_inherits has value
+              return false;
+            } else {
+              return true;
+            }
+          }
+          return false;
+        },
+        // Oftype is defined?
+        checkInheritance: function(m) {
+          // Disabled if it is partitioned table
+          if (m.get('is_partitioned')) {
+            setTimeout( function() {
+               m.set('coll_inherits', []);
+            }, 10);
+            return true;
+          }
+
+          // coll_inherits || typname
+          if(!m.inSchema.apply(this, [m]) &&
+              ( _.isUndefined(m.get('typname')) ||
+                _.isNull(m.get('typname')) ||
+                String(m.get('typname')).replace(/^\s+|\s+$/g, '') == '')) {
+            return false;
+          }
+          return true;
+        },
+        // We will disable Like if ofType is defined
+        isLikeDisable: function(m) {
+          if(!m.inSchemaWithModelCheck.apply(this, [m]) &&
+              ( _.isUndefined(m.get('typname')) ||
+                _.isNull(m.get('typname')) ||
+                String(m.get('typname')).replace(/^\s+|\s+$/g, '') == '')) {
+            return false;
+          }
+          return true;
+        },
+        // Check for column grid when to Add
+        check_grid_add_condition: function(m) {
+          var enable_flag = true;
+          if(!m.inSchema.apply(this, [m])) {
+            // if of_type then disable add in grid
+            if (!_.isUndefined(m.get('typname')) &&
+                !_.isNull(m.get('typname')) &&
+                m.get('typname') !== '') {
+                    enable_flag = false;
+                }
+          }
+          return enable_flag;
+        },
+        // Check for column grid when to edit/delete (for each row)
+        check_grid_row_edit_delete: function(m) {
+          var flag = true;
+          if(!_.isUndefined(m.get('inheritedfrom')) &&
+               !_.isNull(m.get('inheritedfrom')) &&
+               String(m.get('inheritedfrom')).replace(/^\s+|\s+$/g, '') !== '') {
+            flag = false;
+          }
+          return flag;
+        },
+        // We will disable it if Inheritance is defined
+        checkOfType: function(m) {
+          // Disabled if it is partitioned table
+          if (m.get('is_partitioned')) {
+            setTimeout( function() {
+               m.set('typname', undefined);
+            }, 10);
+            return true;
+          }
+
+          //coll_inherits || typname
+          if(!m.inSchemaWithModelCheck.apply(this, [m]) &&
+              (_.isUndefined(m.get('coll_inherits')) ||
+               _.isNull(m.get('coll_inherits')) ||
+               String(m.get('coll_inherits')).replace(/^\s+|\s+$/g, '') == '')) {
+            return false;
+          }
+          return true;
+        },
+        // We will check if we are under schema node & in 'create' mode
+        inSchemaWithModelCheck: function(m) {
+          if(this.node_info &&  'schema' in this.node_info)
+          {
+            // We will disbale control if it's in 'edit' mode
+            if (m.isNew()) {
+              return false;
+            } else {
+              return true;
+            }
+          }
+          return true;
+        },
+        isTableAutoVacuumEnable: function(m) {
+          // We need to check additional condition to toggle enable/disable
+          // for table auto-vacuum
+          if(!m.inSchema.apply(this, [m]) &&
+              m.get('autovacuum_enabled') === true) {
+            return false;
+          }
+          return true;
+        },
+        isToastTableAutoVacuumEnable: function(m) {
+          // We need to check additional condition to toggle enable/disable
+          // for toast table auto-vacuum
+          if(!m.inSchemaWithModelCheck.apply(this, [m]) &&
+              m.get('toast_autovacuum_enabled') == true) {
+            return false;
+          }
+          return true;
+        },
+        fetch_columns_ajax: function(arg) {
+          var self = this,
+              url = 'get_columns',
+              m = self.model.top || self.model,
+              old_columns = _.clone(m.get('columns'))
+              data = undefined,
+              node = this.field.get('schema_node'),
+              node_info = this.field.get('node_info'),
+              full_url = node.generate_url.apply(
+                node, [
+                  null, url, this.field.get('node_data'),
+                  this.field.get('url_with_id') || false, node_info
+                ]
+              ),
+              cache_level = this.field.get('cache_level') || node.type,
+              cache_node = this.field.get('cache_node');
+
+          cache_node = (cache_node && pgBrowser.Nodes['cache_node']) || node;
+
+          m.trigger('pgadmin:view:fetching', m, self.field);
+          // Fetching Columns data for the selected table.
+          $.ajax({
+            async: false,
+            url: full_url,
+            data: arg,
+            success: function(res) {
+              data = cache_node.cache(url, node_info, cache_level, res.data);
+            },
+            error: function() {
+              m.trigger('pgadmin:view:fetch:error', m, self.field);
+            }
+          });
+          m.trigger('pgadmin:view:fetched', m, self.field);
+          data = (data && data.data) || [];
+          return data;
+        }
+      }),
+      canCreate: function(itemData, item, data) {
+          //If check is false then , we will allow create menu
+          if (data && data.check == false)
+            return true;
+
+          var t = pgBrowser.tree, i = item, d = itemData;
+          // To iterate over tree to check parent node
+          while (i) {
+            // If it is schema then allow user to create table
+            if (_.indexOf(['schema'], d._type) > -1)
+              return true;
+
+            if ('coll-table' == d._type) {
+              //Check if we are not child of catalog
+              prev_i = t.hasParent(i) ? t.parent(i) : null;
+              prev_d = prev_i ? t.itemData(prev_i) : null;
+              if( prev_d._type == 'catalog') {
+                return false;
+              } else {
+                return true;
+              }
+            }
+            i = t.hasParent(i) ? t.parent(i) : null;
+            d = i ? t.itemData(i) : null;
+          }
+          // by default we do not want to allow create menu
+          return true;
+      },
+      // Check to whether table has disable trigger(s)
+      canCreate_with_trigger_enable: function(itemData, item, data) {
+        if(this.canCreate.apply(this, [itemData, item, data])) {
+          // We are here means we can create menu, now let's check condition
+          if(itemData.tigger_count > 0) {
+            return true;
+          } else {
+            return false;
+          }
+        }
+      },
+      // Check to whether table has enable trigger(s)
+      canCreate_with_trigger_disable: function(itemData, item, data) {
+        if(this.canCreate.apply(this, [itemData, item, data])) {
+          // We are here means we can create menu, now let's check condition
+          if(itemData.tigger_count > 0 && itemData.has_enable_triggers > 0) {
+            return true;
+          } else {
+            return false;
+          }
+        }
+      }
+    });
+  }
+
+  return pgBrowser.Nodes['partition'];
+});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/templates/rules/js/rules.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/templates/rules/js/rules.js
index 25fe963..db91af3 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/templates/rules/js/rules.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/templates/rules/js/rules.js
@@ -16,6 +16,7 @@ define('pgadmin.node.rule', [
         node: 'rule',
         label: gettext('Rules'),
         type: 'coll-rule',
+        getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
         columns: ["name", "owner", "comment"]
       });
   }
@@ -33,8 +34,9 @@ define('pgadmin.node.rule', [
     rule option in the context menu
    */
   if (!pgBrowser.Nodes['rule']) {
-    pgAdmin.Browser.Nodes['rule'] = pgAdmin.Browser.Node.extend({
-      parent_type: ['table','view'],
+    pgAdmin.Browser.Nodes['rule'] = pgBrowser.Node.extend({
+      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
+      parent_type: ['table','view', 'partition'],
       type: 'rule',
       sqlAlterHelp: 'sql-alterrule.html',
       sqlCreateHelp: 'sql-createrule.html',
@@ -98,6 +100,12 @@ define('pgadmin.node.rule', [
           category: 'create', priority: 4, label: gettext('Rule...'),
           icon: 'wcTabIcon icon-rule', data: {action: 'create', check: true},
           enable: 'canCreate'
+        },{
+          name: 'create_rule', node: 'partition', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: gettext('Rule...'),
+          icon: 'wcTabIcon icon-rule', data: {action: 'create', check: true},
+          enable: 'canCreate'
         }
         ]);
       },
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/partition.utils.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/partition.utils.js
new file mode 100644
index 0000000..5b1e3c1
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/partition.utils.js
@@ -0,0 +1,323 @@
+define(
+        ['sources/gettext', 'jquery', 'underscore', 'pgadmin.browser',
+        'backform','backgrid', 'pgadmin.browser.collection'],
+function(gettext, $, _, pgBrowser, Backform, Backgrid) {
+
+  Backgrid.PartitionRow = Backgrid.Row.extend({
+    modelDuplicateColor: "lightYellow",
+
+    modelUniqueColor: "#fff",
+
+    initialize: function () {
+      Backgrid.Row.prototype.initialize.apply(this, arguments);
+      var self = this;
+      self.model.on("change:is_attach", function() {
+        setTimeout(function() {
+          self.columns.each(function(col) {
+            if (col.get('name') == 'partition_name') {
+              var idx = self.columns.indexOf(col),
+                cf = col.get("cellFunction"),
+                cell = new (cf.apply(col, [self.model]))({
+                  column: col,
+                  model: self.model
+                }),
+                  oldCell = self.cells[idx];
+                oldCell.remove();
+                self.cells[idx] = cell;
+                self.render();
+            }
+          });
+        }, 10);
+      });
+      self.listenTo(self.model, 'pgadmin-session:model:duplicate', self.modelDuplicate);
+      self.listenTo(self.model, 'pgadmin-session:model:unique', self.modelUnique);
+    },
+    modelDuplicate: function() {
+      $(this.el).removeClass("new");
+      this.el.style.backgroundColor = this.modelDuplicateColor;
+    },
+    modelUnique: function() {
+      this.el.style.backgroundColor = this.modelUniqueColor;
+    }
+  });
+
+  var getPartitionCell = function(model) {
+    var is_attach = model.get("is_attach");
+    if (is_attach) {
+      var options = [];
+      model.set({'partition_name': undefined}, {silent:true});
+      _.each(model.top.table_options, function(t) {
+        options.push([t.label, t.value]);
+      });
+      return Backgrid.Extension.Select2Cell.extend({optionValues: options});
+
+    } else {
+      return Backgrid.StringCell;
+    }
+  };
+
+  Backform.PartitionKeyModel = pgBrowser.Node.Model.extend({
+     defaults: {
+       key_type: 'column',
+       pt_column: undefined,
+       expression: undefined
+     },
+     keys:['pt_column'],
+     schema: [{
+       id: 'key_type', label:'Key type', type:'select2', editable: true,
+       cell:'select2', cellHeaderClasses: 'width_percent_25',
+       select2: {allowClear: false},
+       options:[{
+         label: 'Column', value: 'column'
+       },{
+         label: 'Expression', value: 'expression'
+       }]
+     },{
+       id: 'pt_column', label: gettext('Column'), type:'text',
+       cell: Backgrid.Extension.Select2DepCell.extend({
+         keyPathAccessor: function(obj, path) {
+           var res = obj;
+           if(_.isArray(res)) {
+             return _.map(res, function(o) { return o['pt_column']
+             });
+           }
+           path = path.split('.');
+           for (var i = 0; i < path.length; i++) {
+             if (_.isNull(res)) return null;
+             if (_.isEmpty(path[i])) continue;
+             if (!_.isUndefined(res[path[i]])) res = res[path[i]];
+           }
+           return _.isObject(res) && !_.isArray(res) ? null : res;
+         },
+         initialize: function() {
+         // Here we will decide if we need to call URL
+         // Or fetch the data from parent columns collection
+           var self = this;
+           if(this.model.handler) {
+             Backgrid.Extension.Select2DepCell.prototype.initialize.apply(this, arguments);
+             // Do not listen for any event(s) for existing constraint.
+             if (_.isUndefined(self.model.get('oid'))) {
+               var tableCols = self.model.top.get('columns');
+               self.listenTo(tableCols, 'remove' , self.resetColOptions);
+               self.listenTo(tableCols, 'change:name', self.resetColOptions);
+             }
+
+             self.custom_options();
+           }
+         },
+         resetColOptions: function(m) {
+           var self = this;
+
+           setTimeout(function () {
+             self.custom_options();
+             self.render.apply(self);
+           }, 50);
+         },
+         custom_options: function() {
+           // We will add all the columns entered by user in table model
+           var columns = this.model.top.get('columns'),
+             added_columns_from_tables = [];
+
+           if (columns.length > 0) {
+             _.each(columns.models, function(m) {
+                 var col = m.get('name');
+                 if(!_.isUndefined(col) && !_.isNull(col)) {
+                   added_columns_from_tables.push(
+                     {label: col, value: col, image:'icon-column'}
+                   );
+                 }
+             });
+           }
+           // Set the values in to options so that user can select
+           this.column.set('options', added_columns_from_tables);
+         },
+         remove: function() {
+           if(this.model.handler) {
+             var self = this,
+             tableCols = self.model.top.get('columns');
+             self.stopListening(tableCols, 'remove' , self.resetColOptions);
+             self.stopListening(tableCols, 'change:name' , self.resetColOptions);
+             Backgrid.Extension.Select2DepCell.prototype.remove.apply(this, arguments);
+           }
+         }
+       }),
+       deps: ['key_type'],
+       cellHeaderClasses: 'width_percent_30',
+       transform : function(data){
+         var res = [];
+         if (data && _.isArray(data)) {
+           _.each(data, function(d) {
+             res.push({label: d.label, value: d.label, image:'icon-column'});
+           })
+         }
+         return res;
+       },
+       select2:{allowClear:false},
+       editable: function(m) {
+         if (m.get('key_type') == 'expression') {
+           setTimeout( function() {
+              m.set('pt_column', undefined);
+           }, 10);
+           return false;
+         }
+         return true;
+       }
+     },{
+       id: 'expression', label:'Expression', type:'text',
+       cell:Backgrid.Extension.StringDepCell,
+       cellHeaderClasses: 'width_percent_45',
+       deps: ['key_type'],
+       editable: function(m) {
+         if (m.get('key_type') == 'column') {
+           setTimeout( function() {
+              m.set('expression', undefined);
+           }, 10);
+           return false;
+         }
+         return true;
+       }
+     }
+    ],
+    validate: function(keys) {
+      var col_type = this.get('key_type'),
+         pt_column = this.get('pt_column'),
+         expression = this.get('expression');
+
+      // Have to clear existing validation before initiating current state
+      // validation only
+      this.errorModel.clear();
+
+      if (_.isUndefined(col_type) || _.isNull(col_type) ||
+        String(col_type).replace(/^\s+|\s+$/g, '') == '') {
+        msg = gettext('Partition key type cannot be empty.');
+        this.errorModel.set('key_type', msg);
+        return msg;
+      }
+      else if (col_type == 'column' &&
+        _.isUndefined(pt_column) || _.isNull(pt_column) ||
+        String(pt_column).replace(/^\s+|\s+$/g, '') == '') {
+        msg = gettext('Partition key column cannot be empty.');
+        this.errorModel.set('pt_column', msg);
+        return msg;
+      }
+      else if (col_type == 'expression' &&
+        _.isUndefined(expression) || _.isNull(expression) ||
+        String(expression).replace(/^\s+|\s+$/g, '') == '') {
+        msg = gettext('Partition key expression cannot be empty.');
+        this.errorModel.set('expression', msg);
+        return msg;
+      }
+
+      return null;
+    }
+  });
+
+  Backform.PartitionsModel = pgBrowser.Node.Model.extend({
+    defaults: {
+      oid: undefined,
+      is_attach: false,
+      partition_name: undefined,
+      values_from: undefined,
+      values_to: undefined,
+      values_in: undefined
+    },
+    keys:['partition_name'],
+    schema: [{
+      id: 'oid', label: gettext('OID'), type: 'text'
+    },{
+      id: 'is_attach', label:gettext('Mode'), cell: 'switch',
+      type: 'switch', options: { 'onText': 'Attach', 'offText': 'Create'},
+      cellHeaderClasses: 'width_percent_5',
+      editable: function(m) {
+        if (m instanceof Backbone.Model && m.isNew() && !m.top.isNew())
+          return true;
+        return false;
+      }
+    },{
+      id: 'partition_name', label: gettext('Name'), type: 'text', cell:'string',
+      cellHeaderClasses: 'width_percent_25',
+      editable: function(m) {
+        if (m instanceof Backbone.Model && m.isNew())
+          return true;
+        return false;
+      }, cellFunction: getPartitionCell
+    },{
+      id: 'values_from', label:'From', type:'text',
+      cell:Backgrid.Extension.StringDepCell,
+      cellHeaderClasses: 'width_percent_20',
+      editable: function(m) {
+       if(m.handler && m.handler.top &&
+          m.handler.top.attributes &&
+          m.handler.top.attributes.partition_type == 'range' &&
+          m instanceof Backbone.Model && m.isNew())
+          return true;
+       return false;
+      }
+    },{
+      id: 'values_to', label:'To', type:'text',
+      cell:Backgrid.Extension.StringDepCell,
+      cellHeaderClasses: 'width_percent_20',
+      editable: function(m) {
+        if(m.handler && m.handler.top &&
+          m.handler.top.attributes &&
+          m.handler.top.attributes.partition_type == 'range' &&
+          m instanceof Backbone.Model && m.isNew())
+          return true;
+        return false;
+      }
+    },{
+      id: 'values_in', label:'In', type:'text',
+      cell:Backgrid.Extension.StringDepCell,
+      cellHeaderClasses: 'width_percent_25',
+      editable: function(m) {
+        if(m.handler && m.handler.top &&
+          m.handler.top.attributes &&
+          m.handler.top.attributes.partition_type == 'list' &&
+          m instanceof Backbone.Model && m.isNew())
+          return true;
+        return false;
+      }
+    }],
+    validate: function(keys) {
+      var partition_name = this.get('partition_name'),
+         values_from = this.get('values_from'),
+         values_to = this.get('values_to'),
+         values_in = this.get('values_in');
+
+      // Have to clear existing validation before initiating current state
+      // validation only
+      this.errorModel.clear();
+
+      if (_.isUndefined(partition_name) || _.isNull(partition_name) ||
+       String(partition_name).replace(/^\s+|\s+$/g, '') == '') {
+       msg = gettext('Partition name cannot be empty.');
+       this.errorModel.set('partition_name', msg);
+       return msg;
+      }
+
+      if (this.top.get('partition_type') == 'range') {
+        if (_.isUndefined(values_from) || _.isNull(values_from) ||
+          String(values_from).replace(/^\s+|\s+$/g, '') == '') {
+          msg = gettext('For range partition From field cannot be empty.');
+          this.errorModel.set('values_from', msg);
+          return msg;
+        } else if (_.isUndefined(values_to) || _.isNull(values_to) ||
+          String(values_to).replace(/^\s+|\s+$/g, '') == '') {
+          msg = gettext('For range partition To field cannot be empty.');
+          this.errorModel.set('values_to', msg);
+          return msg;
+        }
+      } else if (this.top.get('partition_type') == 'list') {
+        if (_.isUndefined(values_in) || _.isNull(values_in) ||
+          String(values_in).replace(/^\s+|\s+$/g, '') == '') {
+          msg = gettext('For list partition In field cannot be empty.');
+          this.errorModel.set('values_in', msg);
+          return msg;
+        }
+      }
+
+      return null;
+    }
+  });
+
+});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js
index 0b701a4..8fe8ded 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js
@@ -11,7 +11,7 @@ define('pgadmin.node.table', [
         node: 'table',
         label: gettext('Tables'),
         type: 'coll-table',
-        columns: ['name', 'relowner', 'description'],
+        columns: ['name', 'relowner', 'is_partitioned', 'description'],
         hasStatistics: true,
         statsPrettifyFields: ['Size', 'Indexes size', 'Table size',
                               'Toast table size', 'Tuple length',
@@ -21,6 +21,7 @@ define('pgadmin.node.table', [
 
   if (!pgBrowser.Nodes['table']) {
     pgBrowser.Nodes['table'] = pgBrowser.Node.extend({
+      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
       type: 'table',
       label: gettext('Table'),
       collection_type: 'coll-table',
@@ -38,7 +39,7 @@ define('pgadmin.node.table', [
       height: '95%',
       width: '85%',
       Init: function() {
-        /* Avoid mulitple registration of menus */
+        /* Avoid multiple registration of menus */
         if (this.initialized)
             return;
 
@@ -277,7 +278,10 @@ define('pgadmin.node.table', [
           hastoasttable: true,
           toast_autovacuum_enabled: false,
           autovacuum_enabled: false,
-          primary_key: []
+          primary_key: [],
+          partitions: [],
+          partition_type: 'range',
+          is_partitioned: false
         },
         // Default values!
         initialize: function(attrs, args) {
@@ -323,12 +327,37 @@ define('pgadmin.node.table', [
             return (!(d && d.label.match(/pg_global/)))
           }
         },{
+          id: 'partition', type: 'group', label: gettext('Partition'),
+          mode: ['edit', 'create'], min_version: 100000,
+          visible: function(m) {
+            // Always show in case of create mode
+            if (m.isNew() || m.get('is_partitioned'))
+              return true;
+            return false;
+          }
+        },{
+          id: 'is_partitioned', label:gettext('Partitioned Table?'), cell: 'switch',
+          type: 'switch', mode: ['properties', 'create', 'edit'],
+          visible: function(m) {
+            if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
+              && !_.isUndefined(m.node_info.server.version) &&
+              m.node_info.server.version >= 100000)
+              return true;
+
+            return false;
+          },
+          disabled: function(m) {
+            if (!m.isNew())
+              return true;
+            return false;
+          }
+        },{
           id: 'description', label: gettext('Comment'), type: 'multiline',
           mode: ['properties', 'create', 'edit'], disabled: 'inSchema'
         },{
           id: 'coll_inherits', label: gettext('Inherited from table(s)'),
           url: 'get_inherits', type: 'array', group: gettext('Columns'),
-          disabled: 'checkInheritance', deps: ['typname'],
+          disabled: 'checkInheritance', deps: ['typname', 'is_partitioned'],
           mode: ['create', 'edit'],
           select2: { multiple: true, allowClear: true,
           placeholder: gettext('Select to inherit from...')},
@@ -425,7 +454,26 @@ define('pgadmin.node.table', [
           model: pgBrowser.Nodes['column'].model,
           subnode: pgBrowser.Nodes['column'].model,
           mode: ['create', 'edit'],
-          disabled: 'inSchema', deps: ['typname'],
+          disabled: function(m) {
+            // In case of partitioned table remove inherited columns
+            if (m.isNew() && m.get('is_partitioned')) {
+              setTimeout(function() {
+                var coll = m.get('columns');
+                coll.remove(coll.filter(function(model) {
+                  if (_.isUndefined(model.get('inheritedfrom')))
+                    return false;
+                  return true;
+                }));
+              }, 10);
+            }
+
+            if(this.node_info &&  'catalog' in this.node_info)
+            {
+              return true;
+            }
+            return false;
+          },
+          deps: ['typname', 'is_partitioned'],
           canAdd: 'check_grid_add_condition',
           canEdit: true, canDelete: true,
           // For each row edit/delete button enable/disable
@@ -528,10 +576,22 @@ define('pgadmin.node.table', [
               subnode: pgBrowser.Nodes['primary_key'].model,
               editable: false, type: 'collection',
               group: gettext('Primary Key'), mode: ['edit', 'create'],
-              canEdit: true, canDelete: true,
+              canEdit: true, canDelete: true, deps:['is_partitioned'],
               control: 'unique-col-collection',
               columns : ['name', 'columns'],
-              canAdd: true,
+              canAdd: function(m) {
+                if (m.get('is_partitioned')) {
+                  setTimeout(function() {
+                    var coll = m.get('primary_key');
+                    coll.remove(coll.filter(function(model) {
+                      return true;
+                    }));
+                  }, 10);
+                  return false;
+                }
+
+                return true;
+              },
               canAddRow: function(m) {
                // User can only add one primary key
                var columns = m.get('columns');
@@ -546,9 +606,21 @@ define('pgadmin.node.table', [
               subnode: pgBrowser.Nodes['foreign_key'].model,
               editable: false, type: 'collection',
               group: gettext('Foreign Key'), mode: ['edit', 'create'],
-              canEdit: true, canDelete: true,
+              canEdit: true, canDelete: true, deps:['is_partitioned'],
               control: 'unique-col-collection',
-              canAdd: true,
+              canAdd: function(m) {
+                if (m.get('is_partitioned')) {
+                  setTimeout(function() {
+                    var coll = m.get('foreign_key');
+                    coll.remove(coll.filter(function(model) {
+                      return true;
+                    }));
+                  }, 10);
+                  return false;
+                }
+
+                return true;
+              },
               columns : ['name', 'columns'],
               canAddRow: function(m) {
                // User can only add if there is at least one column with name.
@@ -561,7 +633,7 @@ define('pgadmin.node.table', [
               subnode: pgBrowser.Nodes['check_constraints'].model,
               editable: false, type: 'collection',
               group: gettext('Check'), mode: ['edit', 'create'],
-              canEdit: true, canDelete: true,
+              canEdit: true, canDelete: true, deps:['is_partitioned'],
               control: 'unique-col-collection',
               canAdd: true,
               columns : ['name', 'consrc']
@@ -571,10 +643,22 @@ define('pgadmin.node.table', [
               subnode: pgBrowser.Nodes['unique_constraint'].model,
               editable: false, type: 'collection',
               group: gettext('Unique'), mode: ['edit', 'create'],
-              canEdit: true, canDelete: true,
+              canEdit: true, canDelete: true, deps:['is_partitioned'],
               control: 'unique-col-collection',
               columns : ['name', 'columns'],
-              canAdd: true,
+              canAdd: function(m) {
+                if (m.get('is_partitioned')) {
+                  setTimeout(function() {
+                    var coll = m.get('unique_constraint');
+                    coll.remove(coll.filter(function(model) {
+                      return true;
+                    }));
+                  }, 10);
+                  return false;
+                }
+
+                return true;
+              },
               canAddRow: function(m) {
                // User can only add if there is at least one column with name.
                var columns = m.get('columns');
@@ -586,10 +670,22 @@ define('pgadmin.node.table', [
               subnode: pgBrowser.Nodes['exclusion_constraint'].model,
               editable: false, type: 'collection',
               group: gettext('Exclude'), mode: ['edit', 'create'],
-              canEdit: true, canDelete: true,
+              canEdit: true, canDelete: true, deps:['is_partitioned'],
               control: 'unique-col-collection',
               columns : ['name', 'columns', 'constraint'],
-              canAdd: true,
+              canAdd: function(m) {
+                if (m.get('is_partitioned')) {
+                  setTimeout(function() {
+                    var coll = m.get('exclude_constraint');
+                    coll.remove(coll.filter(function(model) {
+                      return true;
+                    }));
+                  }, 10);
+                  return false;
+                }
+
+                return true;
+              },
               canAddRow: function(m) {
                // User can only add if there is at least one column with name.
                var columns = m.get('columns');
@@ -600,7 +696,7 @@ define('pgadmin.node.table', [
           id: 'typname', label: gettext('Of type'), type: 'text',
           control: 'node-ajax-options', mode: ['properties', 'create', 'edit'],
           disabled: 'checkOfType', url: 'get_oftype', group: gettext('Advanced'),
-          deps: ['coll_inherits'], transform: function(data, cell) {
+          deps: ['coll_inherits', 'is_partitioned'], transform: function(data, cell) {
             var control = cell || this,
               m = control.model;
               m.of_types_tables = data;
@@ -704,10 +800,180 @@ define('pgadmin.node.table', [
             disabled: 'isLikeDisable', group: gettext('Like')
           }]
         },{
+          id: 'partition_type', label:gettext('Partition Type'),
+          editable: false, type: 'select2', select2: {allowClear: false},
+          group: 'partition', deps: ['is_partitioned'],
+          options:[{
+            label: 'Range', value: 'range'
+          },{
+            label: 'List', value: 'list'
+          }],
+          mode:['create'],
+          visible: function(m) {
+            if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
+              && !_.isUndefined(m.node_info.server.version) &&
+              m.node_info.server.version >= 100000)
+              return true;
+
+            return false;
+          },
+          disabled: function(m) {
+            if (!m.isNew() || !m.get('is_partitioned'))
+              return true;
+            return false;
+          }
+        },{
+          id: 'partition_keys', label:gettext('Partition Keys'),
+          model: Backform.PartitionKeyModel,
+          subnode: Backform.PartitionKeyModel,
+          editable: true, type: 'collection',
+          group: 'partition', mode: ['create'],
+          deps: ['is_partitioned', 'partition_type'],
+          canEdit: false, canDelete: true,
+          control: 'sub-node-collection',
+          canAdd: function(m) {
+            if (m.isNew() && m.get('is_partitioned'))
+              return true;
+            return false;
+          },
+          canAddRow: function(m) {
+            var columns = m.get('columns');
+            var max_row_count = 1000;
+
+            if (m.get('partition_type') && m.get('partition_type') == 'list')
+              max_row_count = 1;
+
+            return (m.get('partition_keys') &&
+                m.get('partition_keys').length < max_row_count &&
+                _.some(columns.pluck('name'))
+            );
+          },
+          visible: function(m) {
+            if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
+              && !_.isUndefined(m.node_info.server.version) &&
+              m.node_info.server.version >= 100000)
+              return true;
+
+            return false;
+          },
+          disabled: function(m) {
+            if (m.get('partition_keys') && m.get('partition_keys').models.length > 0) {
+              setTimeout(function () {
+                var coll = m.get('partition_keys');
+                coll.remove(coll.filter(function(model) {
+                  return true;
+                }));
+              }, 10);
+            }
+          }
+        },{
+          id: 'partition_scheme', label: gettext('Partition Scheme'),
+          type: 'note', group: 'partition', mode: ['edit'],
+          visible: function(m) {
+            if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
+              && !_.isUndefined(m.node_info.server.version) &&
+              m.node_info.server.version >= 100000)
+              return true;
+
+            return false;
+          },
+          disabled: function(m) {
+            if (!m.isNew()) {
+              this.text = m.get('partition_scheme');
+            }
+          }
+        },{
+          id: 'partitions', label:gettext('Partitions'),
+          model: Backform.PartitionsModel,
+          subnode: Backform.PartitionsModel,
+          editable: true, type: 'collection',
+          group: 'partition', mode: ['edit', 'create'],
+          deps: ['is_partitioned', 'partition_type'],
+          canEdit: false, canDelete: true,
+          customDeleteTitle: gettext('Detach Partition'),
+          customDeleteMsg: gettext('Are you sure you wish to detach this partition?'),
+          columns:['is_attach', 'partition_name', 'values_from', 'values_to', 'values_in'],
+          control: Backform.SubNodeCollectionControl.extend({
+            row: Backgrid.PartitionRow,
+            initialize: function() {
+              Backform.SubNodeCollectionControl.prototype.initialize.apply(this, arguments);
+              var self = this;
+              if (!this.model.isNew()) {
+                var node = this.field.get('schema_node'),
+                    node_info = this.field.get('node_info');
+
+                // Make ajax call to get the tables to be attached
+                $.ajax({
+                  url: node.generate_url.apply(
+                      node, [
+                        null, 'get_attach_tables', this.field.get('node_data'),
+                        true, node_info
+                      ]),
+
+                  type: 'GET',
+                  async: false,
+                  success: function(res) {
+                    if (res.success == 1) {
+                      self.model.table_options = res.data;
+                    }
+                    else {
+                      alertify.alert(
+                        'Error fetching tables to be attached', res.data.result
+                      );
+                    }
+                  },
+                  error: function(e) {
+                    var errmsg = $.parseJSON(e.responseText);
+                    alertify.alert('Error fetching tables to be attached.', errmsg.errormsg);
+                  }
+                });
+              }
+            }
+          }
+          ),
+          canAdd: function(m) {
+            if (m.get('is_partitioned'))
+              return true;
+            return false;
+          },
+          visible: function(m) {
+            if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
+              && !_.isUndefined(m.node_info.server.version) &&
+              m.node_info.server.version >= 100000)
+              return true;
+
+            return false;
+          },
+          disabled: function(m) {
+            if (m.isNew() && m.get('partitions') && m.get('partitions').models.length > 0) {
+              setTimeout(function () {
+                var coll = m.get('partitions');
+                coll.remove(coll.filter(function(model) {
+                  return true;
+                }));
+              }, 10);
+            }
+          }
+        },{
+          id: 'partition_note', label: gettext('Partition'),
+          type: 'note', group: 'partition',
+          text: gettext('Above control will be used to Create/Attach/Detach partitions.<br>' +
+            '<ul><li>Create Mode: User will be able to create N number of partitions. Mode switch control is disabled in this scenario.</li>' +
+            '<li>Edit Mode: User will be able to create/attach/detach N number of partitions.' +
+            'In attach mode there will be list of suitable tables to be attached.</li></ul>'),
+          visible: function(m) {
+            if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
+              && !_.isUndefined(m.node_info.server.version) &&
+              m.node_info.server.version >= 100000)
+              return true;
+
+            return false;
+          }
+        },{
           // Here - we will create tab control for storage parameters
           // (auto vacuum).
           type: 'nested', control: 'tab', group: gettext('Parameter'),
-          mode: ['edit', 'create'],
+          mode: ['edit', 'create'], deps: ['is_partitioned'],
           schema: Backform.VacuumSettingsSchema
         },{
           id: 'relacl_str', label: gettext('Privileges'), disabled: 'inSchema',
@@ -734,7 +1000,9 @@ define('pgadmin.node.table', [
               msg = undefined,
               name = this.get('name'),
               schema = this.get('schema'),
-              relowner = this.get('relowner');
+              relowner = this.get('relowner'),
+              is_partitioned = this.get('is_partitioned'),
+              partition_keys = this.get('partition_keys');
 
           // If nothing to validate or VacuumSetting keys then
           // return from here
@@ -762,6 +1030,12 @@ define('pgadmin.node.table', [
             msg = gettext('Table owner cannot be empty.');
             this.errorModel.set('relowner', msg);
             return msg;
+          } else if (is_partitioned && this.isNew() &&
+            !_.isNull(partition_keys) && partition_keys.length <= 0)
+          {
+            msg = gettext('Please specify at least one key for partitioned table.');
+            this.errorModel.set('partition_keys', msg);
+            return msg;
           }
           return null;
         },
@@ -790,7 +1064,15 @@ define('pgadmin.node.table', [
         },
         // Oftype is defined?
         checkInheritance: function(m) {
-         // coll_inherits || typname
+          // Disabled if it is partitioned table
+          if (m.get('is_partitioned')) {
+            setTimeout( function() {
+               m.set('coll_inherits', []);
+            }, 10);
+            return true;
+          }
+
+          // coll_inherits || typname
           if(!m.inSchema.apply(this, [m]) &&
               ( _.isUndefined(m.get('typname')) ||
                 _.isNull(m.get('typname')) ||
@@ -834,7 +1116,15 @@ define('pgadmin.node.table', [
         },
         // We will disable it if Inheritance is defined
         checkOfType: function(m) {
-        //coll_inherits || typname
+          // Disabled if it is partitioned table
+          if (m.get('is_partitioned')) {
+            setTimeout( function() {
+               m.set('typname', undefined);
+            }, 10);
+            return true;
+          }
+
+          //coll_inherits || typname
           if(!m.inSchemaWithModelCheck.apply(this, [m]) &&
               (_.isUndefined(m.get('coll_inherits')) ||
                _.isNull(m.get('coll_inherits')) ||
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/attach.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/attach.sql
new file mode 100644
index 0000000..0661d9c
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/attach.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {{conn|qtIdent(data.parent_schema, data.partitioned_table_name)}} ATTACH PARTITION {{conn|qtIdent(data.schema, data.name)}}
+    {{ data.partition_value }};
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/backend_support.sql
new file mode 100644
index 0000000..5fa1d7e
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/backend_support.sql
@@ -0,0 +1,9 @@
+{#=============Checks if it is partitioned table========#}
+{% if tid %}
+SELECT
+    CASE WHEN c.relkind = 'p' THEN True ELSE False END As ptable
+FROM
+    pg_class c
+WHERE
+    c.oid = {{ tid }}::oid
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/create.sql
new file mode 100644
index 0000000..b5e9404
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/create.sql
@@ -0,0 +1,30 @@
+{% import 'table/sql/macros/constraints.macro' as CONSTRAINTS %}
+{#===========================================#}
+{#====== MAIN TABLE TEMPLATE STARTS HERE ======#}
+{#===========================================#}
+{### CREATE TABLE STATEMENT FOR partitions ###}
+CREATE {% if data.relpersistence %}UNLOGGED {% endif %}TABLE {{conn|qtIdent(data.schema, data.name)}}{% if data.relispartition is defined and data.relispartition %} PARTITION OF {{conn|qtIdent(data.parent_schema, data.partitioned_table_name)}}{% endif %}
+
+{# Macro to render for constraints #}
+{% if data.primary_key|length > 0 or data.unique_constraint|length > 0 or data.foreign_key|length > 0 or data.check_constraint|length > 0 or data.exclude_constraint|length > 0 %}
+( {% endif %}
+{% if data.primary_key|length > 0 %}{{CONSTRAINTS.PRIMARY_KEY(conn, data.primary_key[0])}}{% endif %}{% if data.unique_constraint|length > 0 %}{% if data.primary_key|length > 0 %},{% endif %}
+{{CONSTRAINTS.UNIQUE(conn, data.unique_constraint)}}{% endif %}{% if data.foreign_key|length > 0 %}{% if data.primary_key|length > 0 or data.unique_constraint|length > 0 %},{% endif %}
+{{CONSTRAINTS.FOREIGN_KEY(conn, data.foreign_key)}}{% endif %}{% if data.check_constraint|length > 0 %}{% if data.primary_key|length > 0 or data.unique_constraint|length > 0 or data.foreign_key|length > 0 %},{% endif %}
+{{CONSTRAINTS.CHECK(conn, data.check_constraint)}}{% endif %}{% if data.exclude_constraint|length > 0 %}{% if data.primary_key|length > 0 or data.unique_constraint|length > 0 or data.foreign_key|length > 0 or data.check_constraint|length > 0 %},{% endif %}
+{{CONSTRAINTS.EXCLUDE(conn, data.exclude_constraint)}}{% endif %}
+{% if data.primary_key|length > 0 or data.unique_constraint|length > 0 or data.foreign_key|length > 0 or data.check_constraint|length > 0 or data.exclude_constraint|length > 0 %}
+
+)
+{% endif %}
+    {{ data.partition_value }}{% if data.is_partitioned is defined and data.is_partitioned %}
+
+    PARTITION BY {{ data.partition_scheme }}{% endif %};
+
+
+{### Alter SQL for Owner ###}
+{% if data.relowner %}
+
+ALTER TABLE {{conn|qtIdent(data.schema, data.name)}}
+    OWNER to {{conn|qtIdent(data.relowner)}};
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/detach.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/detach.sql
new file mode 100644
index 0000000..4b413db
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/detach.sql
@@ -0,0 +1 @@
+ALTER TABLE {{conn|qtIdent(data.parent_schema, data.partitioned_table_name)}} DETACH PARTITION {{conn|qtIdent(data.schema, data.name)}};
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/get_attach_tables.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/get_attach_tables.sql
new file mode 100644
index 0000000..476dc3f
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/get_attach_tables.sql
@@ -0,0 +1,23 @@
+SELECT oid, quote_ident(nspname)||'.'||quote_ident(relname) AS table_name FROM
+(SELECT
+	r.oid, r.relname, n.nspname, array_agg(a.attname) attnames, array_agg(a.atttypid) atttypes
+FROM
+	(SELECT oid, relname, relnamespace FROM pg_catalog.pg_class
+	  WHERE relkind in ('r', 'p') AND NOT relispartition) r
+    JOIN (SELECT oid AS nspoid, nspname FROM
+          pg_catalog.pg_namespace WHERE nspname NOT LIKE E'pg\_%') n
+          ON (r.relnamespace = n.nspoid)
+    JOIN (SELECT attrelid, attname, atttypid FROM
+          pg_catalog.pg_attribute WHERE attnum > 0 ORDER BY attrelid, attnum) a
+          ON (r.oid = a.attrelid)
+GROUP BY r.oid, r.relname, r.relnamespace, n.nspname) all_tables
+JOIN
+(SELECT
+	attrelid, array_agg(attname) attnames, array_agg(atttypid) atttypes
+FROM
+	(SELECT * FROM pg_catalog.pg_attribute
+	  WHERE attrelid = {{ tid }} AND attnum > 0
+	  ORDER BY attrelid, attnum) attributes
+GROUP BY attrelid) current_table ON current_table.attrelid != all_tables.oid
+  AND current_table.attnames = all_tables.attnames
+  AND current_table.atttypes = all_tables.atttypes
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/nodes.sql
new file mode 100644
index 0000000..bd828cd
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/nodes.sql
@@ -0,0 +1,15 @@
+SELECT rel.oid, rel.relname AS name,
+    (SELECT count(*) FROM pg_trigger WHERE tgrelid=rel.oid AND tgisinternal = FALSE) AS triggercount,
+    (SELECT count(*) FROM pg_trigger WHERE tgrelid=rel.oid AND tgisinternal = FALSE AND tgenabled = 'O') AS has_enable_triggers,
+    pg_get_expr(rel.relpartbound, rel.oid) AS partition_value,
+    rel.relnamespace AS schema_id,
+    nsp.nspname AS schema_name,
+    (CASE WHEN rel.relkind = 'p' THEN true ELSE false END) AS is_partitioned,
+    (CASE WHEN rel.relkind = 'p' THEN pg_get_partkeydef(rel.oid::oid) ELSE '' END) AS partition_scheme
+FROM
+    (SELECT * FROM pg_inherits WHERE inhparent = {{ tid }}::oid) inh
+    LEFT JOIN pg_class rel ON inh.inhrelid = rel.oid
+    LEFT JOIN pg_namespace nsp ON rel.relnamespace = nsp.oid
+    WHERE rel.relispartition
+    {% if ptid %} AND rel.oid = {{ ptid }}::OID {% endif %}
+    ORDER BY rel.relname;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/properties.sql
new file mode 100644
index 0000000..85f4476
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/partition/sql/10_plus/properties.sql
@@ -0,0 +1,82 @@
+SELECT rel.oid, rel.relname AS name, rel.reltablespace AS spcoid,rel.relacl AS relacl_str,
+  (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE
+    (SELECT sp.spcname FROM pg_database dtb
+    JOIN pg_tablespace sp ON dtb.dattablespace=sp.oid
+    WHERE dtb.oid = {{ did }}::oid)
+  END) as spcname,
+  (select nspname FROM pg_namespace WHERE oid = {{scid}}::oid ) as parent_schema,
+  nsp.nspname as schema,
+  pg_get_userbyid(rel.relowner) AS relowner, rel.relhasoids, rel.relispartition,
+  rel.relhassubclass, rel.reltuples, des.description, con.conname, con.conkey,
+	EXISTS(select 1 FROM pg_trigger
+			JOIN pg_proc pt ON pt.oid=tgfoid AND pt.proname='logtrigger'
+			JOIN pg_proc pc ON pc.pronamespace=pt.pronamespace AND pc.proname='slonyversion'
+			WHERE tgrelid=rel.oid) AS isrepl,
+	(SELECT count(*) FROM pg_trigger WHERE tgrelid=rel.oid AND tgisinternal = FALSE) AS triggercount,
+	(SELECT ARRAY(SELECT CASE WHEN (nspname NOT LIKE E'pg\_%') THEN
+            quote_ident(nspname)||'.'||quote_ident(c.relname)
+            ELSE quote_ident(c.relname) END AS inherited_tables
+    FROM pg_inherits i
+    JOIN pg_class c ON c.oid = i.inhparent
+    JOIN pg_namespace n ON n.oid=c.relnamespace
+    WHERE i.inhrelid = rel.oid ORDER BY inhseqno)) AS coll_inherits,
+  (SELECT count(*)
+		FROM pg_inherits i
+      JOIN pg_class c ON c.oid = i.inhparent
+      JOIN pg_namespace n ON n.oid=c.relnamespace
+		WHERE i.inhrelid = rel.oid) AS inherited_tables_cnt,
+	(CASE WHEN rel.relpersistence = 'u' THEN true ELSE false END) AS relpersistence,
+	substring(array_to_string(rel.reloptions, ',') FROM 'fillfactor=([0-9]*)') AS fillfactor,
+	(CASE WHEN (substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') = 'true')
+	  THEN true ELSE false END) AS autovacuum_enabled,
+	substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold,
+	substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor,
+	substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold,
+	substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor,
+	substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay,
+	substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit,
+	substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age,
+	substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age,
+	substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age,
+	(CASE WHEN (substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') =  'true')
+	  THEN true ELSE false END) AS toast_autovacuum_enabled,
+	substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold,
+	substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor,
+	substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold,
+	substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor,
+	substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay,
+	substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit,
+	substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age,
+	substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age,
+	substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age,
+	array_to_string(rel.reloptions, ',') AS table_vacuum_settings_str,
+	array_to_string(tst.reloptions, ',') AS toast_table_vacuum_settings_str,
+	rel.reloptions AS reloptions, tst.reloptions AS toast_reloptions, rel.reloftype, typ.typname,
+	(CASE WHEN rel.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable,
+    -- Added for pgAdmin4
+	(CASE WHEN (substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)'))::boolean  THEN true ELSE false END) AS autovacuum_custom,
+	(CASE WHEN (substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)'))::boolean  AND rel.reltoastrelid != 0 THEN true ELSE false END) AS toast_autovacuum,
+
+	(SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=rel.oid AND sl1.objsubid=0) AS seclabels,
+	(CASE WHEN rel.oid <= {{ datlastsysoid}}::oid THEN true ElSE false END) AS is_sys_table,
+	-- Added for partition table
+	(CASE WHEN rel.relkind = 'p' THEN true ELSE false END) AS is_partitioned,
+	(CASE WHEN rel.relkind = 'p' THEN pg_get_partkeydef(rel.oid::oid) ELSE '' END) AS partition_scheme,
+	{% if ptid %}
+	  (CASE WHEN rel.relispartition THEN pg_get_expr(rel.relpartbound, {{ ptid }}::oid) ELSE '' END) AS partition_value,
+	  (SELECT relname FROM pg_class WHERE oid = {{ tid }}::oid) AS partitioned_table_name
+	{% else %}
+	  pg_get_expr(rel.relpartbound, rel.oid) AS partition_value
+	{% endif %}
+
+FROM pg_class rel
+  LEFT OUTER JOIN pg_tablespace spc on spc.oid=rel.reltablespace
+  LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass)
+  LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'
+  LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid
+  LEFT JOIN pg_type typ ON rel.reloftype=typ.oid
+  LEFT JOIN pg_inherits inh ON inh.inhrelid = rel.oid
+  LEFT JOIN pg_namespace nsp ON rel.relnamespace = nsp.oid
+WHERE rel.relispartition AND inh.inhparent = {{ tid }}::oid
+{% if ptid %}  AND rel.oid = {{ ptid }}::oid {% endif %}
+ORDER BY rel.relname;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/10_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/10_plus/acl.sql
new file mode 100644
index 0000000..eadf9d5
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/10_plus/acl.sql
@@ -0,0 +1,46 @@
+{### SQL to fetch privileges for tablespace ###}
+SELECT 'relacl' as deftype, COALESCE(gt.rolname, 'PUBLIC') grantee, g.rolname grantor,
+    array_agg(privilege_type) as privileges, array_agg(is_grantable) as grantable
+FROM
+  (SELECT
+    d.grantee, d.grantor, d.is_grantable,
+    CASE d.privilege_type
+		WHEN 'CONNECT' THEN 'c'
+		WHEN 'CREATE' THEN 'C'
+		WHEN 'DELETE' THEN 'd'
+		WHEN 'EXECUTE' THEN 'X'
+		WHEN 'INSERT' THEN 'a'
+		WHEN 'REFERENCES' THEN 'x'
+		WHEN 'SELECT' THEN 'r'
+		WHEN 'TEMPORARY' THEN 'T'
+		WHEN 'TRIGGER' THEN 't'
+		WHEN 'TRUNCATE' THEN 'D'
+		WHEN 'UPDATE' THEN 'w'
+		WHEN 'USAGE' THEN 'U'
+		ELSE 'UNKNOWN'
+	END AS privilege_type
+  FROM
+    (SELECT rel.relacl
+        FROM pg_class rel
+          LEFT OUTER JOIN pg_tablespace spc on spc.oid=rel.reltablespace
+          LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'
+          LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid
+          LEFT JOIN pg_type typ ON rel.reloftype=typ.oid
+        WHERE rel.relkind IN ('r','s','t','p') AND rel.relnamespace = {{ scid }}::oid
+            AND rel.oid = {{ tid }}::oid
+    ) acl,
+    (SELECT (d).grantee AS grantee, (d).grantor AS grantor, (d).is_grantable
+        AS is_grantable, (d).privilege_type AS privilege_type FROM (SELECT
+        aclexplode(rel.relacl) as d
+        FROM pg_class rel
+          LEFT OUTER JOIN pg_tablespace spc on spc.oid=rel.reltablespace
+          LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'
+          LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid
+          LEFT JOIN pg_type typ ON rel.reloftype=typ.oid
+        WHERE rel.relkind IN ('r','s','t','p') AND rel.relnamespace = {{ scid }}::oid
+            AND rel.oid = {{ tid }}::oid
+        ) a) d
+    ) d
+  LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid)
+  LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid)
+GROUP BY g.rolname, gt.rolname
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/10_plus/get_inherits.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/10_plus/get_inherits.sql
new file mode 100644
index 0000000..f28f298
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/10_plus/get_inherits.sql
@@ -0,0 +1,17 @@
+{% import 'table/sql/macros/db_catalogs.macro' as CATALOG %}
+SELECT c.oid, c.relname , nspname,
+CASE WHEN nspname NOT LIKE E'pg\_%' THEN
+ quote_ident(nspname)||'.'||quote_ident(c.relname)
+ELSE quote_ident(c.relname)
+END AS inherits
+FROM pg_class c
+JOIN pg_namespace n
+ON n.oid=c.relnamespace
+WHERE relkind='r' AND NOT relispartition
+{% if not show_system_objects %}
+{{ CATALOG.VALID_CATALOGS(server_type) }}
+{% endif %}
+{% if tid %}
+AND c.oid != tid
+{% endif %}
+ORDER BY relnamespace, c.relname
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/10_plus/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/10_plus/get_oid.sql
new file mode 100644
index 0000000..f630698
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/10_plus/get_oid.sql
@@ -0,0 +1,5 @@
+SELECT rel.oid as tid
+FROM pg_class rel
+WHERE rel.relkind IN ('r','s','t','p')
+AND rel.relnamespace = {{ scid }}::oid
+AND rel.relname = {{data.name|qtLiteral}}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/10_plus/get_table.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/10_plus/get_table.sql
new file mode 100644
index 0000000..6952c8d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/10_plus/get_table.sql
@@ -0,0 +1,8 @@
+SELECT
+    rel.relname AS name
+FROM
+    pg_class rel
+WHERE
+    rel.relkind IN ('r','s','t','p')
+    AND rel.relnamespace = {{ scid }}::oid
+    AND rel.oid = {{ tid }}::oid;
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/10_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/10_plus/nodes.sql
new file mode 100644
index 0000000..fbf9c42
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/10_plus/nodes.sql
@@ -0,0 +1,9 @@
+SELECT rel.oid, rel.relname AS name,
+    (SELECT count(*) FROM pg_trigger WHERE tgrelid=rel.oid AND tgisinternal = FALSE) AS triggercount,
+    (SELECT count(*) FROM pg_trigger WHERE tgrelid=rel.oid AND tgisinternal = FALSE AND tgenabled = 'O') AS has_enable_triggers,
+    (CASE WHEN rel.relkind = 'p' THEN true ELSE false END) AS is_partitioned
+FROM pg_class rel
+    WHERE rel.relkind IN ('r','s','t','p') AND rel.relnamespace = {{ scid }}::oid
+    AND NOT rel.relispartition
+    {% if tid %} AND rel.oid = {{tid}}::OID {% endif %}
+    ORDER BY rel.relname;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/10_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/10_plus/properties.sql
new file mode 100644
index 0000000..1d589df
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/10_plus/properties.sql
@@ -0,0 +1,73 @@
+SELECT rel.oid, rel.relname AS name, rel.reltablespace AS spcoid,rel.relacl AS relacl_str,
+  (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE
+    (SELECT sp.spcname FROM pg_database dtb
+    JOIN pg_tablespace sp ON dtb.dattablespace=sp.oid
+    WHERE dtb.oid = {{ did }}::oid)
+  END) as spcname,
+  (select nspname FROM pg_namespace WHERE oid = {{scid}}::oid ) as schema,
+  pg_get_userbyid(rel.relowner) AS relowner, rel.relhasoids, rel.relkind,
+  (CASE WHEN rel.relkind = 'p' THEN true ELSE false END) AS is_partitioned,
+  rel.relhassubclass, rel.reltuples, des.description, con.conname, con.conkey,
+	EXISTS(select 1 FROM pg_trigger
+			JOIN pg_proc pt ON pt.oid=tgfoid AND pt.proname='logtrigger'
+			JOIN pg_proc pc ON pc.pronamespace=pt.pronamespace AND pc.proname='slonyversion'
+			WHERE tgrelid=rel.oid) AS isrepl,
+	(SELECT count(*) FROM pg_trigger WHERE tgrelid=rel.oid AND tgisinternal = FALSE) AS triggercount,
+	(SELECT ARRAY(SELECT CASE WHEN (nspname NOT LIKE E'pg\_%') THEN
+            quote_ident(nspname)||'.'||quote_ident(c.relname)
+            ELSE quote_ident(c.relname) END AS inherited_tables
+    FROM pg_inherits i
+    JOIN pg_class c ON c.oid = i.inhparent
+    JOIN pg_namespace n ON n.oid=c.relnamespace
+    WHERE i.inhrelid = rel.oid ORDER BY inhseqno)) AS coll_inherits,
+  (SELECT count(*)
+		FROM pg_inherits i
+      JOIN pg_class c ON c.oid = i.inhparent
+      JOIN pg_namespace n ON n.oid=c.relnamespace
+		WHERE i.inhrelid = rel.oid) AS inherited_tables_cnt,
+	(CASE WHEN rel.relpersistence = 'u' THEN true ELSE false END) AS relpersistence,
+	substring(array_to_string(rel.reloptions, ',') FROM 'fillfactor=([0-9]*)') AS fillfactor,
+	(CASE WHEN (substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') = 'true')
+	  THEN true ELSE false END) AS autovacuum_enabled,
+	substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold,
+	substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor,
+	substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold,
+	substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor,
+	substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay,
+	substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit,
+	substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age,
+	substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age,
+	substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age,
+	(CASE WHEN (substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') =  'true')
+	  THEN true ELSE false END) AS toast_autovacuum_enabled,
+	substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold,
+	substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor,
+	substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold,
+	substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor,
+	substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay,
+	substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit,
+	substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age,
+	substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age,
+	substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age,
+	array_to_string(rel.reloptions, ',') AS table_vacuum_settings_str,
+	array_to_string(tst.reloptions, ',') AS toast_table_vacuum_settings_str,
+	rel.reloptions AS reloptions, tst.reloptions AS toast_reloptions, rel.reloftype, typ.typname,
+	(CASE WHEN rel.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable,
+    -- Added for pgAdmin4
+	(CASE WHEN (substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)'))::boolean  THEN true ELSE false END) AS autovacuum_custom,
+	(CASE WHEN (substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)'))::boolean  AND rel.reltoastrelid != 0 THEN true ELSE false END) AS toast_autovacuum,
+
+	(SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=rel.oid AND sl1.objsubid=0) AS seclabels,
+	(CASE WHEN rel.oid <= {{ datlastsysoid}}::oid THEN true ElSE false END) AS is_sys_table
+	-- Added for partition table
+    {% if tid %}, (CASE WHEN rel.relkind = 'p' THEN pg_get_partkeydef({{ tid }}::oid) ELSE '' END) AS partition_scheme {% endif %}
+FROM pg_class rel
+  LEFT OUTER JOIN pg_tablespace spc on spc.oid=rel.reltablespace
+  LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass)
+  LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'
+  LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid
+  LEFT JOIN pg_type typ ON rel.reloftype=typ.oid
+WHERE rel.relkind IN ('r','s','t','p') AND rel.relnamespace = {{ scid }}::oid
+AND NOT rel.relispartition
+{% if tid %}  AND rel.oid = {{ tid }}::oid {% endif %}
+ORDER BY rel.relname;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/default/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/default/create.sql
index f8e858f..5ec8ffc 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/default/create.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/default/create.sql
@@ -57,7 +57,8 @@ CREATE {% if data.relpersistence %}UNLOGGED {% endif %}TABLE {{conn|qtIdent(data
 {{CONSTRAINTS.EXCLUDE(conn, data.exclude_constraint)}}{% endif %}
 {% if data.like_relation or data.coll_inherits or data.columns|length > 0 or data.primary_key|length > 0 or data.unique_constraint|length > 0 or data.foreign_key|length > 0 or data.check_constraint|length > 0 or data.exclude_constraint|length > 0 %}
 
-)
+){% if data.relkind is defined and data.relkind == 'p' %} PARTITION BY {{ data.partition_scheme }} {% endif %}
+
 {% endif %}
 {### If we are inheriting it from another table(s) ###}
 {% if data.coll_inherits %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/default/get_schema_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/default/get_schema_oid.sql
index 4d329d2..8df84b5 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/default/get_schema_oid.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/default/get_schema_oid.sql
@@ -1,8 +1,9 @@
 {# ===== fetch new assigned schema oid ===== #}
 SELECT
-    c.relnamespace as scid
+    c.relnamespace as scid, nsp.nspname as nspname
 FROM
     pg_class c
+LEFT JOIN pg_namespace nsp ON nsp.oid = c.relnamespace
 WHERE
 {% if tid %}
     c.oid = {{tid}}::oid;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/templates/trigger/js/trigger.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/templates/trigger/js/trigger.js
index 7716b87..7897fd9 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/templates/trigger/js/trigger.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/templates/trigger/js/trigger.js
@@ -29,13 +29,15 @@ define('pgadmin.node.trigger', [
         node: 'trigger',
         label: gettext('Triggers'),
         type: 'coll-trigger',
+        getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
         columns: ['name', 'description']
       });
   };
 
   if (!pgBrowser.Nodes['trigger']) {
-    pgAdmin.Browser.Nodes['trigger'] = pgAdmin.Browser.Node.extend({
-      parent_type: ['table', 'view'],
+    pgAdmin.Browser.Nodes['trigger'] = pgBrowser.Node.extend({
+      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
+      parent_type: ['table', 'view', 'partition'],
       collection_type: ['coll-table', 'coll-view'],
       type: 'trigger',
       label: gettext('Trigger'),
@@ -71,6 +73,12 @@ define('pgadmin.node.trigger', [
           icon: 'wcTabIcon icon-trigger', data: {action: 'create', check: true},
           enable: 'canCreate'
         },{
+          name: 'create_trigger_onPartition', node: 'partition', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: gettext('Trigger...'),
+          icon: 'wcTabIcon icon-trigger', data: {action: 'create', check: true},
+          enable: 'canCreate'
+        },{
           name: 'enable_trigger', node: 'trigger', module: this,
           applies: ['object', 'context'], callback: 'enable_trigger',
           category: 'connect', priority: 3, label: gettext('Enable trigger'),
@@ -201,6 +209,17 @@ define('pgadmin.node.trigger', [
           mode: ['create','edit', 'properties'],
           deps: ['is_constraint_trigger'],
           disabled: function(m) {
+            // Disabled if table is a partitioned table.
+            if (_.has(m, 'node_info') && _.has(m.node_info, 'table') &&
+                _.has(m.node_info.table, 'is_partitioned') && m.node_info.table.is_partitioned)
+            {
+              setTimeout(function(){
+                  m.set('is_row_trigger', false);
+              },10);
+
+              return true;
+            }
+
             // If constraint trigger is set to True then row trigger will
             // automatically set to True and becomes disable
             var is_constraint_trigger = m.get('is_constraint_trigger');
@@ -227,7 +246,19 @@ define('pgadmin.node.trigger', [
           id: 'is_constraint_trigger', label: gettext('Constraint trigger?'),
           type: 'switch', disabled: 'inSchemaWithModelCheck',
           mode: ['create','edit', 'properties'],
-          group: gettext('Definition')
+          group: gettext('Definition'),
+          disabled: function(m) {
+            // Disabled if table is a partitioned table.
+            if (_.has(m, 'node_info') && _.has(m.node_info, 'table') &&
+                _.has(m.node_info.table, 'is_partitioned') && m.node_info.table.is_partitioned)
+            {
+              setTimeout(function(){
+                  m.set('is_constraint_trigger', false);
+              },10);
+
+              return true;
+            }
+          }
         },{
           id: 'tgdeferrable', label: gettext('Deferrable?'),
           type: 'switch', group: gettext('Definition'),
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py
new file mode 100644
index 0000000..7709e1a
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py
@@ -0,0 +1,2112 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2017, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+""" Implements Utility class for Table and Partitioned Table. """
+
+import re
+from functools import wraps
+import simplejson as json
+from flask import render_template, jsonify, request
+from flask_babel import gettext
+from pgadmin.utils.ajax import make_json_response, internal_server_error, \
+    make_response as ajax_response, gone
+from pgadmin.browser.server_groups.servers.databases.schemas.utils \
+    import DataTypeReader, trigger_definition, parse_rule_definition
+from pgadmin.browser.server_groups.servers.utils import parse_priv_from_db, \
+    parse_priv_to_db
+from pgadmin.browser.utils import PGChildNodeView
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+
+
+class BaseTableView(PGChildNodeView):
+    """
+    This class is base class for tables and partitioned tables.
+
+    Methods:
+    -------
+    * check_precondition()
+      - This function will behave as a decorator which will checks
+        database connection before running view, it will also attaches
+        manager,conn & template_path properties to self
+
+    * _formatter(data, tid)
+      - It will return formatted output of query result
+        as per client model format
+
+    * _columns_formatter(tid, data):
+      - It will return formatted output of query result
+        as per client model format for column node
+
+    * _index_constraints_formatter(self, did, tid, data):
+      - It will return formatted output of query result
+        as per client model format for index constraint node
+
+    * _cltype_formatter(type): (staticmethod)
+      - We need to remove [] from type and append it
+        after length/precision so we will send flag for
+        sql template.
+
+    * get_table_dependents(self, tid):
+      - This function get the dependents and return ajax response
+        for the table node.
+
+    * get_table_dependencies(self, tid):
+      - This function get the dependencies and return ajax response
+        for the table node.
+
+    * get_table_statistics(self, tid):
+      - Returns the statistics for a particular table if tid is specified,
+        otherwise it will return statistics for all the tables in that
+        schema.
+    * get_reverse_engineered_sql(self, did, scid, tid, main_sql, data):
+      - This function will creates reverse engineered sql for
+        the table object.
+
+    * reset_statistics(self, scid, tid):
+      - This function will reset statistics of table.
+    """
+    @staticmethod
+    def check_precondition(f):
+        """
+        This function will behave as a decorator which will checks
+        database connection before running view, it will also attaches
+        manager,conn & template_path properties to self
+        """
+
+        @wraps(f)
+        def wrap(*args, **kwargs):
+            # Here args[0] will hold self & kwargs will hold gid,sid,did
+            self = args[0]
+            driver = get_driver(PG_DEFAULT_DRIVER)
+            did = kwargs['did']
+            self.manager = driver.connection_manager(kwargs['sid'])
+            self.conn = self.manager.connection(did=kwargs['did'])
+            self.qtIdent = driver.qtIdent
+            self.qtTypeIdent = driver.qtTypeIdent
+            # We need datlastsysoid to check if current table is system table
+            self.datlastsysoid = self.manager.db_info[
+                did
+            ]['datlastsysoid'] if self.manager.db_info is not None and \
+                did in self.manager.db_info else 0
+
+            ver = self.manager.version
+            # Set the template path for the SQL scripts
+            self.table_template_path = 'table/sql/#{0}#'.format(ver)
+            self.partition_template_path = 'partition/sql/#{0}#'.format(ver)
+
+            # Template for Column ,check constraint and exclusion
+            # constraint node
+            self.column_template_path = 'column/sql/#{0}#'.format(ver)
+            self.check_constraint_template_path = \
+                'check_constraint/sql/#{0}#'.format(ver)
+            self.exclusion_constraint_template_path = \
+                'exclusion_constraint/sql/#{0}#'.format(ver)
+
+            # Template for PK & Unique constraint node
+            self.index_constraint_template_path = 'index_constraint/sql'
+
+            # Template for foreign key constraint node
+            self.foreign_key_template_path = 'foreign_key/sql/#{0}#'.format(ver)
+
+            # Template for index node
+            self.index_template_path = 'index/sql/#{0}#'.format(ver)
+
+            # Template for trigger node
+            self.trigger_template_path = 'trigger/sql/#{0}#'.format(ver)
+
+            # Template for rules node
+            self.rules_template_path = 'rules/sql'
+
+            # Supported ACL for table
+            self.acl = ['a', 'r', 'w', 'd', 'D', 'x', 't']
+
+            # Supported ACL for columns
+            self.column_acl = ['a', 'r', 'w', 'x']
+
+            return f(*args, **kwargs)
+
+        return wrap
+
+    def _columns_formatter(self, tid, data):
+        """
+        Args:
+            tid: Table OID
+            data: dict of query result
+
+        Returns:
+            It will return formatted output of query result
+            as per client model format for column node
+        """
+        for column in data['columns']:
+
+            # We need to format variables according to client js collection
+            if 'attoptions' in column and column['attoptions'] is not None:
+                spcoptions = []
+                for spcoption in column['attoptions']:
+                    k, v = spcoption.split('=')
+                    spcoptions.append({'name': k, 'value': v})
+
+                column['attoptions'] = spcoptions
+
+            # Need to format security labels according to client js collection
+            if 'seclabels' in column and column['seclabels'] is not None:
+                seclabels = []
+                for seclbls in column['seclabels']:
+                    k, v = seclbls.split('=')
+                    seclabels.append({'provider': k, 'label': v})
+
+                column['seclabels'] = seclabels
+
+            if 'attnum' in column and column['attnum'] is not None \
+                    and column['attnum'] > 0:
+                # We need to parse & convert ACL coming from database to
+                # json format
+                SQL = render_template("/".join(
+                    [self.column_template_path, 'acl.sql']),
+                    tid=tid, clid=column['attnum']
+                )
+                status, acl = self.conn.execute_dict(SQL)
+
+                if not status:
+                    return internal_server_error(errormsg=acl)
+
+                # We will set get privileges from acl sql so we don't need
+                # it from properties sql
+                column['attacl'] = []
+
+                for row in acl['rows']:
+                    priv = parse_priv_from_db(row)
+                    column.setdefault(row['deftype'], []).append(priv)
+
+                # we are receiving request when in edit mode
+                # we will send filtered types related to current type
+                present_type = column['cltype']
+
+                type_id = column['atttypid']
+
+                fulltype = self.get_full_type(
+                    column['typnspname'], column['typname'],
+                    column['isdup'], column['attndims'], column['atttypmod']
+                )
+
+                length = False
+                precision = False
+                if 'elemoid' in column:
+                    length, precision, typeval = \
+                        self.get_length_precision(column['elemoid'])
+
+                # Set length and precision to None
+                column['attlen'] = None
+                column['attprecision'] = None
+
+                # If we have length & precision both
+                if length and precision:
+                    matchObj = re.search(r'(\d+),(\d+)', fulltype)
+                    if matchObj:
+                        column['attlen'] = matchObj.group(1)
+                        column['attprecision'] = matchObj.group(2)
+                elif length:
+                    # If we have length only
+                    matchObj = re.search(r'(\d+)', fulltype)
+                    if matchObj:
+                        column['attlen'] = matchObj.group(1)
+                        column['attprecision'] = None
+
+
+                SQL = render_template("/".join([self.column_template_path,
+                                                'is_referenced.sql']),
+                                      tid=tid, clid=column['attnum'])
+
+                status, is_reference = self.conn.execute_scalar(SQL)
+
+                edit_types_list = list()
+                # We will need present type in edit mode
+
+                if column['typnspname'] == "pg_catalog" \
+                        or column['typnspname'] == "public":
+                    edit_types_list.append(present_type)
+                else:
+                    t = self.qtTypeIdent(self.conn, column['typnspname'],
+                                         present_type)
+                    edit_types_list.append(t)
+                    column['cltype'] = t
+
+                if int(is_reference) == 0:
+                    SQL = render_template("/".join([self.column_template_path,
+                                                    'edit_mode_types.sql']),
+                                          type_id=type_id)
+                    status, rset = self.conn.execute_2darray(SQL)
+
+                    for row in rset['rows']:
+                        edit_types_list.append(row['typname'])
+                else:
+                    edit_types_list.append(present_type)
+
+                column['edit_types'] = edit_types_list
+                column['cltype'] = DataTypeReader.parse_type_name(column['cltype'])
+
+                if 'indkey' in column:
+                    # Current column
+                    attnum = str(column['attnum'])
+
+                    # Single/List of primary key column(s)
+                    indkey = str(column['indkey'])
+
+                    # We will check if column is in primary column(s)
+                    if attnum in indkey.split(" "):
+                        column['is_primary_key'] = True
+                    else:
+                        column['is_primary_key'] = False
+
+        return data
+
+    def _index_constraints_formatter(self, did, tid, data):
+        """
+        Args:
+            tid: Table OID
+            data: dict of query result
+
+        Returns:
+            It will return formatted output of query result
+            as per client model format for index constraint node
+        """
+
+        # We will fetch all the index constraints for the table
+        index_constraints = {
+            'p': 'primary_key', 'u': 'unique_constraint'
+        }
+
+        for ctype in index_constraints.keys():
+            data[index_constraints[ctype]] = []
+
+            sql = render_template("/".join([self.index_constraint_template_path,
+                                            'properties.sql']),
+                                  did=did, tid=tid,
+                                  constraint_type=ctype)
+            status, res = self.conn.execute_dict(sql)
+
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            for row in res['rows']:
+                result = row
+                sql = render_template(
+                    "/".join([self.index_constraint_template_path,
+                              'get_constraint_cols.sql']),
+                    cid=row['oid'],
+                    colcnt=row['indnatts'])
+                status, res = self.conn.execute_dict(sql)
+
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                columns = []
+                for r in res['rows']:
+                    columns.append({"column": r['column'].strip('"')})
+
+                result['columns'] = columns
+
+                # If not exists then create list and/or append into
+                # existing list [ Adding into main data dict]
+                data.setdefault(index_constraints[ctype], []).append(result)
+
+        return data
+
+    def _foreign_key_formatter(self, tid, data):
+        """
+        Args:
+            tid: Table OID
+            data: dict of query result
+
+        Returns:
+            It will return formatted output of query result
+            as per client model format for foreign key constraint node
+        """
+
+        # We will fetch all the index constraints for the table
+        sql = render_template("/".join([self.foreign_key_template_path,
+                                        'properties.sql']),
+                              tid=tid)
+
+        status, result = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=result)
+
+        for fk in result['rows']:
+
+            sql = render_template("/".join([self.foreign_key_template_path,
+                                            'get_constraint_cols.sql']),
+                                  tid=tid,
+                                  keys=zip(fk['confkey'], fk['conkey']),
+                                  confrelid=fk['confrelid'])
+
+            status, res = self.conn.execute_dict(sql)
+
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            columns = []
+            cols = []
+            for row in res['rows']:
+                columns.append({"local_column": row['conattname'],
+                                "references": fk['confrelid'],
+                                "referenced": row['confattname']})
+                cols.append(row['conattname'])
+
+            fk['columns'] = columns
+
+            SQL = render_template("/".join([self.foreign_key_template_path,
+                                            'get_parent.sql']),
+                                  tid=fk['columns'][0]['references'])
+
+            status, rset = self.conn.execute_2darray(SQL)
+            if not status:
+                return internal_server_error(errormsg=rset)
+
+            fk['remote_schema'] = rset['rows'][0]['schema']
+            fk['remote_table'] = rset['rows'][0]['table']
+
+            coveringindex = self.search_coveringindex(tid, cols)
+
+            fk['coveringindex'] = coveringindex
+            if coveringindex:
+                fk['autoindex'] = True
+                fk['hasindex'] = True
+            else:
+                fk['autoindex'] = False
+                fk['hasindex'] = False
+            # If not exists then create list and/or append into
+            # existing list [ Adding into main data dict]
+            data.setdefault('foreign_key', []).append(fk)
+
+        return data
+
+    def _check_constraint_formatter(self, tid, data):
+        """
+        Args:
+            tid: Table OID
+            data: dict of query result
+
+        Returns:
+            It will return formatted output of query result
+            as per client model format for check constraint node
+        """
+
+        # We will fetch all the index constraints for the table
+        SQL = render_template("/".join([self.check_constraint_template_path,
+                                        'properties.sql']),
+                              tid=tid)
+
+        status, res = self.conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+        # If not exists then create list and/or append into
+        # existing list [ Adding into main data dict]
+
+        data['check_constraint'] = res['rows']
+
+        return data
+
+    def _exclusion_constraint_formatter(self, did, tid, data):
+        """
+        Args:
+            tid: Table OID
+            data: dict of query result
+
+        Returns:
+            It will return formatted output of query result
+            as per client model format for exclusion constraint node
+        """
+
+        # We will fetch all the index constraints for the table
+        sql = render_template("/".join([self.exclusion_constraint_template_path,
+                                        'properties.sql']),
+                              did=did, tid=tid)
+
+        status, result = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=result)
+
+        for ex in result['rows']:
+
+            sql = render_template("/".join(
+                [self.exclusion_constraint_template_path,
+                 'get_constraint_cols.sql']),
+                cid=ex['oid'],
+                colcnt=ex['indnatts'])
+
+            status, res = self.conn.execute_dict(sql)
+
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            columns = []
+            for row in res['rows']:
+                if row['options'] & 1:
+                    order = False
+                    nulls_order = True if (row['options'] & 2) else False
+                else:
+                    order = True
+                    nulls_order = True if (row['options'] & 2) else False
+
+                columns.append({"column": row['coldef'].strip('"'),
+                                "oper_class": row['opcname'],
+                                "order": order,
+                                "nulls_order": nulls_order,
+                                "operator": row['oprname'],
+                                "col_type": row['datatype']
+                                })
+
+            ex['columns'] = columns
+            # If not exists then create list and/or append into
+            # existing list [ Adding into main data dict]
+            data.setdefault('exclude_constraint', []).append(ex)
+
+        return data
+
+    def _formatter(self, did, scid, tid, data):
+        """
+        Args:
+            data: dict of query result
+            scid: schema oid
+            tid: table oid
+
+        Returns:
+            It will return formatted output of query result
+            as per client model format
+        """
+        # Need to format security labels according to client js collection
+        if 'seclabels' in data and data['seclabels'] is not None:
+            seclabels = []
+            for seclbls in data['seclabels']:
+                k, v = seclbls.split('=')
+                seclabels.append({'provider': k, 'label': v})
+
+            data['seclabels'] = seclabels
+
+        # We need to parse & convert ACL coming from database to json format
+        SQL = render_template("/".join([self.table_template_path, 'acl.sql']),
+                              tid=tid, scid=scid)
+        status, acl = self.conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=acl)
+
+        # We will set get privileges from acl sql so we don't need
+        # it from properties sql
+        for row in acl['rows']:
+            priv = parse_priv_from_db(row)
+            if row['deftype'] in data:
+                data[row['deftype']].append(priv)
+            else:
+                data[row['deftype']] = [priv]
+
+        # We will add Auto vacuum defaults with out result for grid
+        data['vacuum_table'] = self.parse_vacuum_data(self.conn, data, 'table')
+        data['vacuum_toast'] = self.parse_vacuum_data(self.conn, data, 'toast')
+
+        # Fetch columns for the table logic
+        #
+        # 1) Check if of_type and inherited tables are present?
+        # 2) If yes then Fetch all the columns for of_type and inherited tables
+        # 3) Add columns in columns collection
+        # 4) Find all the columns for tables and filter out columns which are
+        #   not inherited from any table & format them one by one
+
+        # Get of_type table columns and add it into columns dict
+        if data['typname']:
+            SQL = render_template("/".join([self.table_template_path,
+                                            'get_columns_for_table.sql']),
+                                  tname=data['typname'])
+
+            status, res = self.conn.execute_dict(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+            data['columns'] = res['rows']
+
+        # Get inherited table(s) columns and add it into columns dict
+        elif data['coll_inherits'] and len(data['coll_inherits']) > 0:
+            columns = []
+            # Return all tables which can be inherited & do not show
+            # system columns
+            SQL = render_template("/".join([self.table_template_path,
+                                            'get_inherits.sql']),
+                                  show_system_objects=False
+                                  )
+            status, rset = self.conn.execute_2darray(SQL)
+            if not status:
+                return internal_server_error(errormsg=rset)
+
+            for row in rset['rows']:
+                if row['inherits'] in data['coll_inherits']:
+                    # Fetch columns using inherited table OID
+                    SQL = render_template("/".join(
+                        [self.table_template_path,
+                         'get_columns_for_table.sql']),
+                        tid=row['oid']
+                    )
+                    status, res = self.conn.execute_dict(SQL)
+                    if not status:
+                        return internal_server_error(errormsg=res)
+                    columns.extend(res['rows'][:])
+            data['columns'] = columns
+
+        # We will fetch all the columns for the table using
+        # columns properties.sql, so we need to set template path
+        SQL = render_template("/".join([self.column_template_path,
+                                        'properties.sql']),
+                              tid=tid,
+                              show_sys_objects=False
+                              )
+
+        status, res = self.conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+        all_columns = res['rows']
+
+        # Filter inherited columns from all columns
+        if 'columns' in data and len(data['columns']) > 0 \
+                and len(all_columns) > 0:
+            for row in data['columns']:
+                for i, col in enumerate(all_columns):
+                    # If both name are same then remove it
+                    # as it is inherited from other table
+                    if col['name'] == row['name']:
+                        # Remove same column from all_columns as
+                        # already have it columns collection
+                        del all_columns[i]
+
+            # If any column is added then update columns collection
+            if len(all_columns) > 0:
+                data['columns'] += all_columns
+        # If no inherited columns found then add all columns
+        elif len(all_columns) > 0:
+            data['columns'] = all_columns
+
+        if 'columns' in data and len(data['columns']) > 0:
+            data = self._columns_formatter(tid, data)
+
+        # Here we will add constraint in our output
+        data = self._index_constraints_formatter(did, tid, data)
+        data = self._foreign_key_formatter(tid, data)
+        data = self._check_constraint_formatter(tid, data)
+        data = self._exclusion_constraint_formatter(did, tid, data)
+
+        return data
+
+    @staticmethod
+    def _cltype_formatter(data_type):
+        """
+
+        Args:
+            data_type: Type string
+
+        Returns:
+            We need to remove [] from type and append it
+            after length/precision so we will send flag for
+            sql template
+        """
+        if '[]' in data_type:
+            return data_type[:-2], True
+        else:
+            return data_type, False
+
+    def get_table_dependents(self, tid):
+        """
+        This function get the dependents and return ajax response
+        for the table node.
+
+        Args:
+            tid: Table ID
+        """
+        # Specific condition for column which we need to append
+        where = "WHERE dep.refobjid={0}::OID".format(tid)
+
+        dependents_result = self.get_dependents(
+            self.conn, tid
+        )
+
+        # Specific sql to run againt column to fetch dependents
+        SQL = render_template("/".join([self.table_template_path,
+                                        'depend.sql']), where=where)
+
+        status, res = self.conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        for row in res['rows']:
+            ref_name = row['refname']
+            if ref_name is None:
+                continue
+
+            dep_type = ''
+            dep_str = row['deptype']
+            if dep_str == 'a':
+                dep_type = 'auto'
+            elif dep_str == 'n':
+                dep_type = 'normal'
+            elif dep_str == 'i':
+                dep_type = 'internal'
+
+            dependents_result.append({'type': 'sequence', 'name': ref_name,
+                                      'field': dep_type})
+
+        return ajax_response(
+            response=dependents_result,
+            status=200
+        )
+
+    def get_table_dependencies(self, tid):
+        """
+        This function get the dependencies and return ajax response
+        for the table node.
+
+        Args:
+            tid: Table ID
+
+        """
+        dependencies_result = self.get_dependencies(
+            self.conn, tid
+        )
+
+        return ajax_response(
+            response=dependencies_result,
+            status=200
+        )
+
+    def get_table_statistics(self, scid, tid):
+        """
+        Statistics
+
+        Args:
+            scid: Schema Id
+            tid: Table Id
+
+        Returns the statistics for a particular table if tid is specified,
+        otherwise it will return statistics for all the tables in that
+        schema.
+        """
+
+        # Fetch schema name
+        status, schema_name = self.conn.execute_scalar(
+            render_template(
+                "/".join([self.table_template_path, 'get_schema.sql']),
+                conn=self.conn, scid=scid
+            )
+        )
+        if not status:
+            return internal_server_error(errormsg=schema_name)
+
+        if tid is None:
+            status, res = self.conn.execute_dict(
+                render_template(
+                    "/".join([self.table_template_path,
+                              'coll_table_stats.sql']), conn=self.conn,
+                    schema_name=schema_name
+                )
+            )
+        else:
+            # For Individual table stats
+
+            # Check if pgstattuple extension is already created?
+            # if created then only add extended stats
+            status, is_pgstattuple = self.conn.execute_scalar("""
+            SELECT (count(extname) > 0) AS is_pgstattuple
+            FROM pg_extension
+            WHERE extname='pgstattuple'
+            """)
+            if not status:
+                return internal_server_error(errormsg=is_pgstattuple)
+
+            # Fetch Table name
+            status, table_name = self.conn.execute_scalar(
+                render_template(
+                    "/".join([self.table_template_path, 'get_table.sql']),
+                    conn=self.conn, scid=scid, tid=tid
+                )
+            )
+            if not status:
+                return internal_server_error(errormsg=table_name)
+
+            status, res = self.conn.execute_dict(
+                render_template(
+                    "/".join([self.table_template_path, 'stats.sql']),
+                    conn=self.conn, schema_name=schema_name,
+                    table_name=table_name,
+                    is_pgstattuple=is_pgstattuple, tid=tid
+                )
+            )
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    def get_reverse_engineered_sql(self, did, scid, tid, main_sql, data):
+        """
+        This function will creates reverse engineered sql for
+        the table object
+
+         Args:
+           did: Database ID
+           scid: Schema ID
+           tid: Table ID
+           main_sql: List contains all the reversed engineered sql
+           data: Table's Data
+        """
+        """
+        #####################################
+        # 1) Reverse engineered sql for TABLE
+        #####################################
+        """
+
+        # Table & Schema declaration so that we can use them in child nodes
+        schema = data['schema']
+        table = data['name']
+
+        data = self._formatter(did, scid, tid, data)
+
+        # Now we have all lis of columns which we need
+        # to include in our create definition, Let's format them
+        if 'columns' in data:
+            for c in data['columns']:
+                if 'attacl' in c:
+                    c['attacl'] = parse_priv_to_db(c['attacl'], self.column_acl)
+
+                # check type for '[]' in it
+                if 'cltype' in c:
+                    c['cltype'], c['hasSqrBracket'] = \
+                        self._cltype_formatter(c['cltype'])
+
+        sql_header = u"-- Table: {0}\n\n-- ".format(
+            self.qtIdent(self.conn, data['schema'], data['name']))
+
+        sql_header += render_template("/".join([self.table_template_path,
+                                                'delete.sql']),
+                                      data=data, conn=self.conn)
+
+        sql_header = sql_header.strip('\n')
+        sql_header += '\n'
+
+        # Add into main sql
+        main_sql.append(sql_header)
+
+        # Parse privilege data
+        if 'relacl' in data:
+            data['relacl'] = parse_priv_to_db(data['relacl'], self.acl)
+
+        # if table is partitions then
+        if 'relispartition' in data and data['relispartition']:
+            table_sql = render_template("/".join([self.partition_template_path,
+                                                  'create.sql']),
+                                        data=data, conn=self.conn)
+        else:
+            table_sql = render_template("/".join([self.table_template_path,
+                                                  'create.sql']),
+                                        data=data, conn=self.conn)
+
+        # Add into main sql
+        table_sql = re.sub('\n{2,}', '\n\n', table_sql)
+        main_sql.append(table_sql.strip('\n'))
+
+        """
+        ######################################
+        # 2) Reverse engineered sql for INDEX
+        ######################################
+        """
+
+        SQL = render_template("/".join([self.index_template_path,
+                                        'nodes.sql']), tid=tid)
+        status, rset = self.conn.execute_2darray(SQL)
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        for row in rset['rows']:
+
+            SQL = render_template("/".join([self.index_template_path,
+                                            'properties.sql']),
+                                  did=did, tid=tid, idx=row['oid'],
+                                  datlastsysoid=self.datlastsysoid)
+
+            status, res = self.conn.execute_dict(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            data = dict(res['rows'][0])
+            # Adding parent into data dict, will be using it while creating sql
+            data['schema'] = schema
+            data['table'] = table
+            # We also need to fecth columns of index
+            SQL = render_template("/".join([self.index_template_path,
+                                            'column_details.sql']),
+                                  idx=row['oid'])
+            status, rset = self.conn.execute_2darray(SQL)
+            if not status:
+                return internal_server_error(errormsg=rset)
+
+            # 'attdef' comes with quotes from query so we need to strip them
+            # 'options' we need true/false to render switch
+            # ASC(false)/DESC(true)
+            columns = []
+            cols = []
+            for col_row in rset['rows']:
+                # We need all data as collection for ColumnsModel
+                cols_data = {
+                    'colname': col_row['attdef'].strip('"'),
+                    'collspcname': col_row['collnspname'],
+                    'op_class': col_row['opcname'],
+                }
+                if col_row['options'][0] == 'DESC':
+                    cols_data['sort_order'] = True
+                columns.append(cols_data)
+
+                # We need same data as string to display in properties window
+                # If multiple column then separate it by colon
+                cols_str = col_row['attdef']
+                if col_row['collnspname']:
+                    cols_str += ' COLLATE ' + col_row['collnspname']
+                if col_row['opcname']:
+                    cols_str += ' ' + col_row['opcname']
+                if col_row['options'][0] == 'DESC':
+                    cols_str += ' DESC'
+                cols.append(cols_str)
+
+            # Push as collection
+            data['columns'] = columns
+            # Push as string
+            data['cols'] = ', '.join(cols)
+
+            sql_header = u"\n-- Index: {0}\n\n-- ".format(data['name'])
+
+            sql_header += render_template("/".join([self.index_template_path,
+                                                    'delete.sql']),
+                                          data=data, conn=self.conn)
+
+            index_sql = render_template("/".join([self.index_template_path,
+                                                  'create.sql']),
+                                        data=data, conn=self.conn)
+            index_sql += "\n"
+            index_sql += render_template("/".join([self.index_template_path,
+                                                   'alter.sql']),
+                                         data=data, conn=self.conn)
+
+            # Add into main sql
+            index_sql = re.sub('\n{2,}', '\n\n', index_sql)
+            main_sql.append(sql_header + '\n\n' + index_sql.strip('\n'))
+
+        """
+        ########################################
+        # 3) Reverse engineered sql for TRIGGERS
+        ########################################
+        """
+        SQL = render_template("/".join([self.trigger_template_path,
+                                        'nodes.sql']), tid=tid)
+        status, rset = self.conn.execute_2darray(SQL)
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        for row in rset['rows']:
+            trigger_sql = ''
+
+            SQL = render_template("/".join([self.trigger_template_path,
+                                            'properties.sql']),
+                                  tid=tid, trid=row['oid'],
+                                  datlastsysoid=self.datlastsysoid)
+
+            status, res = self.conn.execute_dict(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            data = dict(res['rows'][0])
+            # Adding parent into data dict, will be using it while creating sql
+            data['schema'] = schema
+            data['table'] = table
+
+            if data['tgnargs'] > 1:
+                # We know that trigger has more than 1 arguments,
+                # let's join them
+                data['tgargs'] = ', '.join(data['tgargs'])
+
+            if len(data['tgattr']) > 1:
+                columns = ', '.join(data['tgattr'].split(' '))
+
+                SQL = render_template("/".join([self.trigger_template_path,
+                                                'get_columns.sql']),
+                                      tid=tid, clist=columns)
+
+                status, rset = self.conn.execute_2darray(SQL)
+                if not status:
+                    return internal_server_error(errormsg=rset)
+                # 'tgattr' contains list of columns from table used in trigger
+                columns = []
+
+                for col_row in rset['rows']:
+                    columns.append({'column': col_row['name']})
+
+                data['columns'] = columns
+
+            data = trigger_definition(data)
+
+            sql_header = u"\n-- Trigger: {0}\n\n-- ".format(data['name'])
+
+            sql_header += render_template("/".join([self.trigger_template_path,
+                                                    'delete.sql']),
+                                          data=data, conn=self.conn)
+
+            # If the request for new object which do not have did
+            trigger_sql = render_template("/".join([self.trigger_template_path,
+                                                    'create.sql']),
+                                          data=data, conn=self.conn)
+
+            trigger_sql = sql_header + '\n\n' + trigger_sql.strip('\n')
+
+            # If trigger is disabled then add sql code for the same
+            if not data['is_enable_trigger']:
+                trigger_sql += '\n\n'
+                trigger_sql += render_template("/".join([
+                    self.trigger_template_path,
+                    'enable_disable_trigger.sql']),
+                    data=data, conn=self.conn)
+
+            # Add into main sql
+            trigger_sql = re.sub('\n{2,}', '\n\n', trigger_sql)
+            main_sql.append(trigger_sql)
+
+        """
+        #####################################
+        # 4) Reverse engineered sql for RULES
+        #####################################
+        """
+
+        SQL = render_template("/".join(
+            [self.rules_template_path, 'properties.sql']), tid=tid)
+
+        status, rset = self.conn.execute_2darray(SQL)
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        for row in rset['rows']:
+            rules_sql = '\n'
+            SQL = render_template("/".join(
+                [self.rules_template_path, 'properties.sql']
+            ), rid=row['oid'], datlastsysoid=self.datlastsysoid)
+
+            status, res = self.conn.execute_dict(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            res_data = parse_rule_definition(res)
+            rules_sql += render_template("/".join(
+                [self.rules_template_path, 'create.sql']),
+                data=res_data, display_comments=True)
+
+            # Add into main sql
+            rules_sql = re.sub('\n{2,}', '\n\n', rules_sql)
+            main_sql.append(rules_sql)
+
+        """
+        ##########################################
+        # 5) Reverse engineered sql for PARTITIONS
+        ##########################################
+        """
+        if 'is_partitioned' in data and data['is_partitioned']:
+            SQL = render_template("/".join([self.partition_template_path,
+                                            'nodes.sql']),
+                                  scid=scid, tid=tid)
+            status, rset = self.conn.execute_2darray(SQL)
+            if not status:
+                return internal_server_error(errormsg=rset)
+
+            sql_header = u"\n-- Partitions SQL"
+            partition_sql = ''
+            for row in rset['rows']:
+                part_data = dict()
+                part_data['partitioned_table_name'] = data['name']
+                part_data['parent_schema'] = data['schema']
+                part_data['schema'] = row['schema_name']
+                part_data['relispartition'] = True
+                part_data['name'] = row['name']
+                part_data['partition_value'] = row['partition_value']
+                part_data['is_partitioned'] = row ['is_partitioned']
+                part_data['partition_scheme'] = row['partition_scheme']
+
+                partition_sql += render_template("/".join(
+                    [self.partition_template_path, 'create.sql']),
+                    data=part_data, conn=self.conn)
+
+            # Add into main sql
+            partition_sql = re.sub('\n{2,}', '\n\n', partition_sql)
+            main_sql.append(sql_header + '\n\n' + partition_sql.strip('\n'))
+
+        sql = '\n'.join(main_sql)
+
+        return ajax_response(response=sql.strip('\n'))
+
+    def reset_statistics(self, scid, tid):
+        """
+        This function will reset statistics of table
+
+         Args:
+           scid: Schema ID
+           tid: Table ID
+        """
+        try:
+            SQL = render_template("/".join([self.table_template_path,
+                                            'reset_stats.sql']),
+                                  tid=tid)
+            status, res = self.conn.execute_scalar(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            return make_json_response(
+                success=1,
+                info=gettext("Table statistics have been reset"),
+                data={
+                    'id': tid,
+                    'scid': scid
+                }
+            )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    def get_partition_scheme(self, data):
+        partition_scheme = None
+        if 'partition_type' in data \
+                and data['partition_type'] == 'range':
+            partition_scheme = 'RANGE ('
+        elif 'partition_type' in data \
+                and data['partition_type'] == 'list':
+            partition_scheme = 'LIST ('
+
+        for row in data['partition_keys']:
+            if row['key_type'] == 'column':
+                partition_scheme += self.qtIdent(
+                    self.conn, row['pt_column']) + ', '
+            elif row['key_type'] == 'expression':
+                partition_scheme += row['expression'] + ', '
+
+        # Remove extra space and comma
+        if len(data['partition_keys']) > 0:
+            partition_scheme = partition_scheme[:-2]
+        partition_scheme += ')'
+
+        return partition_scheme
+
+    @staticmethod
+    def validate_constrains(key, data):
+
+        if key == 'primary_key' or key == 'unique_constraint':
+            if 'columns' in data and len(data['columns']) > 0:
+                return True
+            else:
+                return False
+        elif key == 'foreign_key':
+            if 'oid' not in data:
+                for arg in ['columns']:
+                    if arg not in data:
+                        return False
+                    elif isinstance(data[arg], list) and len(data[arg]) < 1:
+                        return False
+
+                if 'autoindex' in data and data['autoindex'] and \
+                        ('coveringindex' not in data or
+                                                  data['coveringindex'] == ''):
+                    return False
+
+            return True
+
+        elif key == 'check_constraint':
+            for arg in ['consrc']:
+                if arg not in data or data[arg] == '':
+                    return False
+            return True
+
+        elif key == 'exclude_constraint':
+            pass
+
+        return True
+
+    @staticmethod
+    def check_and_convert_name_to_string(data):
+        """
+        This function will check and covert table to string incase
+        it is numeric
+
+        Args:
+            data: data dict
+
+        Returns:
+            Updated data dict
+        """
+        # For Python2, it can be int, long, float
+        if hasattr(str, 'decode'):
+            if isinstance(data['name'], (int, long, float)):
+                data['name'] = str(data['name'])
+        else:
+            # For Python3, it can be int, float
+            if isinstance(data['name'], (int, float)):
+                data['name'] = str(data['name'])
+        return data
+
+    def get_index_constraint_sql(self, did, tid, data):
+        """
+         Args:
+           tid: Table ID
+           data: data dict coming from the client
+
+        Returns:
+        This function will generate modified sql for index constraints
+        (Primary Key & Unique)
+        """
+        sql = []
+        # We will fetch all the index constraints for the table
+        index_constraints = {
+            'p': 'primary_key', 'u': 'unique_constraint'
+        }
+
+        for ctype in index_constraints.keys():
+            # Check if constraint is in data
+            # If yes then we need to check for add/change/delete
+            if index_constraints[ctype] in data:
+                constraint = data[index_constraints[ctype]]
+                # If constraint(s) is/are deleted
+                if 'deleted' in constraint:
+                    for c in constraint['deleted']:
+                        c['schema'] = data['schema']
+                        c['table'] = data['name']
+
+                        # Sql for drop
+                        sql.append(
+                            render_template("/".join(
+                                [self.index_constraint_template_path,
+                                 'delete.sql']),
+                                data=c, conn=self.conn).strip('\n')
+                        )
+
+                if 'changed' in constraint:
+                    for c in constraint['changed']:
+                        c['schema'] = data['schema']
+                        c['table'] = data['name']
+
+                        properties_sql = render_template("/".join(
+                            [self.index_constraint_template_path, 'properties.sql']),
+                            did=did, tid=tid, cid=c['oid'], constraint_type=ctype)
+                        status, res = self.conn.execute_dict(properties_sql)
+                        if not status:
+                            return internal_server_error(errormsg=res)
+
+                        old_data = res['rows'][0]
+                        # Sql to update object
+                        sql.append(
+                            render_template("/".join([
+                                self.index_constraint_template_path,
+                                'update.sql']), data=c, o_data=old_data,
+                                conn=self.conn).strip('\n')
+                        )
+
+                if 'added' in constraint:
+                    for c in constraint['added']:
+                        c['schema'] = data['schema']
+                        c['table'] = data['name']
+
+                        # Sql to add object
+                        if self.validate_constrains(index_constraints[ctype], c):
+                            sql.append(
+                                render_template(
+                                    "/".join([self.index_constraint_template_path,
+                                              'create.sql']),
+                                    data=c, conn=self.conn,
+                                    constraint_name='PRIMARY KEY'
+                                    if ctype == 'p' else 'UNIQUE'
+                                ).strip('\n')
+                            )
+                        else:
+                            sql.append(
+                                gettext(
+                                    '-- definition incomplete for {0} constraint'.format(index_constraints[ctype])
+                                )
+                            )
+        if len(sql) > 0:
+            # Join all the sql(s) as single string
+            return '\n\n'.join(sql)
+        else:
+            return None
+
+    def get_foreign_key_sql(self, tid, data):
+        """
+         Args:
+           tid: Table ID
+           data: data dict coming from the client
+
+        Returns:
+        This function will generate modified sql for foreign key
+        """
+        sql = []
+        # Check if constraint is in data
+        # If yes then we need to check for add/change/delete
+        if 'foreign_key' in data:
+            constraint = data['foreign_key']
+            # If constraint(s) is/are deleted
+            if 'deleted' in constraint:
+                for c in constraint['deleted']:
+                    c['schema'] = data['schema']
+                    c['table'] = data['name']
+
+                    # Sql for drop
+                    sql.append(
+                        render_template("/".join(
+                            [self.foreign_key_template_path,
+                             'delete.sql']),
+                            data=c, conn=self.conn).strip('\n')
+                    )
+
+            if 'changed' in constraint:
+                for c in constraint['changed']:
+                    c['schema'] = data['schema']
+                    c['table'] = data['name']
+
+                    properties_sql = render_template("/".join(
+                        [self.foreign_key_template_path, 'properties.sql']),
+                        tid=tid, cid=c['oid'])
+                    status, res = self.conn.execute_dict(properties_sql)
+                    if not status:
+                        return internal_server_error(errormsg=res)
+
+                    old_data = res['rows'][0]
+                    # Sql to update object
+                    sql.append(
+                        render_template("/".join([
+                            self.foreign_key_template_path,
+                            'update.sql']), data=c, o_data=old_data,
+                            conn=self.conn).strip('\n')
+                    )
+
+                    if not self.validate_constrains('foreign_key', c):
+                        sql.append(
+                            gettext(
+                                '-- definition incomplete for foreign_key constraint'
+                            )
+                        )
+                        return '\n\n'.join(sql)
+
+                    if 'columns' in c:
+                        cols = []
+                        for col in c['columns']:
+                            cols.append(col['local_column'])
+
+                        coveringindex = self.search_coveringindex(tid, cols)
+
+                        if coveringindex is None and 'autoindex' in c and c['autoindex'] and \
+                                ('coveringindex' in c and
+                                         c['coveringindex'] != ''):
+                            sql.append(render_template(
+                                "/".join([self.foreign_key_template_path, 'create_index.sql']),
+                                data=c, conn=self.conn).strip('\n')
+                                       )
+
+            if 'added' in constraint:
+                for c in constraint['added']:
+                    c['schema'] = data['schema']
+                    c['table'] = data['name']
+
+                    # Sql to add object
+                    # Columns
+
+                    if not self.validate_constrains('foreign_key', c):
+                        sql.append(
+                            gettext(
+                                '-- definition incomplete for foreign_key constraint'
+                            )
+                        )
+                        return '\n\n'.join(sql)
+
+                    SQL = render_template("/".join([self.foreign_key_template_path,
+                                                    'get_parent.sql']),
+                                          tid=c['columns'][0]['references'])
+                    status, rset = self.conn.execute_2darray(SQL)
+                    if not status:
+                        return internal_server_error(errormsg=rset)
+
+                    c['remote_schema'] = rset['rows'][0]['schema']
+                    c['remote_table'] = rset['rows'][0]['table']
+
+                    sql.append(
+                        render_template(
+                            "/".join([self.foreign_key_template_path,
+                                      'create.sql']),
+                            data=c, conn=self.conn
+                        ).strip('\n')
+                    )
+
+                    if c['autoindex']:
+                        sql.append(
+                            render_template(
+                                "/".join([self.foreign_key_template_path,
+                                          'create_index.sql']),
+                                data=c, conn=self.conn).strip('\n')
+                        )
+
+        if len(sql) > 0:
+            # Join all the sql(s) as single string
+            return '\n\n'.join(sql)
+        else:
+            return None
+
+    def get_check_constraint_sql(self, tid, data):
+        """
+         Args:
+           tid: Table ID
+           data: data dict coming from the client
+
+        Returns:
+          This function will generate modified sql for check constraint
+        """
+        sql = []
+        # Check if constraint is in data
+        # If yes then we need to check for add/change/delete
+        if 'check_constraint' in data:
+            constraint = data['check_constraint']
+            # If constraint(s) is/are deleted
+            if 'deleted' in constraint:
+                for c in constraint['deleted']:
+                    c['schema'] = data['schema']
+                    c['table'] = data['name']
+
+                    # Sql for drop
+                    sql.append(
+                        render_template("/".join(
+                            [self.check_constraint_template_path,
+                             'delete.sql']),
+                            data=c, conn=self.conn).strip('\n')
+                    )
+
+            if 'changed' in constraint:
+                for c in constraint['changed']:
+                    c['schema'] = data['schema']
+                    c['table'] = data['name']
+
+                    properties_sql = render_template("/".join(
+                        [self.check_constraint_template_path, 'properties.sql']),
+                        tid=tid, cid=c['oid'])
+                    status, res = self.conn.execute_dict(properties_sql)
+                    if not status:
+                        return internal_server_error(errormsg=res)
+
+                    old_data = res['rows'][0]
+                    # Sql to update object
+                    sql.append(
+                        render_template("/".join([
+                            self.check_constraint_template_path,
+                            'update.sql']), data=c, o_data=old_data,
+                            conn=self.conn).strip('\n')
+                    )
+
+            if 'added' in constraint:
+                for c in constraint['added']:
+                    c['schema'] = data['schema']
+                    c['table'] = data['name']
+
+                    if not self.validate_constrains('check_constraint', c):
+                        sql.append(
+                            gettext(
+                                '-- definition incomplete for check_constraint'
+                            )
+                        )
+                        return '\n\n'.join(sql)
+
+                    sql.append(
+                        render_template(
+                            "/".join([self.check_constraint_template_path,
+                                      'create.sql']),
+                            data=c, conn=self.conn
+                        ).strip('\n')
+                    )
+
+        if len(sql) > 0:
+            # Join all the sql(s) as single string
+            return '\n\n'.join(sql)
+        else:
+            return None
+
+    def get_exclusion_constraint_sql(self, did, tid, data):
+        """
+         Args:
+           tid: Table ID
+           data: data dict coming from the client
+
+        Returns:
+          This function will generate modified sql for exclusion constraint
+        """
+        sql = []
+        # Check if constraint is in data
+        # If yes then we need to check for add/change/delete
+        if 'exclude_constraint' in data:
+            constraint = data['exclude_constraint']
+            # If constraint(s) is/are deleted
+            if 'deleted' in constraint:
+                for c in constraint['deleted']:
+                    c['schema'] = data['schema']
+                    c['table'] = data['name']
+
+                    # Sql for drop
+                    sql.append(
+                        render_template("/".join(
+                            [self.exclusion_constraint_template_path,
+                             'delete.sql']),
+                            data=c, conn=self.conn).strip('\n')
+                    )
+
+            if 'changed' in constraint:
+                for c in constraint['changed']:
+                    c['schema'] = data['schema']
+                    c['table'] = data['name']
+
+                    properties_sql = render_template("/".join(
+                        [self.exclusion_constraint_template_path, 'properties.sql']),
+                        did=did, tid=tid, cid=c['oid'])
+                    status, res = self.conn.execute_dict(properties_sql)
+                    if not status:
+                        return internal_server_error(errormsg=res)
+
+                    old_data = res['rows'][0]
+                    # Sql to update object
+                    sql.append(
+                        render_template("/".join([
+                            self.exclusion_constraint_template_path,
+                            'update.sql']), data=c, o_data=old_data,
+                            conn=self.conn).strip('\n')
+                    )
+
+            if 'added' in constraint:
+                for c in constraint['added']:
+                    c['schema'] = data['schema']
+                    c['table'] = data['name']
+
+                    if not self.validate_constrains('exclude_constraint', c):
+                        sql.append(
+                            gettext(
+                                '-- definition incomplete for exclusion_constraint'
+                            )
+                        )
+                        return '\n\n'.join(sql)
+
+                    sql.append(
+                        render_template(
+                            "/".join([self.exclusion_constraint_template_path,
+                                      'create.sql']),
+                            data=c, conn=self.conn
+                        ).strip('\n')
+                    )
+
+        if len(sql) > 0:
+            # Join all the sql(s) as single string
+            return u'\n\n'.join(sql)
+        else:
+            return None
+
+    def get_sql(self, did, scid, tid, data, res):
+        """
+        This function will generate create/update sql from model data
+        coming from client
+        """
+        if tid is not None:
+            old_data = res['rows'][0]
+            old_data = self._formatter(did, scid, tid, old_data)
+
+            # We will convert privileges coming from client required
+            if 'relacl' in data:
+                for mode in ['added', 'changed', 'deleted']:
+                    if mode in data['relacl']:
+                        data['relacl'][mode] = parse_priv_to_db(
+                            data['relacl'][mode], self.acl
+                        )
+
+            # If name is not present in request data
+            if 'name' not in data:
+                data['name'] = old_data['name']
+
+            data = BaseTableView.check_and_convert_name_to_string(data)
+
+            # If name if not present
+            if 'schema' not in data:
+                data['schema'] = old_data['schema']
+
+            # Filter out new tables from list, we will send complete list
+            # and not newly added tables in the list from client
+            # so we will filter new tables here
+            if 'coll_inherits' in data:
+                p_len = len(old_data['coll_inherits'])
+                c_len = len(data['coll_inherits'])
+                # If table(s) added
+                if c_len > p_len:
+                    data['coll_inherits_added'] = list(
+                        set(data['coll_inherits']) - set(old_data['coll_inherits'])
+                    )
+                # If table(s)removed
+                elif c_len < p_len:
+                    data['coll_inherits_removed'] = list(
+                        set(old_data['coll_inherits']) - set(data['coll_inherits'])
+                    )
+                # Safe side verification,In case it happens..
+                # If user removes and adds same number of table
+                # eg removed one table and added one new table
+                elif c_len == p_len:
+                    data['coll_inherits_added'] = list(
+                        set(data['coll_inherits']) - set(old_data['coll_inherits'])
+                    )
+                    data['coll_inherits_removed'] = list(
+                        set(old_data['coll_inherits']) - set(data['coll_inherits'])
+                    )
+
+            SQL = render_template("/".join([self.table_template_path, 'update.sql']),
+                                  o_data=old_data, data=data, conn=self.conn)
+            # Removes training new lines
+            SQL = SQL.strip('\n') + '\n\n'
+
+            # Parse/Format columns & create sql
+            if 'columns' in data:
+                # Parse the data coming from client
+                data = self._parse_format_columns(data, mode='edit')
+
+                columns = data['columns']
+                column_sql = '\n'
+
+                # If column(s) is/are deleted
+                if 'deleted' in columns:
+                    for c in columns['deleted']:
+                        c['schema'] = data['schema']
+                        c['table'] = data['name']
+                        # Sql for drop column
+                        if 'inheritedfrom' not in c:
+                            column_sql += render_template("/".join(
+                                [self.column_template_path, 'delete.sql']),
+                                data=c, conn=self.conn).strip('\n') + '\n\n'
+
+                # If column(s) is/are changed
+                # Here we will be needing previous properties of column
+                # so that we can compare & update it
+                if 'changed' in columns:
+                    for c in columns['changed']:
+                        c['schema'] = data['schema']
+                        c['table'] = data['name']
+                        if 'attacl' in c:
+                            c['attacl'] = parse_priv_to_db(c['attacl'],
+                                                           self.column_acl)
+
+                        properties_sql = render_template("/".join([self.column_template_path,
+                                                                   'properties.sql']),
+                                                         tid=tid,
+                                                         clid=c['attnum'],
+                                                         show_sys_objects=self.blueprint.show_system_objects
+                                                         )
+
+                        status, res = self.conn.execute_dict(properties_sql)
+                        if not status:
+                            return internal_server_error(errormsg=res)
+                        old_data = res['rows'][0]
+
+                        old_data['cltype'], old_data['hasSqrBracket'] = self._cltype_formatter(old_data['cltype'])
+                        old_data = BaseTableView.convert_length_precision_to_string(old_data)
+
+                        fulltype = self.get_full_type(
+                            old_data['typnspname'], old_data['typname'],
+                            old_data['isdup'], old_data['attndims'], old_data['atttypmod']
+                        )
+
+                        # If we have length & precision both
+                        matchObj = re.search(r'(\d+),(\d+)', fulltype)
+                        if matchObj:
+                            old_data['attlen'] = int(matchObj.group(1))
+                            old_data['attprecision'] = int(matchObj.group(2))
+                        else:
+                            # If we have length only
+                            matchObj = re.search(r'(\d+)', fulltype)
+                            if matchObj:
+                                old_data['attlen'] = int(matchObj.group(1))
+                                old_data['attprecision'] = None
+                            else:
+                                old_data['attlen'] = None
+                                old_data['attprecision'] = None
+
+                        old_data['cltype'] = DataTypeReader.parse_type_name(old_data['cltype'])
+
+                        # Sql for alter column
+                        if 'inheritedfrom' not in c:
+                            column_sql += render_template("/".join(
+                                [self.column_template_path, 'update.sql']),
+                                data=c, o_data=old_data, conn=self.conn).strip('\n') + '\n\n'
+
+                # If column(s) is/are added
+                if 'added' in columns:
+                    for c in columns['added']:
+                        c['schema'] = data['schema']
+                        c['table'] = data['name']
+                        # Sql for create column
+                        if 'attacl' in c:
+                            c['attacl'] = parse_priv_to_db(c['attacl'],
+                                                           self.column_acl)
+
+                        c = BaseTableView.convert_length_precision_to_string(c)
+
+                        if 'inheritedfrom' not in c:
+                            column_sql += render_template("/".join(
+                                [self.column_template_path, 'create.sql']),
+                                data=c, conn=self.conn).strip('\n') + '\n\n'
+
+                # Combine all the SQL together
+                SQL += column_sql.strip('\n')
+
+            # Check for partitions
+            if 'partitions' in data:
+                partitions = data['partitions']
+                partitions_sql = '\n'
+
+                # If partition(s) is/are deleted
+                if 'deleted' in partitions:
+                    for row in partitions['deleted']:
+                        temp_data = dict()
+                        schema_name, table_name = \
+                            self.get_schema_and_table_name(row['oid'])
+
+                        temp_data['parent_schema'] = old_data['schema']
+                        temp_data['partitioned_table_name'] = old_data['name']
+                        temp_data['schema'] = schema_name
+                        temp_data['name'] = table_name
+
+                        # Sql for detach partition
+                        partitions_sql += render_template("/".join(
+                            [self.partition_template_path, 'detach.sql']),
+                            data=temp_data, conn=self.conn).strip('\n') + '\n\n'
+
+                # If partition(s) is/are added
+                if 'added' in partitions:
+                    temp_data = dict()
+                    temp_data['schema'] = old_data['schema']
+                    temp_data['name'] = old_data['name']
+                    # get the partition type
+                    temp_data['partition_type'] = \
+                        old_data['partition_scheme'].split()[0].lower()
+                    temp_data['partitions'] = partitions['added']
+
+                    partitions_sql += \
+                        self.get_partitions_sql(temp_data).strip('\n') + '\n\n'
+
+                # Combine all the SQL together
+                SQL += partitions_sql.strip('\n')
+
+            # Check if index constraints are added/changed/deleted
+            index_constraint_sql = self.get_index_constraint_sql(did, tid, data)
+            # If we have index constraint sql then ad it in main sql
+            if index_constraint_sql is not None:
+                SQL += '\n' + index_constraint_sql
+
+            # Check if foreign key(s) is/are added/changed/deleted
+            foreign_key_sql = self.get_foreign_key_sql(tid, data)
+            # If we have foreign key sql then ad it in main sql
+            if foreign_key_sql is not None:
+                SQL += '\n' + foreign_key_sql
+
+            # Check if check constraint(s) is/are added/changed/deleted
+            check_constraint_sql = self.get_check_constraint_sql(tid, data)
+            # If we have check constraint sql then ad it in main sql
+            if check_constraint_sql is not None:
+                SQL += '\n' + check_constraint_sql
+
+            # Check if exclusion constraint(s) is/are added/changed/deleted
+            exclusion_constraint_sql = self.get_exclusion_constraint_sql(did, tid, data)
+            # If we have check constraint sql then ad it in main sql
+            if exclusion_constraint_sql is not None:
+                SQL += '\n' + exclusion_constraint_sql
+
+        else:
+            res = None
+            required_args = [
+                'name'
+            ]
+
+            for arg in required_args:
+                if arg not in data:
+                    return gettext('-- definition incomplete')
+
+            # validate constraint data.
+            for key in ['primary_key', 'unique_constraint',
+                        'foreign_key', 'check_constraint',
+                        'exclude_constraint']:
+                if key in data and len(data[key]) > 0:
+                    for constraint in data[key]:
+                        if not self.validate_constrains(key, constraint):
+                            return gettext(
+                                '-- definition incomplete for {0}'.format(key)
+                            )
+
+            # We will convert privileges coming from client required
+            # in server side format
+            if 'relacl' in data:
+                data['relacl'] = parse_priv_to_db(data['relacl'], self.acl)
+
+            # Parse & format columns
+            data = self._parse_format_columns(data)
+            data = BaseTableView.check_and_convert_name_to_string(data)
+
+            if 'foreign_key' in data:
+                for c in data['foreign_key']:
+                    SQL = render_template("/".join(
+                        [self.foreign_key_template_path,
+                         'get_parent.sql']),
+                        tid=c['columns'][0]['references'])
+                    status, rset = self.conn.execute_2darray(SQL)
+                    if not status:
+                        return internal_server_error(errormsg=rset)
+
+                    c['remote_schema'] = rset['rows'][0]['schema']
+                    c['remote_table'] = rset['rows'][0]['table']
+
+            partitions_sql = ''
+            if 'is_partitioned' in data and data['is_partitioned']:
+                data['relkind'] = 'p'
+                # create partition scheme
+                data['partition_scheme'] = self.get_partition_scheme(data)
+                partitions_sql = self.get_partitions_sql(data)
+
+            SQL = render_template("/".join([self.table_template_path,
+                                            'create.sql']),
+                                  data=data, conn=self.conn)
+
+            # Append SQL for partitions
+            SQL += '\n' + partitions_sql
+
+        SQL = re.sub('\n{2,}', '\n\n', SQL)
+        SQL = SQL.strip('\n')
+
+        return SQL, data['name'] if 'name' in data else old_data['name']
+
+    def update(self, gid, sid, did, scid, tid, data, res, parent_id=None):
+        """
+        This function will update an existing table object
+
+         Args:
+           gid: Server Group ID
+           sid: Server ID
+           did: Database ID
+           scid: Schema ID
+           tid: Table ID
+           data: Data to update
+           res: Table properties
+           parent_id: parent table id if current table is partition of parent
+                    table else none
+        """
+        try:
+            relkind = res['relkind'] if 'relkind' in res else ''
+            SQL, name = self.get_sql(did, scid, tid, data, res)
+
+            SQL = SQL.strip('\n').strip(' ')
+            status, rest = self.conn.execute_scalar(SQL)
+            if not status:
+                return internal_server_error(errormsg=rest)
+
+            SQL = render_template("/".join([self.table_template_path,
+                                  'get_schema_oid.sql']), tid=tid)
+            status, rest = self.conn.execute_2darray(SQL)
+            if not status:
+                return internal_server_error(errormsg=rest)
+
+            if not parent_id:
+                parent_id = scid
+
+            return jsonify(
+                node=self.blueprint.generate_browser_node(
+                    tid,
+                    parent_id,
+                    name,
+                    icon="icon-partition" if ('is_partitioned' in res['rows'][0] and
+                                             res['rows'][0]['is_partitioned']) or
+                                             self.node_type == 'partition' else "icon-table",
+                    is_partitioned=True if relkind == 'p' else False,
+                    parent_schema_id=scid,
+                    schema_id=rest['rows'][0]['scid'],
+                    schema_name=rest['rows'][0]['nspname']
+                )
+            )
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    def properties(self, gid, sid, did, scid, tid, res):
+        """
+        This function will show the properties of the selected table node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did:  Database ID
+            scid: Schema ID
+            scid: Schema ID
+            tid: Table ID
+            res: Table/Partition table properties
+
+        Returns:
+            JSON of selected table node
+        """
+        data = res['rows'][0]
+
+        data['vacuum_settings_str'] = ""
+
+        if data['table_vacuum_settings_str'] is not None:
+            data['vacuum_settings_str'] += data[
+                'table_vacuum_settings_str'].replace(',', '\n')
+
+        if data['toast_table_vacuum_settings_str'] is not None:
+            data['vacuum_settings_str'] += '\n' + '\n'.join(
+                ['toast_' + setting for setting in data[
+                    'toast_table_vacuum_settings_str'
+                ].split(',')]
+            )
+        data['vacuum_settings_str'] = data[
+            'vacuum_settings_str'
+        ].replace("=", " = ")
+
+        data = self._formatter(did, scid, tid, data)
+
+        # Fetch partition of this table if it is partitioned table.
+        if 'is_partitioned' in data and data['is_partitioned']:
+            # get the partition type
+            data['partition_type'] = data['partition_scheme'].split()[0].lower()
+
+            partitions = []
+            SQL = render_template("/".join([self.partition_template_path,
+                                            'nodes.sql']),
+                                  scid=scid, tid=tid)
+            status, rset = self.conn.execute_2darray(SQL)
+            if not status:
+                return internal_server_error(errormsg=rset)
+
+            for row in rset['rows']:
+                partition_name = row['name']
+                # if schema name is different then display schema
+                # qualified name on UI.
+                if data['schema'] != row['schema_name']:
+                    partition_name = row['schema_name'] + '.' + row['name']
+
+                if data['partition_type'] == 'range':
+                    range_part = row['partition_value'].split(
+                            'FOR VALUES FROM (')[1].split(') TO')
+                    range_from = range_part[0]
+                    range_to = range_part[1][2:-1]
+
+                    partitions.append({
+                        'oid': row['oid'],
+                        'partition_name': partition_name,
+                        'values_from': range_from,
+                        'values_to': range_to
+                    })
+                else:
+                    range_part = \
+                        row['partition_value'].split('FOR VALUES IN (')[1]
+
+                    range_in = range_part[:-1]
+                    partitions.append({
+                        'oid': row['oid'],
+                        'partition_name': partition_name,
+                        'values_in': range_in
+                    })
+
+            data['partitions'] = partitions
+
+        return ajax_response(
+            response=data,
+            status=200
+        )
+
+    def get_partitions_sql(self, partitions):
+        """
+        This function will iterate all the partitions and create SQL.
+
+        :param partitions: List of partitions
+        """
+        sql = ''
+
+        for row in partitions['partitions']:
+            part_data = dict()
+            part_data['partitioned_table_name'] = partitions['name']
+            part_data['parent_schema'] = partitions['schema']
+
+            if 'is_attach' in row and row['is_attach']:
+                schema_name, table_name = \
+                    self.get_schema_and_table_name(row['partition_name'])
+
+                part_data['schema'] = schema_name
+                part_data['name'] = table_name
+            else:
+                part_data['schema'] = partitions['schema']
+                part_data['relispartition'] = True
+                part_data['name'] = row['partition_name']
+
+            if partitions['partition_type'] == 'range':
+                range_from = row['values_from'].split(',')
+                range_to = row['values_to'].split(',')
+
+                from_str = ', '.join("{0}".format(item) for item in range_from)
+                to_str = ', '.join("{0}".format(item) for item in range_to)
+
+                part_data['partition_value'] = 'FOR VALUES FROM (' + from_str \
+                                               + ') TO (' + to_str + ')'
+            else:
+                range_in = row['values_in'].split(',')
+                in_str = ', '.join("{0}".format(item) for item in range_in)
+                part_data['partition_value'] = 'FOR VALUES IN (' + in_str + ')'
+
+            if 'is_attach' in row and row['is_attach']:
+                partition_sql = render_template(
+                    "/".join([self.partition_template_path, 'attach.sql']),
+                    data=part_data, conn=self.conn
+                )
+            else:
+                partition_sql = render_template(
+                    "/".join([self.partition_template_path, 'create.sql']),
+                    data=part_data, conn=self.conn
+                )
+
+            sql += partition_sql + '\n'
+
+        return sql
+
+    def truncate(self, gid, sid, did, scid, tid, res):
+        """
+        This function will truncate the table object
+
+         Args:
+           gid: Server Group ID
+           sid: Server ID
+           did: Database ID
+           scid: Schema ID
+           tid: Table ID
+        """
+        # Below will decide if it's simple drop or drop with cascade call
+        data = request.form if request.form else json.loads(
+            request.data, encoding='utf-8'
+        )
+        # Convert str 'true' to boolean type
+        is_cascade = json.loads(data['cascade'])
+
+        data = res['rows'][0]
+
+        SQL = render_template("/".join([self.table_template_path,
+                                        'truncate.sql']),
+                              data=data, cascade=is_cascade)
+        status, res = self.conn.execute_scalar(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        return make_json_response(
+            success=1,
+            info=gettext("Table truncated"),
+            data={
+                'id': tid,
+                'scid': scid
+            }
+        )
+
+    def get_schema_and_table_name(self, tid):
+        """
+        This function will fetch the schema qualified name of the
+        given table id.
+
+        :param tid: Table Id.
+        """
+        # Get schema oid
+        status, scid = self.conn.execute_scalar(
+            render_template("/".join([self.table_template_path,
+                                      'get_schema_oid.sql']), tid=tid))
+        if not status:
+            return internal_server_error(errormsg=scid)
+
+        # Fetch schema name
+        status, schema_name = self.conn.execute_scalar(
+            render_template("/".join([self.table_template_path,
+                                      'get_schema.sql']), conn=self.conn,
+                            scid=scid)
+        )
+        if not status:
+            return internal_server_error(errormsg=schema_name)
+
+        # Fetch Table name
+        status, table_name = self.conn.execute_scalar(
+            render_template(
+                "/".join([self.table_template_path, 'get_table.sql']),
+                conn=self.conn, scid=scid, tid=tid
+            )
+        )
+        if not status:
+            return internal_server_error(errormsg=table_name)
+
+        return schema_name, table_name
+
+    @staticmethod
+    def convert_length_precision_to_string(data):
+        """
+        This function is used to convert length & precision to string
+        to handle case like when user gives 0 as length
+
+        Args:
+            data: Data from client
+
+        Returns:
+            Converted data
+        """
+        if 'attlen' in data and data['attlen'] is not None:
+            data['attlen'] = str(data['attlen'])
+        if 'attprecision' in data and data['attprecision'] is not None:
+            data['attprecision'] = str(data['attprecision'])
+        return data
+
+    def search_coveringindex(self, tid, cols):
+        """
+
+        Args:
+          tid: Table id
+          cols: column list
+
+        Returns:
+
+        """
+
+        cols = set(cols)
+        SQL = render_template("/".join([self.foreign_key_template_path,
+                                        'get_constraints.sql']),
+                              tid=tid)
+        status, constraints = self.conn.execute_dict(SQL)
+
+        if not status:
+            raise Exception(constraints)
+
+        for costrnt in constraints['rows']:
+
+            sql = render_template(
+                "/".join([self.foreign_key_template_path, 'get_cols.sql']),
+                cid=costrnt['oid'],
+                colcnt=costrnt['indnatts'])
+            status, rest = self.conn.execute_dict(sql)
+
+            if not status:
+                return internal_server_error(errormsg=rest)
+
+            indexcols = set()
+            for r in rest['rows']:
+                indexcols.add(r['column'].strip('"'))
+
+            if len(cols - indexcols) == len(indexcols - cols) == 0:
+                return costrnt["idxname"]
+
+        return None
\ No newline at end of file
diff --git a/web/pgadmin/browser/utils.py b/web/pgadmin/browser/utils.py
index acc1662..3bc8800 100644
--- a/web/pgadmin/browser/utils.py
+++ b/web/pgadmin/browser/utils.py
@@ -321,6 +321,11 @@ class PGChildNodeView(NodeView):
             if isinstance(module, PGChildModule):
                 if manager is not None and \
                         module.BackendSupported(manager, **kwargs):
+                    # treat partition table as normal table.
+                    # replace tid with ptid and pop ptid from kwargs
+                    if 'ptid' in kwargs:
+                        ptid = kwargs.pop('ptid')
+                        kwargs['tid'] = ptid
                     nodes.extend(module.get_nodes(**kwargs))
             else:
                 nodes.extend(module.get_nodes(**kwargs))
diff --git a/web/pgadmin/static/js/backform.pgadmin.js b/web/pgadmin/static/js/backform.pgadmin.js
index a82205e..cd8f588 100644
--- a/web/pgadmin/static/js/backform.pgadmin.js
+++ b/web/pgadmin/static/js/backform.pgadmin.js
@@ -1022,16 +1022,16 @@
 
       var collection = this.model.get(data.name);
 
-      var cellEditing = function(args){
-        var self = this,
+      var cellEditing = function(args) {
+        var that = this,
           cell = args[0];
         // Search for any other rows which are open.
         this.each(function(m){
           // Check if row which we are about to close is not current row.
           if (cell.model != m) {
-            var idx = self.indexOf(m);
+            var idx = that.indexOf(m);
             if (idx > -1) {
-              var row = grid.body.rows[idx],
+              var row = self.grid.body.rows[idx],
                   editCell = row.$el.find(".subnode-edit-in-process").parent();
               // Only close row if it's open.
               if (editCell.length > 0){
@@ -1053,7 +1053,7 @@
       });
 
       // Render subNode grid
-      subNodeGrid = grid.render().$el;
+      subNodeGrid = self.grid.render().$el;
 
       // Combine Edit and Delete Cell
       if (data.canDelete && data.canEdit) {
@@ -1071,7 +1071,7 @@
                             data.canAddRow.apply(self, [self.model]) : true;
           if (canAddRow) {
               // Close any existing expanded row before adding new one.
-              _.each(grid.body.rows, function(row){
+              _.each(self.grid.body.rows, function(row){
                 var editCell = row.$el.find(".subnode-edit-in-process").parent();
                 // Only close row if it's open.
                 if (editCell.length > 0){
@@ -1100,7 +1100,7 @@
                 }
               }
 
-              $(grid.body.$el.find($("tr.new"))).removeClass("new")
+              $(self.grid.body.$el.find($("tr.new"))).removeClass("new")
               var m = new (data.model) (null, {
                 silent: true,
                 handler: collection,
@@ -1111,7 +1111,7 @@
               collection.add(m);
 
               var idx = collection.indexOf(m),
-                  newRow = grid.body.rows[idx].$el;
+                  newRow = self.grid.body.rows[idx].$el;
 
               newRow.addClass("new");
               $(newRow).pgMakeVisible('backform-tab');
@@ -1149,6 +1149,7 @@
   });
 
   var SubNodeCollectionControl = Backform.SubNodeCollectionControl = Backform.Control.extend({
+    row: Backgrid.Row,
     render: function() {
       var field = _.defaults(this.field.toJSON(), this.defaults),
           attributes = this.model.toJSON(),
@@ -1179,6 +1180,12 @@
       // Show Backgrid Control
       grid = (data.subnode == undefined) ? "" : this.showGridControl(data);
 
+      // Clean up first
+      this.$el.removeClass(Backform.hiddenClassname);
+
+      if (!data.visible)
+        this.$el.addClass(Backform.hiddenClassname);
+
       this.$el.html(grid).addClass(field.name);
       this.updateInvalid();
 
@@ -1246,7 +1253,9 @@
             name: "pg-backform-delete", label: "",
             cell: Backgrid.Extension.DeleteCell,
             editable: false, cell_priority: -1,
-            canDeleteRow: data.canDeleteRow
+            canDeleteRow: data.canDeleteRow,
+            customDeleteMsg: data.customDeleteMsg,
+            customDeleteTitle: data.customDeleteTitle
           });
       }
 
@@ -1303,6 +1312,7 @@
       var grid = self.grid = new Backgrid.Grid({
           columns: gridSchema.columns,
           collection: collection,
+          row: this.row,
           className: "backgrid table-bordered"
       });
 
diff --git a/web/pgadmin/static/js/backgrid.pgadmin.js b/web/pgadmin/static/js/backgrid.pgadmin.js
index 64f0cb6..d426a82 100644
--- a/web/pgadmin/static/js/backgrid.pgadmin.js
+++ b/web/pgadmin/static/js/backgrid.pgadmin.js
@@ -380,7 +380,8 @@
 
   var DeleteCell = Backgrid.Extension.DeleteCell = Backgrid.Cell.extend({
       defaults: _.defaults({
-        defaultDeleteMsg: 'Are you sure you wish to delete this row?'
+        defaultDeleteMsg: 'Are you sure you wish to delete this row?',
+        defaultDeleteTitle: 'Delete Row'
       }, Backgrid.Cell.prototype.defaults),
 
       /** @property */
@@ -399,8 +400,10 @@
         if (canDeleteRow) {
           var delete_msg = !_.isUndefined(this.column.get('customDeleteMsg')) ?
                            this.column.get('customDeleteMsg'): that.defaults.defaultDeleteMsg;
+          var delete_title = !_.isUndefined(this.column.get('customDeleteTitle')) ?
+                           this.column.get('customDeleteTitle'): that.defaults.defaultDeleteTitle;
           Alertify.confirm(
-            'Delete Row',
+            delete_title,
             delete_msg,
             function(evt) {
               that.model.collection.remove(that.model);
@@ -1378,6 +1381,58 @@
 
   _.extend(MomentCell.prototype, MomentFormatter.prototype.defaults);
 
+
+  Backgrid.Extension.StringDepCell = Backgrid.StringCell.extend({
+      initialize: function() {
+        Backgrid.StringCell.prototype.initialize.apply(this, arguments);
+        Backgrid.Extension.DependentCell.prototype.initialize.apply(this, arguments);
+      },
+      dependentChanged: function () {
+        this.$el.empty();
+
+        var self = this,
+            model = this.model,
+            column = this.column,
+            editable = this.column.get("editable");
+
+        this.render();
+
+        is_editable = _.isFunction(editable) ? !!editable.apply(column, [model]) : !!editable;
+        setTimeout(function() {
+          self.$el.removeClass("editor");
+          if (is_editable){ self.$el.addClass("editable"); }
+          else { self.$el.removeClass("editable"); }
+        }, 10);
+
+        this.delegateEvents();
+        return this;
+      },
+      remove: Backgrid.Extension.DependentCell.prototype.remove
+    });
+
+  Backgrid.Extension.Select2DepCell = Backgrid.Extension.Select2Cell.extend({
+    initialize: function() {
+      Backgrid.Extension.Select2Cell.prototype.initialize.apply(this, arguments);
+      Backgrid.Extension.DependentCell.prototype.initialize.apply(this, arguments);
+    },
+
+    dependentChanged: function () {
+      var model = this.model;
+      var column = this.column;
+      editable = this.column.get("editable");
+
+      this.render();
+
+      is_editable = _.isFunction(editable) ? !!editable.apply(column, [model]) : !!editable;
+      if (is_editable){ this.$el.addClass("editable"); }
+      else { this.$el.removeClass("editable"); }
+
+      this.delegateEvents();
+      return this;
+    },
+    remove: Backgrid.Extension.DependentCell.prototype.remove
+  });
+
   return Backgrid;
 
 }));
diff --git a/web/pgadmin/tools/backup/templates/backup/js/backup.js b/web/pgadmin/tools/backup/templates/backup/js/backup.js
index 874bbeb..8e96d1b 100644
--- a/web/pgadmin/tools/backup/templates/backup/js/backup.js
+++ b/web/pgadmin/tools/backup/templates/backup/js/backup.js
@@ -269,7 +269,7 @@ TODO LIST FOR BACKUP:
 
         // Define list of nodes on which backup context menu option appears
         var backup_supported_nodes = [
-              'database', 'schema', 'table'
+              'database', 'schema', 'table', 'partition'
             ];
 
         /**
diff --git a/web/pgadmin/tools/datagrid/__init__.py b/web/pgadmin/tools/datagrid/__init__.py
index f4629fc..f8a7e4c 100644
--- a/web/pgadmin/tools/datagrid/__init__.py
+++ b/web/pgadmin/tools/datagrid/__init__.py
@@ -112,6 +112,10 @@ def initialize_datagrid(cmd_type, obj_type, sid, did, obj_id):
         return internal_server_error(errormsg=str(msg))
 
     try:
+        # if object type is partition then it is nothing but a table.
+        if obj_type == 'partition':
+            obj_type = 'table'
+
         # Get the object as per the object type
         command_obj = ObjectRegistry.get_object(obj_type, conn_id=conn_id, sid=sid,
                                                 did=did, obj_id=obj_id, cmd_type=cmd_type,
diff --git a/web/pgadmin/tools/datagrid/templates/datagrid/js/datagrid.js b/web/pgadmin/tools/datagrid/templates/datagrid/js/datagrid.js
index 7c48ea5..63067f0 100644
--- a/web/pgadmin/tools/datagrid/templates/datagrid/js/datagrid.js
+++ b/web/pgadmin/tools/datagrid/templates/datagrid/js/datagrid.js
@@ -28,7 +28,7 @@ define([
         // Define list of nodes on which view data option appears
         var supported_nodes = [
            'table', 'view', 'mview',
-           'foreign-table', 'catalog_object'
+           'foreign-table', 'catalog_object', 'partition'
         ],
 
         /* Enable/disable View data menu in tools based
diff --git a/web/pgadmin/tools/maintenance/templates/maintenance/js/maintenance.js b/web/pgadmin/tools/maintenance/templates/maintenance/js/maintenance.js
index f3823f9..54ad762 100644
--- a/web/pgadmin/tools/maintenance/templates/maintenance/js/maintenance.js
+++ b/web/pgadmin/tools/maintenance/templates/maintenance/js/maintenance.js
@@ -147,7 +147,7 @@ define([
 
         var maintenance_supported_nodes = [
               'database', 'table', 'primary_key',
-              'unique_constraint', 'index'
+              'unique_constraint', 'index', 'partition'
             ];
 
         /**
@@ -177,7 +177,7 @@ define([
 
         var menus = [{
           name: 'maintenance', module: this,
-          applies: ['tools'], callback: 'callback_maintenace',
+          applies: ['tools'], callback: 'callback_maintenance',
           priority: 10, label: gettext('Maintenance...'),
           icon: 'fa fa-wrench', enable: menu_enabled
         }];
@@ -187,7 +187,7 @@ define([
           menus.push({
             name: 'maintenance_context_' + maintenance_supported_nodes[idx],
             node: maintenance_supported_nodes[idx], module: this,
-            applies: ['context'], callback: 'callback_maintenace',
+            applies: ['context'], callback: 'callback_maintenance',
             priority: 10, label: gettext('Maintenance...'),
             icon: 'fa fa-wrench', enable: menu_enabled
           });
@@ -198,7 +198,7 @@ define([
       /*
         Open the dialog for the maintenance functionality
       */
-      callback_maintenace: function(args, item) {
+      callback_maintenance: function(args, item) {
         var i = item || pgBrowser.tree.selected(),
           server_data = null;
 
@@ -317,7 +317,10 @@ define([
                   if (treeInfo.schema != undefined) {
                     schema = treeInfo.schema._label;
                   }
-                  if (treeInfo.table != undefined) {
+
+                  if (treeInfo.partition != undefined) {
+                    table = treeInfo.partition._label;
+                  } else if  (treeInfo.table != undefined) {
                     table = treeInfo.table._label;
                   }
 
diff --git a/web/pgadmin/tools/restore/templates/restore/js/restore.js b/web/pgadmin/tools/restore/templates/restore/js/restore.js
index f821162..2f212ac 100644
--- a/web/pgadmin/tools/restore/templates/restore/js/restore.js
+++ b/web/pgadmin/tools/restore/templates/restore/js/restore.js
@@ -227,7 +227,8 @@ define([
         var restore_supported_nodes = [
               'database', 'schema',
               'table', 'function',
-              'trigger', 'index'
+              'trigger', 'index',
+              'partition'
             ];
 
         /**
diff --git a/web/pgadmin/tools/user_management/templates/user_management/js/user_management.js b/web/pgadmin/tools/user_management/templates/user_management/js/user_management.js
index f11b552..68e51f5 100644
--- a/web/pgadmin/tools/user_management/templates/user_management/js/user_management.js
+++ b/web/pgadmin/tools/user_management/templates/user_management/js/user_management.js
@@ -25,34 +25,7 @@ define([
             // How long to wait after typing has stopped before searching can start
             wait: 150
           }));
-        },
-        StringDepCell = Backgrid.StringCell.extend({
-          initialize: function() {
-            Backgrid.StringCell.prototype.initialize.apply(this, arguments);
-            Backgrid.Extension.DependentCell.prototype.initialize.apply(this, arguments);
-          },
-          dependentChanged: function () {
-            this.$el.empty();
-
-            var self = this,
-              model = this.model,
-              column = this.column,
-              editable = this.column.get("editable");
-
-            this.render();
-
-            is_editable = _.isFunction(editable) ? !!editable.apply(column, [model]) : !!editable;
-            setTimeout(function() {
-              self.$el.removeClass("editor");
-              if (is_editable){ self.$el.addClass("editable"); }
-              else { self.$el.removeClass("editable"); }
-            }, 10);
-
-            this.delegateEvents();
-            return this;
-          },
-          remove: Backgrid.Extension.DependentCell.prototype.remove
-        });
+        }
 
     pgBrowser.UserManagement  = {
       init: function() {
@@ -83,7 +56,7 @@ define([
             schema: [
             {
               id: 'email', label: gettext('Email'), type: 'text',
-              cell:StringDepCell, cellHeaderClasses:'width_percent_30',
+              cell:Backgrid.Extension.StringDepCell, cellHeaderClasses:'width_percent_30',
               deps: ['id'],
               editable: function(m) {
                 if(m instanceof Backbone.Collection) {
diff --git a/web/pgadmin/utils/__init__.py b/web/pgadmin/utils/__init__.py
index 550bad2..701a38c 100644
--- a/web/pgadmin/utils/__init__.py
+++ b/web/pgadmin/utils/__init__.py
@@ -29,6 +29,7 @@ class PgAdminModule(Blueprint):
         kwargs.setdefault('template_folder', 'templates')
         kwargs.setdefault('static_folder', 'static')
         self.submodules = []
+        self.parentmodules = []
 
         super(PgAdminModule, self).__init__(name, import_name, **kwargs)
 
@@ -59,6 +60,8 @@ class PgAdminModule(Blueprint):
         super(PgAdminModule, self).register(app, options, first_registration)
 
         for module in self.submodules:
+            if first_registration:
+                module.parentmodules.append(self)
             app.register_blueprint(module)
 
     def get_own_stylesheets(self):

---

From: Harshal Dhumal <[email-masked]>
Date: Fri, 23 Jun 2017 18:57:48 +0530

P.S. Akshay is working on test cases for same and will send separate patch.

-- 
*Harshal Dhumal*
*Sr. Software Engineer*

On Fri, Jun 23, 2017 at 6:55 PM, Harshal Dhumal <
[email-masked]> wrote:

> This patch includes all the work done by Akashy and support for child
> nodes (constraints, rules, index, triggers and partition itself ) under
> partition node.
>
>
> Thanks,
>
> --
> *Harshal Dhumal*
> *Sr. Software Engineer*
>
>> It would improve the experience if the 'In' column was removed when a
>> user selects 'Range' partitions then. And then if a user is creating a
>> 'List' partition, 'From/To' should be hidden. In this case, 'From/To' and
>> 'In' are dependent on that first drop down step, so seeing 'In' while on
>> 'Range partitions' (and 'From/To' on 'List partitions') is not providing
>> any value.
>>
>>
>>>

---

From: Dave Page <[email-masked]>
Date: Fri, 23 Jun 2017 16:24:52 +0100

Thanks - Ashesh, can you review/commit this please? Additional reviews
welcome as well of course.

---

From: Akshay Joshi <[email-masked]>
Date: Sun, 25 Jun 2017 18:52:51 +0530

Hi All

I have implemented test cases for partitioned table. Attached is the patch
file. Please review it and if it looks good then please commit it.

On Fri, Jun 23, 2017 at 8:54 PM, Dave Page <[email-masked]> wrote:

>>> Typically, disabling a UI element is useful when that disabled UI
>>> element also provides some context or value while disabled. In this case,
>>> I'm not sure it is.
>>>
>>> If hiding options isn't possible, providing some text in the fields
>>> (like N/A or --) would be helpful.
>>>
>>>
>>>
>>>>

---

From: Ashesh Vashi <[email-masked]>
Date: Thu, 29 Jun 2017 17:02:42 +0530

Hi Harshal,

These are initial review comments.
1.
Please share a separate patch for generic code changes from this patch for
the following files:
-
web/pgadmin/tools/user_management/templates/user_management/js/user_management.js
- web/pgadmin/static/js/backform.pgadmin.js
- web/pgadmin/static/js/backgrid.pgadmin.js

This should be committed as separate functionality, and should not be part
of this commit.

2.
Please put a space after a colon (:) in javascript object definition.
i.e.
{cell: Backgrid.Extension.StringDepCell, cellHeaderClasses:
'width_percent_30'}

3.
Conversion of ptid (partition table OID) to tid (table OID) for its
children must not be in 'web/pgadmin/browser/utils.py'  file.
Please create a inherited class of PGChildNodeView, and extend the
functionality in it, and use it as the base class for all children of table.

I will keep you posted for further review comments.

--

Thanks & Regards,

Ashesh Vashi
EnterpriseDB INDIA: Enterprise PostgreSQL Company
<http://www.enterprisedb.com>

*http://www.linkedin.com/in/asheshvashi*
<http://www.linkedin.com/in/asheshvashi>

---

From: Ashesh Vashi <[email-masked]>
Date: Fri, 30 Jun 2017 12:46:19 +0530

On Thu, Jun 29, 2017 at 5:02 PM, Ashesh Vashi <[email-masked]

> - web/pgadmin/tools/user_management/templates/user_management/
> js/user_management.js
4.
URL definition of the javascript for tables & partition tables utility must
be exposed from the table/partition table module, and not from the database
module.
i.e.
No changes should be done in the database module for this feature. Hence -
I don't expect any change in the file:
web/pgadmin/browser/server_groups/servers/databases/__init__.py

-- Thanks, Ashesh

---

From: Akshay Joshi <[email-masked]>
Date: Mon, 3 Jul 2017 15:14:35 +0530

Hi Ashesh

On Fri, Jun 30, 2017 at 12:46 PM, Ashesh Vashi <
[email-masked]> wrote:

> On Thu, Jun 29, 2017 at 5:02 PM, Ashesh Vashi <
>> Please share a separate patch for generic code changes from this patch
>> for the following files:
>> This should be committed as separate functionality, and should not be
>> part of this commit.
>>
>
      Committed.

> URL definition of the javascript for tables & partition tables utility
> must be exposed from the table/partition table module, and not from the
> database module.
> i.e.

    Fixed.

   Apart from above this patch contains test cases as well.

>>>>>    I think we should not duplicate that data as we already have all
>>>>> the information on "Columns" tab and by providing drop down user can select
>>>>> columns from there only.
>>>>>
>>>>>>
>>>>>    'Mode control' is a switch control in subnode control that should
>>>>> be "Mode switch control". 'Create Mode' is when user creates the new table
>>>>> by clicking create-> table and 'Edit Mode' is when user open the properties
>>>> I see. Describing these various states is great in case a user needs
>>>> it. What are your thoughts on having it live in the documentation of
>>>> pgAdmin4 rather than in the dialog? This seems to be the established
>>>> pattern for all other explanations.
>>>>
>>>>>
>>>
>>
>

---

From: Ashesh Vashi <[email-masked]>
Date: Tue, 4 Jul 2017 11:35:37 +0530

Hi Akshay,

I found couple of issues, and fixed them in the updated patch.
- Validation issue resolved
- Disable "Is primary key?" in columns subnode for partitioned table.

Review comments:
- Add note about - what value is expected in 'from', and 'to' field. Please
add an example in the note.
- PostgreSQL allows to create partioned table using 'OF TYPE'.
- We should add collation, and opclass for 'partition by statement (We can
do this later).
- Refresh the browser tree after creating, attaching, and detaching the
partitions.

-- Thanks, Ashesh

On Mon, Jul 3, 2017 at 3:14 PM, Akshay Joshi <[email-masked]>
wrote:

>> No changes should be done in the database module for this feature. Hence
>> - I don't expect any change in the file:

---

From: Ashesh Vashi <[email-masked]>
Date: Tue, 4 Jul 2017 12:03:09 +0530

Hi Akshay,

Please find the patch, generated using the 'git --binary' command for
inclusion of the icons.

--

Thanks & Regards,

Ashesh Vashi

On Tue, Jul 4, 2017 at 11:35 AM, Ashesh Vashi <[email-masked]

> - Add note about - what value is expected in 'from', and 'to' field.
> Please add an example in the note.
> On Mon, Jul 3, 2017 at 3:14 PM, Akshay Joshi <
>>>>>> Can a user select more than one key type? The use case I can see
>>>>>> where hiding 'Columns' or 'Expressions' would fail is if someone can create
>>>>>> an expression key type and a column key type.
>>>>>>
>>>>>>>> For the NoteControl on the bottom, what do 'Mode Control' or
>>>>>>>> 'Attach Mode' refer to? And how can I tell the difference between 'Create
>>>>>>>> Mode' and 'Edit Mode'?
>>>>>>>>
>>>>>>>

---

From: Akshay Joshi <[email-masked]>
Date: Tue, 4 Jul 2017 15:52:00 +0530

Hi Ashesh

   Fixed.

> - PostgreSQL allows to create partioned table using 'OF TYPE'.
   I have tried to add support of the above, but there are couple of issues
in existing functionality. I'll create one RM case for this. Once that is
fixed, I'll add support.

> - We should add collation, and opclass for 'partition by statement (We can
   It requires some R&D, so we will add it later.

> - Refresh the browser tree after creating, attaching, and detaching the
    As per our discussion, I have modified the logic and return oid and
schema id for all the created/attached/detached partitions.

 Attached is the latest patch, can you please review it.

---

From: Ashesh Vashi <[email-masked]>
Date: Tue, 4 Jul 2017 15:59:02 +0530

>    Apart from above this patch contains test cases as well.
One bug:
If I rename the partitioned table from the properties dialog, and attach a
partition at the same time, then - it generates wrong modified queries.

-- Thanks, Ashesh

---

From: Akshay Joshi <[email-masked]>
Date: Tue, 4 Jul 2017 17:42:08 +0530

> partition at the same time, then - it generates wrong modified queries.

   Fixed. Attached is the latest patch

---

From: Ashesh Vashi <[email-masked]>
Date: Fri, 7 Jul 2017 09:01:06 +0530

Hi Akshay,

Please find the updated patch with the refresh logic.

Please create redmine cases for the following todos:
- PostgreSQL allows to create partioned table using 'OF TYPE'.
- We should add collation, and opclass for 'partition by statement.
- Allow to attach/create/detach partitions on the partition table directly,
open the subcontrol in dialog for the same. (We support detaching an
individual partitions, not the bulk operation.)

Please review it, and commit it (if possible).

--

Thanks & Regards,

Ashesh Vashi

On Tue, Jul 4, 2017 at 5:42 PM, Akshay Joshi <[email-masked]>
wrote:

>
>
> On Tue, Jul 4, 2017 at 3:59 PM, Ashesh Vashi <
>>>> No changes should be done in the database module for this feature.
>>>> Hence - I don't expect any change in the file:
>> If I rename the partitioned table from the properties dialog, and attach
>> a partition at the same time, then - it generates wrong modified queries.
>>
>
>>>>>>>>> Looks good. I noticed people clicking back and forth to the
>>>>>>>>> columns tab to remember which columns they've created while filling out the
>>>>>>> Ah I see what you mean. What I meant was that the column would
>>>>>>> change depending on if someone selects Column or Expressions from the
>>>>>>> dropdown
>>>>>>> Disabling a feature is one way to guide user behavior, but it
>>>>>>> doesn't provide enough context for someone to understand why it's disabled.
>>>>>>> It's better to only display what is absolutely necessary and hide fields
>>>>>>> that are unrelated to the workflow.
>>>>>>>
>>>>>>> It would improve the experience if the 'In' column was removed when
>>>>>>> a user selects 'Range' partitions then. And then if a user is creating a
>>>>>>>>    'Mode control' is a switch control in subnode control that
>>>>>>>> should be "Mode switch control". 'Create Mode' is when user creates the new
>>>>>>>> table by clicking create-> table and 'Edit Mode' is when user open the
>>>>>>>> properties dialog for the existing table. In case of 'Edit Mode' there are
>>>>>>>> two ways user can create/attach partitions. In Attach mode we will identify
>>>>>>>> and list down the suitable tables to be attached.
>>>>>>>>
>>>>>>>

---

From: Akshay Joshi <[email-masked]>
Date: Fri, 7 Jul 2017 12:05:26 +0530

Hi Ashesh

On Fri, Jul 7, 2017 at 9:01 AM, Ashesh Vashi <[email-masked]>
wrote:

> - Allow to attach/create/detach partitions on the partition table
> directly, open the subcontrol in dialog for the same. (We support detaching
> an individual partitions, not the bulk operation.)
>

    We are supporting detach as bulk operation from parent table. I'll
create RM's for the above todo's.

> Please review it, and commit it (if possible).

    Code looks good to me. I have committed the code.
    Thanks Harshal and Ashesh for your support to finish this feature.

> On Tue, Jul 4, 2017 at 5:42 PM, Akshay Joshi <
>>>>>> Please share a separate patch for generic code changes from this
>>>>>> patch for the following files:
>>>>>>> This patch includes all the work done by Akashy and support for
>>>>>>> child nodes (constraints, rules, index, triggers and partition itself )
>>>>>>> under partition node.
>>>>>>>
>>>>>>>
>>>>>>> Thanks,
>>>>>>>
>>>>>>> --
>>>>>>> *Harshal Dhumal*
>>>>>>> *Sr. Software Engineer*
>>>>>>>
>>>>>>>>>    I think we should not duplicate that data as we already have
>>>>>>>>> all the information on "Columns" tab and by providing drop down user can
>>>>>>>>> select columns from there only.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Also, I think the fields someone sees after selecting the Key
>>>>>>>>>> type needs to depend on what they select. Seeing both Column and
>>>>>>>>>> Expressions type field might lead someone to think they need to fill out
>>>>>>>>>> both fields.
>>>>>>>>>>
>>>>>>>>>
>>>>>>>> I see. Describing these various states is great in case a user
>>>>>>>> needs it. What are your thoughts on having it live in the documentation of