Steve Singer ssinger_pg
Sun Dec 17 20:27:55 PST 2006
Attached is my first attempt at a patch to allow EXECUTE SCRIPT to take a 
user specified list of tables to lock.

The idea is that you can specify something like

EXECUTE SCRIPT(set id=1, filename='new_column.sql',
 	event node=1, lock tables = ('public.test1','public.address')
 	);

The ddl_updates_tbl_locks.sql should be added to tests/testddl

The diff file should be applied against CVS HEAD.

Let me know if there are any concerns.


Steve Singer

-------------- next part --------------
ALTER TABLE table5 DROP COLUMN a;
ALTER TABLE table1 ADD COLUMN xy int4;
UPDATE table1 SET xy=1;
-------------- next part --------------
? tests/testddl/ddl_updates_tbl_locks.sql
Index: doc/adminguide/slonik_ref.sgml
===================================================================
RCS file: /usr/local/cvsroot/slony1/slony1-engine/doc/adminguide/slonik_ref.sgml,v
retrieving revision 1.65
diff -c -r1.65 slonik_ref.sgml
*** doc/adminguide/slonik_ref.sgml	31 Oct 2006 22:09:39 -0000	1.65
--- doc/adminguide/slonik_ref.sgml	18 Dec 2006 04:27:50 -0000
***************
*** 2548,2553 ****
--- 2548,2563 ----
  	subscribed to the set.</para></listitem>
        
       </varlistentry>
+      <varlistentry><term><literal>LOCK TABLES = ('fully.qualified_table1',
+      'fully.qualified_table2','fully_qualified.table3')
+      </literal</term>
+      <listitem><para>(Optional) This option tells Slony to only obtain locks on the
+      tables specified.  It is important that any tables that are referenced in
+      the script being executed be listed here or that this option not be specified
+      which will result in a lock being obtained on all tables.
+      </para>
+      </listitem>
+      </varlistentry>
      </variablelist>
      
      <para> See also the warnings in &rddlchanges;.</para>
***************
*** 2556,2573 ****
      it can get stuck behind other database activity.</para>
       
      <para> At the start of this event, all replicated tables are
!     unlocked via the function
      <function>alterTableRestore(tab_id)</function>.  After the SQL
      script has run, they are returned to <quote>replicating
      state</quote> using
      <function>alterTableForReplication(tab_id)</function>.  This means
!     that all of these tables are locked by this &lslon process for the
      duration of the SQL script execution.</para>
  
      <para> If a table's columns are modified, it is very important
      that the triggers be regenerated, otherwise they may be
      inappropriate for the new form of the table schema.</para>
  
      <para> Note that if you need to make reference to the cluster
      name, you can use the token <command>@CLUSTERNAME@</command>; if
      you need to make reference to the &slony1; namespace, you can use
--- 2566,2590 ----
      it can get stuck behind other database activity.</para>
       
      <para> At the start of this event, all replicated tables are
!     locked and replication is stopped via the function
      <function>alterTableRestore(tab_id)</function>.  After the SQL
      script has run, they are returned to <quote>replicating
      state</quote> using
      <function>alterTableForReplication(tab_id)</function>.  This means
!     that all of these tables are locked by the slonik  process(on the event node) or
!     by the slon process(for other nodes) for the
      duration of the SQL script execution.</para>
  
      <para> If a table's columns are modified, it is very important
      that the triggers be regenerated, otherwise they may be
      inappropriate for the new form of the table schema.</para>
  
+     <para> The LOCK TABLES option can be used to tell Slony to only 
+     lock the tables and suspend replication on the tables indicated.
+     If this option is not specified all replicated tables will be locked
+     during the execution of the script.
+     </para>
+ 
      <para> Note that if you need to make reference to the cluster
      name, you can use the token <command>@CLUSTERNAME@</command>; if
      you need to make reference to the &slony1; namespace, you can use
***************
*** 2587,2595 ****
     </refsect1>
     <refsect1> <title> Locking Behaviour </title>
  
!     <para> Each replicated table receives an exclusive lock, on the
!     origin node, in order to remove the replication triggers; after
!     the DDL script completes, those locks will be cleared. </para>
  
      <para> After the DDL script has run on the origin node, it will
      then run on subscriber nodes, where replicated tables will be
--- 2604,2645 ----
     </refsect1>
     <refsect1> <title> Locking Behaviour </title>
  
!     <para> If the LOCK TABLES options is not specified then each replicated table
!      receives an exclusive lock on the
!     origin node in order to remove the replication triggers. Every other replicated
!     table will have its application and constraint triggers re-enablled on the slave
!     databases for the duration of the EXECUTE SCRIPT. After
!     the DDL script completes the locks will be cleared and triggers will be reverted.
!     All non-Slony database
!     activity on replicated tables should be stopped to prevent the risk of deadlock.</para>
!     
!     <para> If the LOCK TABLES option is specified then only the the tables
!     listed will have the replication triggers removed from them on the origin or other triggers
!     enabled on the slave. This will require
!      obtaining an exclusive lock on each listed table for the duration of the EXECUTE script.
!     After the DDL script completes, those locks will be cleared. 
!     This allows the DBA to control which tables will be locked during the EXECUTE SCRIPT
!     </para>
! 
!     <para>
!     Any tables that are referenced in the DDL script need to be locked.  The following guidelines
!     are useful:    
!     </para>
!     <itemizedlist>
!     <listitem><para>Any tables referenced by an ALTER table must be locked</para></listitem>
!     <listitem><para>If adding a foreign key both the table the key is being added to and
!     the table the foreign key references must be locked</para></listitem>
!     <listitem><para>Any tables that are the target of INSERT or UPDATE statements must be locked
!     </para></listitem>
!     <listitem><para>Any tables that are being queried must be locked</para></listitem>
!     <listitem><para>If a table is being dropped it should be removed from all replication sets
!     before the DDL script is run and thus does not need to be locked</para></listitem>
!     <listitem><para>If in doubt don't use the LOCK TABLES option and allow Slony to lock everything
!     </para></listitem>
! 
!     </itemizedlist>
! 
!    
  
      <para> After the DDL script has run on the origin node, it will
      then run on subscriber nodes, where replicated tables will be
