diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 002319e..a41be00 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/heapam_xlog.h"
 #include "access/multixact.h"
 #include "access/reloptions.h"
 #include "access/relscan.h"
@@ -79,6 +80,7 @@
 #include "storage/lmgr.h"
 #include "storage/lock.h"
 #include "storage/predicate.h"
+#include "storage/procarray.h"
 #include "storage/smgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -392,9 +394,13 @@ static void change_owner_fix_column_acls(Oid relationOid,
 							 Oid oldOwnerId, Oid newOwnerId);
 static void change_owner_recurse_to_sequences(Oid relationOid,
 								  Oid newOwnerId, LOCKMODE lockmode);
+static void change_relreadonly(Oid relOid, bool flag);
 static ObjectAddress ATExecClusterOn(Relation rel, const char *indexName,
 				LOCKMODE lockmode);
 static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
+static void ATPrepSetReadOnly(Relation rel, LOCKMODE lockmode);
+static void ATExecSetReadOnly(Relation rel);
+static void ATExecSetReadWrite(Relation rel);
 static bool ATPrepChangePersistence(Relation rel, bool toLogged);
 static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
 					char *tablespacename, LOCKMODE lockmode);
@@ -3006,6 +3012,9 @@ AlterTableGetLockLevel(List *cmds)
 				cmd_lockmode = ShareUpdateExclusiveLock;
 				break;
 
+			case AT_SetReadOnly:
+			case AT_SetReadWrite:
+				cmd_lockmode = ShareLock;
 			case AT_SetLogged:
 			case AT_SetUnLogged:
 				cmd_lockmode = AccessExclusiveLock;
@@ -3254,6 +3263,16 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			}
 			pass = AT_PASS_MISC;
 			break;
+		case AT_SetReadOnly:	/* SET READ ONLY */
+			ATSimplePermissions(rel, ATT_TABLE);
+			/* Performs freezing all tuples */
+			ATPrepSetReadOnly(rel, lockmode);
+			pass = AT_PASS_MISC;
+			break;
+		case AT_SetReadWrite:	/* SET READ WRITE */
+			ATSimplePermissions(rel, ATT_TABLE);
+			pass = AT_PASS_MISC;
+			break;
 		case AT_AddOids:		/* SET WITH OIDS */
 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 			if (!rel->rd_rel->relhasoids || recursing)
@@ -3548,6 +3567,14 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_SetLogged:		/* SET LOGGED */
 		case AT_SetUnLogged:	/* SET UNLOGGED */
 			break;
+		case AT_SetReadOnly:	/* SET READ ONLY */
+			/* Update system catalog to change flag to true */
+			ATExecSetReadOnly(rel);
+			break;
+		case AT_SetReadWrite:	/* SET READ WRITE */
+			/* Update system catalog to change flag to false */
+			ATExecSetReadWrite(rel);
+			break;
 		case AT_AddOids:		/* SET WITH OIDS */
 			/* Use the ADD COLUMN code, unless prep decided to do nothing */
 			if (cmd->def != NULL)