Index: src/backend/slony1_funcs.sql
===================================================================
RCS file: /usr/local/cvsroot/slony1/slony1-engine/src/backend/slony1_funcs.sql,v
retrieving revision 1.103
diff -c -r1.103 slony1_funcs.sql
*** src/backend/slony1_funcs.sql	15 Dec 2006 05:34:18 -0000	1.103
--- src/backend/slony1_funcs.sql	18 Dec 2006 04:27:53 -0000
***************
*** 3664,3669 ****
--- 3664,3671 ----
  Processes DROP_TRIGGER event to make sure that trigger trig_tgname on
  replicated table trig_tabid IS disabled.';
  
+ 
+ 
  -- ----------------------------------------------------------------------
  -- FUNCTION ddlScript_prepare (set_id, only_on_node)
  --
***************
*** 3671,3680 ****
--- 3673,3700 ----
  -- ----------------------------------------------------------------------
  create or replace function @NAMESPACE at .ddlScript_prepare (int4, int4)
  returns integer
+ as ' 
+ begin
+   return @NAMESPACE at .ddlScirpt_prepare($1,$2,null);
+ end;
+ ' language plpgsql;
+ 
+ 
+ comment on function @NAMESPACE at .ddlScript_prepare (int4, int4) is 
+ 'Prepare for DDL script execution on origin. All replicated tables will be prepared';
+ 
+ -- ----------------------------------------------------------------------
+ -- FUNCTION ddlScript_prepare (set_id, only_on_node,p_lock_tab_ids)
+ --
+ --	Generate the DDL_SCRIPT event
+ -- ----------------------------------------------------------------------
+ create or replace function @NAMESPACE at .ddlScript_prepare (int4, int4,int4[])
+ returns integer
  as '
  declare
  	p_set_id			alias for $1;
  	p_only_on_node		alias for $2;
+ 	p_lock_tab_ids          alias for $3;
  	v_set_origin		int4;
  begin
  	-- ----
***************
*** 3697,3714 ****
  				p_set_id;
  	end if;
  
  	-- ----
  	-- Create a SYNC event, run the script and generate the DDL_SCRIPT event
  	-- ----
-     perform @NAMESPACE at .alterTableRestore(tab_id) from @NAMESPACE at .sl_table where tab_set in (select set_id from @NAMESPACE at .sl_set where set_origin = @NAMESPACE at .getLocalNodeId(''_ at CLUSTERNAME@''));
- 
  	perform @NAMESPACE at .createEvent(''_ at CLUSTERNAME@'', ''SYNC'', NULL);
  	return 1;
  end;
  ' language plpgsql;
  
! comment on function @NAMESPACE at .ddlScript_prepare (int4, int4) is 
! 'Prepare for DDL script execution on origin';
  
  -- 	perform @NAMESPACE at .ddlScript_int(p_set_id, p_script, p_only_on_node);
  
--- 3717,3747 ----
  				p_set_id;
  	end if;
  
+ 
+ 	
+ 	if p_lock_tab_ids is not null then
+ 		
+ 	      perform @NAMESPACE at .alterTableRestore(tab_id) from @NAMESPACE at .sl_table where tab_set in (select set_id from @NAMESPACE at .sl_set where set_origin = @NAMESPACE at .getLocalNodeId(''_ at CLUSTERNAME@''))
+ 	and tab_id =any (p_lock_tab_ids)
+ 	;
+ 	else
+ 
+ 	    perform @NAMESPACE at .alterTableRestore(tab_id) from @NAMESPACE at .sl_table where tab_set in (select set_id from @NAMESPACE at .sl_set where set_origin = @NAMESPACE at .getLocalNodeId(''_ at CLUSTERNAME@''));
+ 	end if;
+ 
  	-- ----
  	-- Create a SYNC event, run the script and generate the DDL_SCRIPT event
  	-- ----
  	perform @NAMESPACE at .createEvent(''_ at CLUSTERNAME@'', ''SYNC'', NULL);
  	return 1;
  end;
  ' language plpgsql;
  
! 
! comment on function @NAMESPACE at .ddlScript_prepare (int4, int4,int4[]) is 
! 'Prepare for DDL script execution on origin. All replicated tables will be prepared if
!  lock_tab_ids is null otherwise just the specified tables will be prepared. ';
! 
  
  -- 	perform @NAMESPACE at .ddlScript_int(p_set_id, p_script, p_only_on_node);
  
***************
*** 3720,3756 ****
  create or replace function @NAMESPACE at .ddlScript_complete (int4, text, int4)
  returns integer
  as '
  declare
  	p_set_id			alias for $1;
  	p_script			alias for $2;
  	p_only_on_node		alias for $3;
  	v_set_origin		int4;
  begin
  	perform @NAMESPACE at .updateRelname(p_set_id, p_only_on_node);
!     perform @NAMESPACE at .alterTableForReplication(tab_id) from @NAMESPACE at .sl_table where tab_set in (select set_id from @NAMESPACE at .sl_set where set_origin = @NAMESPACE at .getLocalNodeId(''_ at CLUSTERNAME@''));
  	return  @NAMESPACE at .createEvent(''_ at CLUSTERNAME@'', ''DDL_SCRIPT'', 
! 			p_set_id, p_script, p_only_on_node);
  end;
  ' language plpgsql;
  
  comment on function @NAMESPACE at .ddlScript_complete(int4, text, int4) is
! 'ddlScript_complete(set_id, script, only_on_node)
  
  After script has run on origin, this fixes up relnames, restores
  triggers, and generates a DDL_SCRIPT event to request it to be run on
! replicated slaves.';
  
  -- ----------------------------------------------------------------------
! -- FUNCTION ddlScript_prepare_int (set_id, only_on_node)
  --
  --	Prepare for the DDL_SCRIPT event
  -- ----------------------------------------------------------------------
! create or replace function @NAMESPACE at .ddlScript_prepare_int (int4, int4)
  returns int4
  as '
  declare
  	p_set_id			alias for $1;
  	p_only_on_node		alias for $2;
  	v_set_origin		int4;
  	v_no_id				int4;
  	v_row				record;
--- 3753,3813 ----
  create or replace function @NAMESPACE at .ddlScript_complete (int4, text, int4)
  returns integer
  as '
+ begin
+ 	return @NAMESPACE at .ddlScript_complete($1,$2,$3,null);
+ end;
+ ' language plpgsql;
+ 
+ 
+ -- 	perform @NAMESPACE at .ddlScript_int(p_set_id, p_script, p_only_on_node);
+ 
+ -- ----------------------------------------------------------------------
+ -- FUNCTION ddlScript_complete (set_id, script, only_on_node)
+ --
+ --	Generate the DDL_SCRIPT event
+ -- ----------------------------------------------------------------------
+ create or replace function @NAMESPACE at .ddlScript_complete (int4, text, int4,int4[])
+ returns integer
+ as '
  declare
  	p_set_id			alias for $1;
  	p_script			alias for $2;
  	p_only_on_node		alias for $3;
+ 	p_lock_tab_ids          alias for $4;
  	v_set_origin		int4;
  begin
  	perform @NAMESPACE at .updateRelname(p_set_id, p_only_on_node);
! 
! 	if p_lock_tab_ids is not null then
! 		perform @NAMESPACE at .alterTableForReplication(tab_id) from @NAMESPACE at .sl_table where tab_set in (select set_id from @NAMESPACE at .sl_set where set_origin = @NAMESPACE at .getLocalNodeId(''_ at CLUSTERNAME@'')) and tab_id = any (p_lock_tab_ids) ;
! 	else
!     		perform @NAMESPACE at .alterTableForReplication(tab_id) from @NAMESPACE at .sl_table where tab_set in (select set_id from @NAMESPACE at .sl_set where set_origin = @NAMESPACE at .getLocalNodeId(''_ at CLUSTERNAME@''));
! 	end if;
! 
  	return  @NAMESPACE at .createEvent(''_ at CLUSTERNAME@'', ''DDL_SCRIPT'', 
! 			p_set_id, p_script, p_only_on_node,array_to_string(p_lock_tab_ids,'',''));
  end;
  ' language plpgsql;
  
  comment on function @NAMESPACE at .ddlScript_complete(int4, text, int4) is
! 'ddlScript_complete(set_id, script, only_on_node,p_lock_tab_ids)
  
  After script has run on origin, this fixes up relnames, restores
  triggers, and generates a DDL_SCRIPT event to request it to be run on
! replicated slaves. ';
  
  -- ----------------------------------------------------------------------
! -- FUNCTION ddlScript_prepare_int (set_id, only_on_node,lock_table_ids)
  --
  --	Prepare for the DDL_SCRIPT event
  -- ----------------------------------------------------------------------
! create or replace function @NAMESPACE at .ddlScript_prepare_int (int4, int4,int4[])
  returns int4
  as '
  declare
  	p_set_id			alias for $1;
  	p_only_on_node		alias for $2;
+ 	p_lock_table_ids        alias for $3;
  	v_set_origin		int4;
  	v_no_id				int4;
  	v_row				record;
***************
*** 3788,3797 ****
  		return 0;
  	end if;
  
  	-- ----
  	-- Restore all original triggers and rules of all sets
  	-- ----
! 	for v_row in select * from @NAMESPACE at .sl_table
  	loop
  		perform @NAMESPACE at .alterTableRestore(v_row.tab_id);
  	end loop;
--- 3845,3856 ----
  		return 0;
  	end if;
  
+ 
  	-- ----
  	-- Restore all original triggers and rules of all sets
  	-- ----
! 	for v_row in select * from @NAMESPACE at .sl_table	
! 	    where tab_id =any (p_lock_table_ids) or p_lock_table_ids is null
  	loop
  		perform @NAMESPACE at .alterTableRestore(v_row.tab_id);
  	end loop;
***************
*** 3799,3805 ****
  end;
  ' language plpgsql;
  
! comment on function @NAMESPACE at .ddlScript_prepare_int (int4, int4) is
  'ddlScript_prepare_int (set_id, only_on_node)
  
  Do preparatory work for a DDL script, restoring 
--- 3858,3864 ----
  end;
  ' language plpgsql;
  
! comment on function @NAMESPACE at .ddlScript_prepare_int (int4, int4,int4[]) is
  'ddlScript_prepare_int (set_id, only_on_node)
  
  Do preparatory work for a DDL script, restoring 
***************
*** 3807,3828 ****
  
  
  -- ----------------------------------------------------------------------
! -- FUNCTION ddlScript_complete_int (set_id, only_on_node)
  --
  --	Complete the DDL_SCRIPT event
  -- ----------------------------------------------------------------------