@@ -9168,6 +9195,192 @@ ATExecDropCluster(Relation rel, LOCKMODE lockmode)
 }
 
 /*
+ * ALTER TABLE SET READ ONLY
+ *
+ * We have to do freezing the all live tuples at first, and then update
+ * relreadonly column of pg_class system catalog to true.
+ * Processing freezing tuples always scan all pages and freeze tuple
+ * one by one like VACUUM FREEZE.
+ */
+static void
+ATPrepSetReadOnly(Relation rel, LOCKMODE lockmode)
+{
+	BlockNumber	nblocks,
+				blkno;
+	OffsetNumber	offnum;
+	HeapTupleData	tuple;
+	xl_heap_freeze_tuple *frozen;
+	int	nfrozen;
+	int	i;
+	TransactionId	oldestxmin, freezelimit;
+	MultiXactId		mxactcutoff;
+
+	PreventTransactionChain(true, "ALTER TABLE SET READ ONLY");
+
+	relation_open(RelationGetRelid(rel), lockmode);
+	nblocks = RelationGetNumberOfBlocks(rel);
+	oldestxmin = freezelimit = GetOldestXmin(rel, true);
+	mxactcutoff = ReadNextMultiXactId();
+	frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
+
+	for (blkno = 0; blkno < nblocks; blkno++)
+	{
+		Buffer			buf;
+		Page			page;
+		OffsetNumber	maxoff;
+
+		nfrozen = 0;
+
+		buf = ReadBuffer(rel, blkno);
+		LockBuffer(buf, BUFFER_LOCK_SHARE);
+		page = BufferGetPage(buf);
+		maxoff = PageGetMaxOffsetNumber(page);
+
+		for (offnum = FirstOffsetNumber;
+			 offnum <= maxoff;
+			 offnum = OffsetNumberNext(offnum))
+		{
+			ItemId	itemid;
+
+			itemid = PageGetItemId(page, offnum);
+
+			/* Skip unused items */
+			if (!ItemIdIsUsed(itemid) |
+				ItemIdIsRedirected(itemid) |
+				ItemIdIsDead(itemid))
+				continue;
+
+			Assert(ItemIdIsNormal(itemid));
+
+			ItemPointerSet(&(tuple.t_self), blkno, offnum);
+			tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
+			tuple.t_len = ItemIdGetLength(itemid);
+			tuple.t_tableOid = RelationGetRelid(rel);
+
+			/*
+			 * Check state of each tuple by using mechanism which is used in
+			 * VACUUM. We are interested in only live tuple, so skip dead tuple.
+			 */
+			switch(HeapTupleSatisfiesVacuum(&tuple, oldestxmin, buf))
+			{
+				case HEAPTUPLE_DEAD:
+					break;
+				case HEAPTUPLE_LIVE:
+				case HEAPTUPLE_RECENTLY_DEAD:
+				case HEAPTUPLE_INSERT_IN_PROGRESS:
+				case HEAPTUPLE_DELETE_IN_PROGRESS:
+					if (heap_prepare_freeze_tuple(tuple.t_data, freezelimit,
+												  mxactcutoff, &frozen[nfrozen]))
+						frozen[nfrozen++].offset = offnum;
+					break;
+				default:
+					elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
+					break;
+			}
+		}
+
+		/*
+		 * If there are any tuple which is needed to freeze then we do that.
+		 * Also if relation needs WAL, we do logging WAL as well.
+		 */
+		if (nfrozen > 0)
+		{
+			START_CRIT_SECTION();
+			MarkBufferDirty(buf);
+
+			for (i = 0; i < nfrozen; i++)
+			{
+				ItemId	itemid;
+				HeapTupleHeader	htup;
+
+				itemid = PageGetItemId(page, frozen[i].offset);
+				htup = (HeapTupleHeader) PageGetItem(page, itemid);
+
+				heap_execute_freeze_tuple(htup, &frozen[i]);
+			}
+
+			if (RelationNeedsWAL(rel))
+			{
+				XLogRecPtr	recptr;
+
+				recptr = log_heap_freeze(rel, buf, freezelimit, frozen, nfrozen);
+				PageSetLSN(page, recptr);
+			}
+
+			END_CRIT_SECTION();
+		}
+
+		UnlockReleaseBuffer(buf);
+	}
+
+	pfree(frozen);
+	relation_close(rel, lockmode);
+}
+
+/*
+ * It's pharse 2 in ALTER TABLE SET READ ONLY here.
+ * We just do to updating pg_class.relreadonly to true
+ */
+static void
+ATExecSetReadOnly(Relation rel)
+{
+	Oid			relid,
+				toast_relid;
+
+	relid = RelationGetRelid(rel);
+	Assert(OidIsValid(relid));
+
+	/* Change readonly flag to true */
+	change_relreadonly(relid, true);
+
+	/* If relation has TOAST table then change readonly flag as well */
+	toast_relid = rel->rd_rel->reltoastrelid;
+	if (OidIsValid(toast_relid))
+		change_relreadonly(toast_relid, true);
+}
+
+static void
+change_relreadonly(Oid relOid, bool flag)
+{
+	Relation		relationRelation;
+	HeapTuple		tuple;
+	Form_pg_class	classtuple;
+
+	relationRelation = heap_open(RelationRelationId, AccessShareLock);
+	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relOid));
+
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache look up filed for relation %u", relOid);
+
+	classtuple = (Form_pg_class) GETSTRUCT(tuple);
+	classtuple->relreadonly = flag;
+
+	simple_heap_update(relationRelation, &tuple->t_self, tuple);
+	CatalogUpdateIndexes(relationRelation, tuple);
+
+	heap_freetuple(tuple);
+	heap_close(relationRelation, AccessShareLock);
+}
+
+static void
+ATExecSetReadWrite(Relation rel)
+{
+	Oid			relid,
+				toast_relid;
+
+	relid = RelationGetRelid(rel);
+	Assert(OidIsValid(relid));
+
+	/* Change readonly flag to false */
+	change_relreadonly(relid, false);
+
+	/* If relation has TOAST table then change readonly flag as well */
+	toast_relid = rel->rd_rel->reltoastrelid;
+	if (OidIsValid(toast_relid))
+		change_relreadonly(toast_relid, false);
+}
+
+/*
  * ALTER TABLE SET TABLESPACE
  */
 static void
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index bd57b68..4adddb3 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1297,6 +1297,20 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	}
 
 	/*
+	 * Check whether it's read-only table
+	 */
+	if (RelationIsReadOnly(onerel))
+	{
+		ereport(WARNING,
+				(errmsg("skipping \"%s\" --- cannot vacuum read-only table",
+						RelationGetRelationName(onerel))));
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
+	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
 	 * contents are probably not up-to-date on disk.  (We don't throw a
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index f96fb24..96a2acf 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -185,6 +185,13 @@ ExecInsert(TupleTableSlot *slot,
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
+	 * check if we are going to do INSERT read-only table
+	 */
+	if (RelationIsReadOnly(resultRelationDesc))
+		ereport(ERROR, (errmsg("cannot INSERT to read-only table: \"%s\"",
+							   RelationGetRelationName(resultRelationDesc))));
+
+	/*
 	 * If the result relation has OIDs, force the tuple's OID to zero so that
 	 * heap_insert will assign a fresh OID.  Usually the OID already will be
 	 * zero at this point, but there are corner cases where the plan tree can
@@ -337,6 +344,14 @@ ExecDelete(ItemPointer tupleid,
 	resultRelInfo = estate->es_result_relation_info;
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
+	/*
+	 * check if we are going to do DELETE read-only table.
+	 */
+	if (RelationIsReadOnly(resultRelationDesc))
+		ereport(ERROR, (errmsg("cannot DELETE to read-only table: \"%s\"",
+							   RelationGetRelationName(resultRelationDesc))));
+
+
 	/* BEFORE ROW DELETE Triggers */
 	if (resultRelInfo->ri_TrigDesc &&
 		resultRelInfo->ri_TrigDesc->trig_delete_before_row)
@@ -599,6 +614,13 @@ ExecUpdate(ItemPointer tupleid,
 	resultRelInfo = estate->es_result_relation_info;
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
+	/*
+	 * chec if we are going to do UPDATE read-only table.
+	 */
+	if (RelationIsReadOnly(resultRelationDesc))
+		ereport(ERROR, (errmsg("cannot UPDATE to read-only table: \"%s\"",
+							   RelationGetRelationName(resultRelationDesc))));
+
 	/* BEFORE ROW UPDATE Triggers */
 	if (resultRelInfo->ri_TrigDesc &&
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 88ec83c..6d160f2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2132,6 +2132,20 @@ alter_table_cmd:
 					n->subtype = AT_SetUnLogged;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET READ ONLY */
+			| SET READ ONLY
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetReadOnly;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> SET READ WRITE */
+			| SET READ WRITE
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetReadWrite;
+					$$ = (Node *)n;
+				}
 			/* ALTER TABLE <name> ENABLE TRIGGER <trig> */
 			| ENABLE_P TRIGGER name
 				{
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index c93b412..7898e89 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1985,6 +1985,13 @@ do_autovacuum(void)
 			classForm->relkind != RELKIND_MATVIEW)
 			continue;
 
+		/*
+		 * Skip if the table is marked as read-only.
+		 */
+		if (classForm->relreadonly)
+			continue;
+
+
 		relid = HeapTupleGetOid(tuple);
 
 		/* Fetch reloptions and the pgstat entry for this table */
@@ -2100,6 +2107,12 @@ do_autovacuum(void)
 		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
 			continue;
 
+		/*
+		 * Skip if the table is marked as read-only.
+		 */
+		if (classForm->relreadonly)
+			continue;
+
 		relid = HeapTupleGetOid(tuple);
 
 		/*
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 8b4c35c..6742dc7 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -50,6 +50,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	Oid			reltoastrelid;	/* OID of toast table; 0 if none */
 	bool		relhasindex;	/* T if has (or has had) any indexes */
 	bool		relisshared;	/* T if shared across databases */
+	bool		relreadonly;	/* T if read only table */
 	char		relpersistence; /* see RELPERSISTENCE_xxx constants below */
 	char		relkind;		/* see RELKIND_xxx constants below */
 	int16		relnatts;		/* number of user attributes */
@@ -95,7 +96,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class					30
+#define Natts_pg_class					31
 #define Anum_pg_class_relname			1
 #define Anum_pg_class_relnamespace		2
 #define Anum_pg_class_reltype			3
@@ -110,22 +111,23 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_reltoastrelid		12
 #define Anum_pg_class_relhasindex		13
 #define Anum_pg_class_relisshared		14
-#define Anum_pg_class_relpersistence	15
-#define Anum_pg_class_relkind			16
-#define Anum_pg_class_relnatts			17
-#define Anum_pg_class_relchecks			18
-#define Anum_pg_class_relhasoids		19
-#define Anum_pg_class_relhaspkey		20
-#define Anum_pg_class_relhasrules		21
-#define Anum_pg_class_relhastriggers	22
-#define Anum_pg_class_relhassubclass	23
-#define Anum_pg_class_relrowsecurity	24
-#define Anum_pg_class_relispopulated	25
-#define Anum_pg_class_relreplident		26
-#define Anum_pg_class_relfrozenxid		27
-#define Anum_pg_class_relminmxid		28
-#define Anum_pg_class_relacl			29
-#define Anum_pg_class_reloptions		30
+#define Anum_pg_class_relreadonly		15
+#define Anum_pg_class_relpersistence	16
+#define Anum_pg_class_relkind			17
+#define Anum_pg_class_relnatts			18
+#define Anum_pg_class_relchecks			19
+#define Anum_pg_class_relhasoids		20
+#define Anum_pg_class_relhaspkey		21
+#define Anum_pg_class_relhasrules		22
+#define Anum_pg_class_relhastriggers	23
+#define Anum_pg_class_relhassubclass	24
+#define Anum_pg_class_relrowsecurity	25
+#define Anum_pg_class_relispopulated	26
+#define Anum_pg_class_relreplident		27
+#define Anum_pg_class_relfrozenxid		28
+#define Anum_pg_class_relminmxid		29
+#define Anum_pg_class_relacl			30
+#define Anum_pg_class_reloptions		31
 
 /* ----------------
  *		initial contents of pg_class
@@ -140,13 +142,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f f p r 30 0 t f f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f f p r 21 0 f f f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 27 0 t f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f f p r 27 0 t f f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f f p r 31 0 t f f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
 
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2893cef..869df79 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1336,6 +1336,8 @@ typedef enum AlterTableType
 	AT_DropCluster,				/* SET WITHOUT CLUSTER */
 	AT_SetLogged,				/* SET LOGGED */
 	AT_SetUnLogged,				/* SET UNLOGGED */
+	AT_SetReadOnly,				/* SET READ ONLY */
+	AT_SetReadWrite,			/* SET READ WREITE */
 	AT_AddOids,					/* SET WITH OIDS */
 	AT_AddOidsRecurse,			/* internal to commands/tablecmds.c */
 	AT_DropOids,				/* SET WITHOUT OIDS */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 6bd786d..12620fa 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -435,6 +435,13 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT)
 
 /*
+ * RelationIsReadOnly
+ *		True if relation is read only.
+ */
+#define RelationIsReadOnly(relation) \
+	((relation)->rd_rel->relreadonly)
+
+/*
  * RelationUsesLocalBuffers
  *		True if relation's pages are stored in local buffers.
  */