! create or replace function @NAMESPACE at .ddlScript_complete_int (int4, int4)
  returns int4
  as '
  declare
  	p_set_id			alias for $1;
  	p_only_on_node		alias for $2;
  	v_row				record;
  begin
  	-- ----
  	-- Put all tables back into replicated mode
  	-- ----
  	for v_row in select * from @NAMESPACE at .sl_table
  	loop
  		perform @NAMESPACE at .alterTableForReplication(v_row.tab_id);
  	end loop;
--- 3866,3889 ----
  
  
  -- ----------------------------------------------------------------------
! -- FUNCTION ddlScript_complete_int (set_id, only_on_node,p_lock_table_ids)
  --
  --	Complete the DDL_SCRIPT event
  -- ----------------------------------------------------------------------
! create or replace function @NAMESPACE at .ddlScript_complete_int (int4, int4,int4[])
  returns int4
  as '
  declare
  	p_set_id			alias for $1;
  	p_only_on_node		alias for $2;
+ 	p_lock_table_ids        alias for $3;
  	v_row				record;
  begin
  	-- ----
  	-- Put all tables back into replicated mode
  	-- ----
  	for v_row in select * from @NAMESPACE at .sl_table
+ 	       where tab_id =any (p_lock_table_ids) or p_lock_table_ids is null
  	loop
  		perform @NAMESPACE at .alterTableForReplication(v_row.tab_id);
  	end loop;
***************
*** 3830,3836 ****
  	return p_set_id;
  end;
  ' language plpgsql;
! comment on function @NAMESPACE at .ddlScript_complete_int(int4, int4) is
  'ddlScript_complete_int(set_id, script, only_on_node)
  
  Complete processing the DDL_SCRIPT event.  This puts tables back into
--- 3891,3897 ----
  	return p_set_id;
  end;
  ' language plpgsql;
! comment on function @NAMESPACE at .ddlScript_complete_int(int4, int4,int4[]) is
  'ddlScript_complete_int(set_id, script, only_on_node)
  
  Complete processing the DDL_SCRIPT event.  This puts tables back into
***************
*** 5902,5904 ****
--- 5963,5994 ----
  to specify fields for the passed-in tab_id.  
  
  In PG versions > 7.3, this looks like (field1,field2,...fieldn)';
+ 
+ 
+ 
+ 
+ 
+ -- ----------------------------------------------------------------------
+ -- FUNCTION getTabId(fully_qualifed_name)
+ -- Get the slony table id for the fully qualified name
+ --
+ --	
+ -- ----------------------------------------------------------------------
+ create or replace function @NAMESPACE at .getTabId(text)
+ returns integer
+ as '
+ declare
+     p_fq_name  alias for $1;
+     result integer;
+ begin
+ 
+ 	select into result tab_id from @NAMESPACE at .sl_table where
+ 	tab_nspname || ''.'' || tab_relname = p_fq_name limit 1;
+ 	return result;
+ end;
+ ' language plpgsql;
+ 
+ comment on function @NAMESPACE at .getTabId(text) is
+ 'Returns the slony table_id for the fully qualified (schema.tablename) passed.
+ If no such table exists null is returned.
+ ';
\ No newline at end of file
Index: src/slon/remote_worker.c
===================================================================
RCS file: /usr/local/cvsroot/slony1/slony1-engine/src/slon/remote_worker.c,v
retrieving revision 1.131
diff -c -r1.131 remote_worker.c
*** src/slon/remote_worker.c	12 Dec 2006 20:13:01 -0000	1.131
--- src/slon/remote_worker.c	18 Dec 2006 04:27:57 -0000
***************
*** 1340,1360 ****
  				char	   *ddl_script = event->ev_data2;
  				int			ddl_only_on_node = (int)strtol(event->ev_data3, NULL, 10);
  				int num_statements = -1, stmtno;
! 
  				PGresult *res;
  				ExecStatusType rstat;
! 
! 
! 				slon_appendquery(&query1,
! 						 "select %s.ddlScript_prepare_int(%d, %d); ",
! 						 rtcfg_namespace,
! 						 ddl_setid, ddl_only_on_node);
! 
! 				if (query_execute(node, local_dbconn, &query1) < 0) {
! 						slon_log(SLON_ERROR, "remoteWorkerThread_%d: DDL preparation failed - set %d - only on node %\n",
! 							 node->no_id, ddl_setid, ddl_only_on_node);
! 						slon_retry();
  				}
  
  				num_statements = scan_for_statements (ddl_script);
  				slon_log(SLON_CONFIG, "remoteWorkerThread_%d: DDL request with %d statements\n",
--- 1340,1371 ----
  				char	   *ddl_script = event->ev_data2;
  				int			ddl_only_on_node = (int)strtol(event->ev_data3, NULL, 10);
  				int num_statements = -1, stmtno;
! 				const char * lock_table_list = event->ev_data4;
! 				
  				PGresult *res;
  				ExecStatusType rstat;
! 				
! 				if(lock_table_list != NULL ) 
! 				{
! 					 slon_appendquery(&query1,
! 									  "select %s.ddlScript_prepare_int(%d,%d, '{%s}'); ",
! 									  rtcfg_namespace,
! 									  ddl_setid, ddl_only_on_node,lock_table_list);
! 				}/*locks*/							
! 				else 
! 				{
! 					 slon_appendquery(&query1,
! 									  "select %s.ddlScript_prepare_int(%d, %d,null); ",
! 									  rtcfg_namespace,
! 									  ddl_setid, ddl_only_on_node);
! 				}
! 				if (query_execute(node, local_dbconn, &query1) < 0)
! 				{
! 				     slon_log(SLON_ERROR, "remoteWorkerThread_%d: DDL preparation failed - set %d - only on node %d\n",
! 							  node->no_id, ddl_setid, ddl_only_on_node);
! 					 slon_retry();
  				}
+ 				
  
  				num_statements = scan_for_statements (ddl_script);
  				slon_log(SLON_CONFIG, "remoteWorkerThread_%d: DDL request with %d statements\n",
***************
*** 1402,1411 ****
  					slon_log (SLON_CONFIG, "DDL success - %s\n", PQresStatus(rstat));
  				}
  	
! 				slon_mkquery(&query1, "select %s.ddlScript_complete_int(%d, %d); ", 
! 					     rtcfg_namespace,
! 					     ddl_setid,
! 					     ddl_only_on_node);
  
  				/* DDL_SCRIPT needs to be turned into a log shipping script */
  				/* Note that the issue about parsing that mandates breaking 
--- 1413,1437 ----
  					slon_log (SLON_CONFIG, "DDL success - %s\n", PQresStatus(rstat));
  				}
  	
! 				
! 				if(lock_table_list != NULL ) 
! 				{
! 				  
! 				     slon_mkquery(&query1, "select %s.ddlScript_complete_int(%d,%d,'{%s}'); ", 
! 						  rtcfg_namespace,
! 						  ddl_setid,
! 						  ddl_only_on_node,lock_table_list);
! 				   
! 				}
! 				else 
! 				{
! 
! 				
! 				     slon_mkquery(&query1, "select %s.ddlScript_complete_int(%d, %d,null); ", 
! 						  rtcfg_namespace,
! 						  ddl_setid,
! 						  ddl_only_on_node);
! 				}
  
  				/* DDL_SCRIPT needs to be turned into a log shipping script */
  				/* Note that the issue about parsing that mandates breaking 
***************
*** 6091,6093 ****
--- 6117,6122 ----
  	}
  	slon_log(SLON_DEBUG3, " compressed actionseq subquery... %s\n", dstring_data(action_subquery));
  }
+ 
+ 
+ 
Index: src/slonik/parser.y
===================================================================
RCS file: /usr/local/cvsroot/slony1/slony1-engine/src/slonik/parser.y,v
retrieving revision 1.27
diff -c -r1.27 parser.y
*** src/slonik/parser.y	31 Oct 2006 22:09:40 -0000	1.27
--- src/slonik/parser.y	18 Dec 2006 04:27:58 -0000
***************
*** 50,60 ****
  	O_USE_KEY,
  	O_WAIT_CONFIRMED,
  	O_WAIT_ON,
! 
  	END_OF_OPTIONS = -1
  } option_code;
  
  
  /*
   * Common given option list
   */
--- 50,70 ----
  	O_USE_KEY,
  	O_WAIT_CONFIRMED,
  	O_WAIT_ON,
! 	O_LOCK_TABLES,
  	END_OF_OPTIONS = -1
  } option_code;
  
  
+ /**
+  * A structure that stores a list of table names as a linked list.
+  * Table names are stored in fully qualified format.
+  */
+ typedef struct table_list {
+ 	char * name;
+ 	struct table_list * next;
+ } table_list;
+ 
+ 
  /*
   * Common given option list
   */
***************
*** 63,69 ****
  	int			lineno;
  	int32		ival;
  	char	   *str;
! 
  	struct option_list *next;
  } option_list;
  
--- 73,79 ----
  	int			lineno;
  	int32		ival;
  	char	   *str;
! 	table_list * table_list;
  	struct option_list *next;
  } option_list;
  
***************
*** 76,86 ****
  	int			lineno;
  	int			ival;
  	char	   *str;
  } statement_option;
! #define	STMT_OPTION_INT(_code,_dfl)		{_code, -1, _dfl, NULL}
! #define	STMT_OPTION_STR(_code,_dfl)		{_code, -1, -1, _dfl}
! #define	STMT_OPTION_YN(_code,_dfl)		{_code, -1, _dfl, NULL}
! #define STMT_OPTION_END					{END_OF_OPTIONS, -1, -1, NULL}
  
  
  /*
--- 86,100 ----
  	int			lineno;
  	int			ival;
  	char	   *str;
+ 	table_list * table_list;
  } statement_option;
! #define	STMT_OPTION_INT(_code,_dfl)		{_code, -1, _dfl, NULL,NULL}
! #define	STMT_OPTION_STR(_code,_dfl)		{_code, -1, -1, _dfl,NULL}
! #define	STMT_OPTION_YN(_code,_dfl)		{_code, -1, _dfl, NULL,NULL}
! #define STMT_OPTION_TBLLIST(_code,_dfl) {_code, -1,-1,NULL,_dfl}
! #define STMT_OPTION_END					{END_OF_OPTIONS, -1, -1, NULL,NULL}
! 
! 
  
  
  /*
***************
*** 109,114 ****
--- 123,129 ----
  	option_list	*opt_list;
  	SlonikAdmInfo	*adm_info;
  	SlonikStmt	*statement;
+ 	table_list * table_list;
  }
  
  %type <ival>		id
***************
*** 169,175 ****
  %type <opt_list>	option_item_id
  %type <opt_list>	option_item_literal
  %type <opt_list>	option_item_yn
! 
  
  /*
   * Keyword tokens
--- 184,191 ----
  %type <opt_list>	option_item_id
  %type <opt_list>	option_item_literal
  %type <opt_list>	option_item_yn
! %type <table_list>  table_list
! %type <table_list>  table_list_items
  
  /*
   * Keyword tokens
***************
*** 247,253 ****
  %token	K_WAIT
  %token	K_SYNC
  %token	K_SLEEP
! 
  /*
   * Other scanner tokens
   */
--- 263,269 ----
  %token	K_WAIT
  %token	K_SYNC
  %token	K_SLEEP
! %token  K_TABLES
  /*
   * Other scanner tokens
   */
***************
*** 1345,1350 ****
--- 1361,1367 ----
  							STMT_OPTION_STR( O_FILENAME, NULL ),
  							STMT_OPTION_INT( O_EVENT_NODE, 1 ),
  							STMT_OPTION_INT( O_EXECUTE_ONLY_ON, -1 ),
+ 							STMT_OPTION_TBLLIST( O_LOCK_TABLES,NULL),
  							STMT_OPTION_END
  						};
  
***************
*** 1354,1366 ****
  						new->hdr.stmt_type		= STMT_DDL_SCRIPT;
  						new->hdr.stmt_filename	= current_file;
  						new->hdr.stmt_lno		= $1;
! 
  						if (assign_options(opt, $4) == 0)
  						{
  							new->ddl_setid		= opt[0].ival;
  							new->ddl_fname		= opt[1].str;
  							new->ev_origin		= opt[2].ival;
  							new->only_on_node	= opt[3].ival;
  							new->ddl_fd		= NULL;
  						}
  						else
--- 1371,1410 ----
  						new->hdr.stmt_type		= STMT_DDL_SCRIPT;
  						new->hdr.stmt_filename	= current_file;
  						new->hdr.stmt_lno		= $1;
! 						
  						if (assign_options(opt, $4) == 0)
  						{
+ 							table_list * lock_list_ptr;
+ 							int lock_count;
+ 							int table_ind;
  							new->ddl_setid		= opt[0].ival;
  							new->ddl_fname		= opt[1].str;
  							new->ev_origin		= opt[2].ival;
  							new->only_on_node	= opt[3].ival;
+ 							lock_count=0;
+ 							for(lock_list_ptr=opt[4].table_list;
+ 								lock_list_ptr!=NULL;
+ 								lock_list_ptr=lock_list_ptr->next)
+ 							{
+ 								lock_count++;
+ 							}
+ 								
+ 							if(lock_count > 0 ) 
+ 							{
+ 								new->table_locks=malloc(sizeof(char*)*lock_count+1);
+ 								for(lock_list_ptr=opt[4].table_list,table_ind=0;
+ 									lock_list_ptr!=NULL;
+ 									lock_list_ptr=lock_list_ptr->next,table_ind++)
+ 								{
+ 									new->table_locks[table_ind] = lock_list_ptr->name;
+ 									
+ 								}
+ 								new->table_locks[table_ind]=NULL;
+ 							}
+ 							else 
+ 							{
+ 								new->table_locks=NULL;
+ 							}
  							new->ddl_fd		= NULL;
  						}
  						else
***************
*** 1662,1667 ****
--- 1706,1712 ----
  						new->ival	= 1;
  						new->str	= NULL;
  						new->lineno	= yylineno;
+ 						new->table_list=NULL;
  						new->next	= NULL;
  
  						$$ = new;
***************
*** 1685,1690 ****
--- 1730,1736 ----
  						new->ival	= -2;
  						new->str	= NULL;
  						new->lineno	= yylineno;
+ 						new->table_list=NULL;
  						new->next	= NULL;
  
  						$$ = new;
***************
*** 1703,1708 ****
--- 1749,1755 ----
  						new->ival	= -2;
  						new->str	= NULL;
  						new->lineno	= yylineno;
+ 						new->table_list=NULL;
  						new->next	= NULL;
  
  						$$ = new;
***************
*** 1732,1737 ****
--- 1779,1796 ----
  						$3->opt_code	= O_SECONDS;
  						$$ = $3;
  					}
+ 					| K_LOCK K_TABLES '=' table_list
+ 					{
+ 						option_list * new;
+ 
+ 						new = (option_list*) malloc(sizeof(option_list));
+ 						new->ival=-1;
+ 						new->str=NULL;
+ 						new->next=NULL;
+ 						new->table_list=$4;
+ 						new->opt_code	= O_LOCK_TABLES;
+ 						$$=new;
+ 					}
  					;
  
  option_item_id		: id
***************
*** 1742,1747 ****
--- 1801,1807 ----
  						new->ival	= $1;
  						new->str	= NULL;
  						new->lineno	= yylineno;
+ 						new->table_list=NULL;
  						new->next	= NULL;
  
  						$$ = new;
***************
*** 1756,1761 ****
--- 1816,1822 ----
  						new->ival	= -1;
  						new->str	= $1;
  						new->lineno	= yylineno;
+ 						new->table_list=NULL;
  						new->next	= NULL;
  
  						$$ = new;
***************
*** 1770,1775 ****
--- 1831,1837 ----
  						new->ival	= 1;
  						new->str	= NULL;
  						new->lineno	= yylineno;
+ 						new->table_list=NULL;
  						new->next	= NULL;
  
  						$$ = new;
***************
*** 1782,1787 ****
--- 1844,1850 ----
  						new->ival	= 0;
  						new->str	= NULL;
  						new->lineno	= yylineno;
+ 						new->table_list=NULL;
  						new->next	= NULL;
  
  						$$ = new;
***************
*** 1829,1836 ****
  					;
  
  lno					:
! 					{ $$ = yylineno; }
  					;
  
  %%
  
--- 1892,1920 ----
  					;
  
  lno					:
!                     { $$ = yylineno; }
  					;
+ table_list          : '(' table_list_items ')'
+                        {
+ 						   $$=$2;
+ 					   }
+                     ;
+ 
+                       
+ table_list_items          : table_list_items ',' literal 
+                       {
+ 						  $$ = malloc(sizeof(table_list));
+ 						  $$->name=$3;
+ 						  $$->next = $1;
+                       }
+                       | literal
+                       {
+ 						  $$ = malloc(sizeof(table_list));
+ 						  $$->name = $1;
+ 						  $$->next=NULL;
+ 					  }
+                       ;
+ 
  
  %%
  
***************
*** 1876,1881 ****
--- 1960,1966 ----
  		case O_USE_KEY:			return "key";
  		case O_WAIT_CONFIRMED:	return "confirmed";
  		case O_WAIT_ON:			return "wait on";
+ 	    case O_LOCK_TABLES:     return "lock tables";
  		case END_OF_OPTIONS:	return "???";
  	}
  	return "???";
***************
*** 1924,1929 ****
--- 2009,2015 ----
  		s_opt->lineno	= u_opt->lineno;
  		s_opt->ival		= u_opt->ival;
  		s_opt->str		= u_opt->str;
+ 		s_opt->table_list = u_opt->table_list;
  	}
  
  	return errors;
Index: src/slonik/scan.l
===================================================================
RCS file: /usr/local/cvsroot/slony1/slony1-engine/src/slonik/scan.l,v
retrieving revision 1.26
diff -c -r1.26 scan.l
*** src/slonik/scan.l	31 Oct 2006 22:09:40 -0000	1.26
--- src/slonik/scan.l	18 Dec 2006 04:27:58 -0000
***************
*** 139,145 ****
  update			{ return K_UPDATE;			}
  yes				{ return K_YES;				}
  wait			{ return K_WAIT;			}
! 
  {digit}+		{ return T_NUMBER;			}
  {identifier}	{ return T_IDENT;			}
  
--- 139,145 ----
  update			{ return K_UPDATE;			}
  yes				{ return K_YES;				}
  wait			{ return K_WAIT;			}
! tables          { return K_TABLES;          }
  {digit}+		{ return T_NUMBER;			}
  {identifier}	{ return T_IDENT;			}
  
Index: src/slonik/slonik.c
===================================================================
RCS file: /usr/local/cvsroot/slony1/slony1-engine/src/slonik/slonik.c,v
retrieving revision 1.71
diff -c -r1.71 slonik.c
*** src/slonik/slonik.c	12 Dec 2006 14:54:48 -0000	1.71
--- src/slonik/slonik.c	18 Dec 2006 04:28:00 -0000
***************
*** 3834,3846 ****
  	char		rex4[256];
  	PGresult *res;
  	ExecStatusType rstat;
  
  #define PARMCOUNT 1  
  
          const char *params[PARMCOUNT];
          int paramlens[PARMCOUNT];
          int paramfmts[PARMCOUNT];
! 
  	adminfo1 = get_active_adminfo((SlonikStmt *) stmt, stmt->ev_origin);
  	if (adminfo1 == NULL)
  		return -1;
--- 3834,3847 ----
  	char		rex4[256];
  	PGresult *res;
  	ExecStatusType rstat;
+ 	SlonDString lock_tab_ids;
  
  #define PARMCOUNT 1  
  
          const char *params[PARMCOUNT];
          int paramlens[PARMCOUNT];
          int paramfmts[PARMCOUNT];
! 	
  	adminfo1 = get_active_adminfo((SlonikStmt *) stmt, stmt->ev_origin);
  	if (adminfo1 == NULL)
  		return -1;
***************
*** 3863,3879 ****
  		dstring_nappend(&script, buf, rc);
  	}
  	dstring_terminate(&script);
- 
  	dstring_init(&query);
  	slon_mkquery(&query,
! 		     "select \"_%s\".ddlScript_prepare(%d, %d); ",
! 		     stmt->hdr.script->clustername,
! 		     stmt->ddl_setid, /* dstring_data(&script),  */ 
! 		     stmt->only_on_node);
! 
  	if (db_exec_evcommand((SlonikStmt *) stmt, adminfo1, &query) < 0)
  	{
  		dstring_free(&query);
  		return -1;
  	}
  
--- 3864,3943 ----
  		dstring_nappend(&script, buf, rc);
  	}
  	dstring_terminate(&script);
  	dstring_init(&query);
+ 	dstring_init(&lock_tab_ids);
+ 		
+ 	if(stmt->table_locks!=NULL) 
+ 	{
+ 		
+ 
+ 		char ** table;
+ 		/**
+ 		 * Only Obtain locks on the specified tables.
+ 		 */
+ 		dstring_append(&lock_tab_ids,"'{");
+ 		for(table=stmt->table_locks;
+ 			*table!=NULL;
+ 			table++)
+ 		{
+ 			int tab_id;
+ 			/**
+ 			 * Get the tab_id for each
+ 			 */
+ 			slon_mkquery(&query,
+ 						 "select \"_%s\".getTabId('%s');",
+ 						 stmt->hdr.script->clustername,*table);
+ 			res = db_exec_select((SlonikStmt*) stmt,adminfo1,&query);
+ 			if(res==NULL) 
+ 			{
+ 				dstring_free(&query);
+ 				dstring_free(&lock_tab_ids);
+ 				return -1;
+ 			}
+ 			if(PQntuples(res)==0 || PQgetisnull(res,0,0) ) 
+ 			{
+ 				printf("%s is not a replicated table\n",*table);
+ 				dstring_free(&query);
+ 				dstring_free(&lock_tab_ids);
+ 				PQclear(res);
+ 				return -1;
+ 			}
+ 			tab_id = atoi(PQgetvalue(res,0,0));
+ 			PQclear(res);
+ 			if(table==stmt->table_locks) 
+ 			{
+ 				sprintf(buf,"%d",tab_id);
+ 				dstring_append(&lock_tab_ids,buf);
+ 			}
+ 			else
+ 			{
+ 				sprintf(buf,",%d",tab_id);
+ 				dstring_append(&lock_tab_ids,buf);
+ 			}
+ 		}
+ 		dstring_append(&lock_tab_ids,"}'");
+ 		
+ 	}
+ 	else {
+ 		dstring_append(&lock_tab_ids,"null");
+ 	}
+ 	dstring_terminate(&lock_tab_ids);
+ 	/**
+ 	 * Lock  replicated tables requested. (If none requested all tables get locked)
+ 	 */
+ 	
+ 	dstring_reset(&query);
  	slon_mkquery(&query,
! 				 "select \"_%s\".ddlScript_prepare(%d, %d,%s); ",
! 				 stmt->hdr.script->clustername,
! 				 stmt->ddl_setid, /* dstring_data(&script),  */ 
! 				 stmt->only_on_node,
! 				 dstring_data(&lock_tab_ids));
! 	
  	if (db_exec_evcommand((SlonikStmt *) stmt, adminfo1, &query) < 0)
  	{
  		dstring_free(&query);
+ 		dstring_free(&lock_tab_ids);
  		return -1;
  	}
  
***************
*** 3923,3932 ****
  	
  	printf("Submit DDL Event to subscribers...\n");
  
! 	slon_mkquery(&query, "select \"_%s\".ddlScript_complete(%d, $1::text, %d); ", 
  		     stmt->hdr.script->clustername,
  		     stmt->ddl_setid,  
! 		     stmt->only_on_node);
  
  	paramlens[PARMCOUNT-1] = 0;
  	paramfmts[PARMCOUNT-1] = 0;
--- 3987,3997 ----
  	
  	printf("Submit DDL Event to subscribers...\n");
  
! 	slon_mkquery(&query, "select \"_%s\".ddlScript_complete(%d, $1::text, %d,%s); ", 
  		     stmt->hdr.script->clustername,
  		     stmt->ddl_setid,  
! 		     stmt->only_on_node,
! 			 dstring_data(&lock_tab_ids));
  
  	paramlens[PARMCOUNT-1] = 0;
  	paramfmts[PARMCOUNT-1] = 0;
***************
*** 3950,3955 ****
--- 4015,4021 ----
  	
  	dstring_free(&script);
  	dstring_free(&query);
+ 	dstring_free(&lock_tab_ids);
  	return 0;
  }
  
Index: src/slonik/slonik.h
===================================================================
RCS file: /usr/local/cvsroot/slony1/slony1-engine/src/slonik/slonik.h,v
retrieving revision 1.29
diff -c -r1.29 slonik.h
*** src/slonik/slonik.h	31 Oct 2006 22:09:40 -0000	1.29
--- src/slonik/slonik.h	18 Dec 2006 04:28:00 -0000
***************
*** 408,413 ****
--- 408,414 ----
  	int			ev_origin;
  	int			only_on_node;
  	FILE	   *ddl_fd;
+ 	char    ** table_locks;
  };
  
  
Index: tests/testddl/exec_ddl.sh
===================================================================
RCS file: /usr/local/cvsroot/slony1/slony1-engine/tests/testddl/exec_ddl.sh,v
retrieving revision 1.2
diff -c -r1.2 exec_ddl.sh
*** tests/testddl/exec_ddl.sh	29 Mar 2006 17:10:31 -0000	1.2
--- tests/testddl/exec_ddl.sh	18 Dec 2006 04:28:00 -0000
***************
*** 5,8 ****
--- 5,16 ----
         FILENAME = '${testname}/ddl_updates.sql',
         EVENT NODE = 1
      );
+ 
+   EXECUTE SCRIPT (
+       SET ID=1,
+       FILENAME = '${testname}/ddl_updates_tbl_locks.sql',
+       EVENT NODE=1,
+       LOCK TABLES=('public.table5','public.table1')
+ 
+    );
  "
Index: tests/testddl/init_add_tables.ik
===================================================================
RCS file: /usr/local/cvsroot/slony1/slony1-engine/tests/testddl/init_add_tables.ik,v
retrieving revision 1.2
diff -c -r1.2 init_add_tables.ik
*** tests/testddl/init_add_tables.ik	9 Jan 2006 20:12:56 -0000	1.2
--- tests/testddl/init_add_tables.ik	18 Dec 2006 04:28:00 -0000
***************
*** 4,6 ****
--- 4,7 ----
  set add table (id=3, set id=1, origin=1, fully qualified name = 'public.table3', key = SERIAL);
  set add table (id=4, set id=1, origin=1, fully qualified name = 'public.table4');
  set add table (id=5, set id=1, origin=1, fully qualified name = 'public.billing_discount');
+ set add table (id=6, set id=1, origin=1, fully qualified name='public.table5');
\ No newline at end of file
Index: tests/testddl/init_schema.sql
===================================================================
RCS file: /usr/local/cvsroot/slony1/slony1-engine/tests/testddl/init_schema.sql,v
retrieving revision 1.2
diff -c -r1.2 init_schema.sql
*** tests/testddl/init_schema.sql	9 Jan 2006 20:12:56 -0000	1.2
--- tests/testddl/init_schema.sql	18 Dec 2006 04:28:00 -0000
***************
*** 26,31 ****
--- 26,32 ----
    primary key (id1, id2)
  );
  
+ 
  insert into table4 (data) values ('BA Baracus');
  insert into table4 (data) values ('HM Murdoch');
  insert into table4 (data) values ('Face');
***************
*** 48,50 ****
--- 49,61 ----
  
  ALTER TABLE ONLY billing_discount
      ADD CONSTRAINT billing_discount_pkey PRIMARY KEY (billing_discount_id);
+ 
+ CREATE TABLE table5 (
+ 
+ 	id serial4
+ 	,a int4
+ 	,PRIMARY KEY(id)
+ );
+ 
+ INSERT INTO table5(a) VALUES (1);
+ INSERT INTO table5(b) VALUES (2);



More information about the Slony1-general mailing list