Commit 62c544cf authored by James Hawkins's avatar James Hawkins Committed by Alexandre Julliard

msi: Implement the DROP TABLE sql command.

parent 68525652
...@@ -19,6 +19,7 @@ C_SRCS = \ ...@@ -19,6 +19,7 @@ C_SRCS = \
delete.c \ delete.c \
dialog.c \ dialog.c \
distinct.c \ distinct.c \
drop.c \
events.c \ events.c \
files.c \ files.c \
font.c \ font.c \
......
...@@ -242,6 +242,7 @@ static const MSIVIEWOPS alter_ops = ...@@ -242,6 +242,7 @@ static const MSIVIEWOPS alter_ops =
NULL, NULL,
NULL, NULL,
NULL, NULL,
NULL,
}; };
UINT ALTER_CreateView( MSIDATABASE *db, MSIVIEW **view, LPCWSTR name, column_info *colinfo, int hold ) UINT ALTER_CreateView( MSIDATABASE *db, MSIVIEW **view, LPCWSTR name, column_info *colinfo, int hold )
......
...@@ -137,6 +137,7 @@ static const MSIVIEWOPS create_ops = ...@@ -137,6 +137,7 @@ static const MSIVIEWOPS create_ops =
NULL, NULL,
NULL, NULL,
NULL, NULL,
NULL,
}; };
static UINT check_columns( column_info *col_info ) static UINT check_columns( column_info *col_info )
...@@ -157,7 +158,7 @@ UINT CREATE_CreateView( MSIDATABASE *db, MSIVIEW **view, LPWSTR table, ...@@ -157,7 +158,7 @@ UINT CREATE_CreateView( MSIDATABASE *db, MSIVIEW **view, LPWSTR table,
{ {
MSICREATEVIEW *cv = NULL; MSICREATEVIEW *cv = NULL;
UINT r; UINT r;
const column_info *col; column_info *col;
BOOL temp = TRUE; BOOL temp = TRUE;
TRACE("%p\n", cv ); TRACE("%p\n", cv );
...@@ -171,11 +172,16 @@ UINT CREATE_CreateView( MSIDATABASE *db, MSIVIEW **view, LPWSTR table, ...@@ -171,11 +172,16 @@ UINT CREATE_CreateView( MSIDATABASE *db, MSIVIEW **view, LPWSTR table,
return ERROR_FUNCTION_FAILED; return ERROR_FUNCTION_FAILED;
for( col = col_info; col; col = col->next ) for( col = col_info; col; col = col->next )
{
if (!col->table)
col->table = strdupW(table);
if( !col->temporary ) if( !col->temporary )
{ {
temp = FALSE; temp = FALSE;
break; break;
} }
}
/* fill the structure */ /* fill the structure */
cv->view.ops = &create_ops; cv->view.ops = &create_ops;
......
...@@ -191,7 +191,8 @@ static const MSIVIEWOPS delete_ops = ...@@ -191,7 +191,8 @@ static const MSIVIEWOPS delete_ops =
NULL, NULL,
NULL, NULL,
NULL, NULL,
NULL NULL,
NULL,
}; };
UINT DELETE_CreateView( MSIDATABASE *db, MSIVIEW **view, MSIVIEW *table ) UINT DELETE_CreateView( MSIDATABASE *db, MSIVIEW **view, MSIVIEW *table )
......
...@@ -296,6 +296,7 @@ static const MSIVIEWOPS distinct_ops = ...@@ -296,6 +296,7 @@ static const MSIVIEWOPS distinct_ops =
NULL, NULL,
NULL, NULL,
DISTINCT_sort, DISTINCT_sort,
NULL,
}; };
UINT DISTINCT_CreateView( MSIDATABASE *db, MSIVIEW **view, MSIVIEW *table ) UINT DISTINCT_CreateView( MSIDATABASE *db, MSIVIEW **view, MSIVIEW *table )
......
/*
* Implementation of the Microsoft Installer (msi.dll)
*
* Copyright 2008 James Hawkins
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <stdarg.h>
#include "windef.h"
#include "winbase.h"
#include "winerror.h"
#include "wine/debug.h"
#include "msi.h"
#include "msiquery.h"
#include "objbase.h"
#include "objidl.h"
#include "msipriv.h"
#include "query.h"
WINE_DEFAULT_DEBUG_CHANNEL(msidb);
typedef struct tagMSIDROPVIEW
{
MSIVIEW view;
MSIDATABASE *db;
MSIVIEW *table;
column_info *colinfo;
INT hold;
} MSIDROPVIEW;
static UINT DROP_execute(struct tagMSIVIEW *view, MSIRECORD *record)
{
MSIDROPVIEW *dv = (MSIDROPVIEW*)view;
UINT r;
TRACE("%p %p\n", dv, record);
if( !dv->table )
return ERROR_FUNCTION_FAILED;
r = dv->table->ops->execute(dv->table, record);
if (r != ERROR_SUCCESS)
return r;
return dv->table->ops->drop(dv->table);
}
static UINT DROP_close(struct tagMSIVIEW *view)
{
MSIDROPVIEW *dv = (MSIDROPVIEW*)view;
TRACE("%p\n", dv);
return ERROR_SUCCESS;
}
static UINT DROP_get_dimensions(struct tagMSIVIEW *view, UINT *rows, UINT *cols)
{
MSIDROPVIEW *dv = (MSIDROPVIEW*)view;
TRACE("%p %p %p\n", dv, rows, cols);
return ERROR_FUNCTION_FAILED;
}
static const MSIVIEWOPS drop_ops =
{
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
DROP_execute,
DROP_close,
DROP_get_dimensions,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
};
UINT DROP_CreateView(MSIDATABASE *db, MSIVIEW **view, LPCWSTR name)
{
MSIDROPVIEW *dv;
UINT r;
TRACE("%p %s\n", view, debugstr_w(name));
dv = msi_alloc_zero(sizeof *dv);
if(!dv)
return ERROR_FUNCTION_FAILED;
r = TABLE_CreateView(db, name, &dv->table);
if (r != ERROR_SUCCESS || !dv->table)
return r;
dv->view.ops = &drop_ops;
dv->db = db;
*view = (MSIVIEW *)dv;
return ERROR_SUCCESS;
}
...@@ -239,6 +239,7 @@ static const MSIVIEWOPS insert_ops = ...@@ -239,6 +239,7 @@ static const MSIVIEWOPS insert_ops =
NULL, NULL,
NULL, NULL,
NULL, NULL,
NULL,
}; };
static UINT count_column_info( const column_info *ci ) static UINT count_column_info( const column_info *ci )
......
...@@ -306,6 +306,7 @@ static const MSIVIEWOPS join_ops = ...@@ -306,6 +306,7 @@ static const MSIVIEWOPS join_ops =
NULL, NULL,
NULL, NULL,
JOIN_sort, JOIN_sort,
NULL,
}; };
UINT JOIN_CreateView( MSIDATABASE *db, MSIVIEW **view, LPWSTR tables ) UINT JOIN_CreateView( MSIDATABASE *db, MSIVIEW **view, LPWSTR tables )
......
...@@ -274,6 +274,11 @@ typedef struct tagMSIVIEWOPS ...@@ -274,6 +274,11 @@ typedef struct tagMSIVIEWOPS
* sort - orders the table by columns * sort - orders the table by columns
*/ */
UINT (*sort)( struct tagMSIVIEW *view, column_info *columns ); UINT (*sort)( struct tagMSIVIEW *view, column_info *columns );
/*
* drop - drops the table from the database
*/
UINT (*drop)( struct tagMSIVIEW *view );
} MSIVIEWOPS; } MSIVIEWOPS;
struct tagMSIVIEW struct tagMSIVIEW
......
...@@ -117,6 +117,8 @@ UINT STREAMS_CreateView( MSIDATABASE *db, MSIVIEW **view ); ...@@ -117,6 +117,8 @@ UINT STREAMS_CreateView( MSIDATABASE *db, MSIVIEW **view );
UINT STORAGES_CreateView( MSIDATABASE *db, MSIVIEW **view ); UINT STORAGES_CreateView( MSIDATABASE *db, MSIVIEW **view );
UINT DROP_CreateView( MSIDATABASE *db, MSIVIEW **view, LPCWSTR name );
int sqliteGetToken(const WCHAR *z, int *tokenType); int sqliteGetToken(const WCHAR *z, int *tokenType);
MSIRECORD *msi_query_merge_record( UINT fields, const column_info *vl, MSIRECORD *rec ); MSIRECORD *msi_query_merge_record( UINT fields, const column_info *vl, MSIRECORD *rec );
......
...@@ -363,6 +363,7 @@ static const MSIVIEWOPS select_ops = ...@@ -363,6 +363,7 @@ static const MSIVIEWOPS select_ops =
NULL, NULL,
NULL, NULL,
SELECT_sort, SELECT_sort,
NULL,
}; };
static UINT SELECT_AddColumn( MSISELECTVIEW *sv, LPCWSTR name ) static UINT SELECT_AddColumn( MSISELECTVIEW *sv, LPCWSTR name )
......
...@@ -81,7 +81,7 @@ static struct expr * EXPR_wildcard( void *info ); ...@@ -81,7 +81,7 @@ static struct expr * EXPR_wildcard( void *info );
int integer; int integer;
} }
%token TK_ALTER TK_AND TK_BY TK_CHAR TK_COMMA TK_CREATE TK_DELETE %token TK_ALTER TK_AND TK_BY TK_CHAR TK_COMMA TK_CREATE TK_DELETE TK_DROP
%token TK_DISTINCT TK_DOT TK_EQ TK_FREE TK_FROM TK_GE TK_GT TK_HOLD TK_ADD %token TK_DISTINCT TK_DOT TK_EQ TK_FREE TK_FROM TK_GE TK_GT TK_HOLD TK_ADD
%token <str> TK_ID %token <str> TK_ID
%token TK_ILLEGAL TK_INSERT TK_INT %token TK_ILLEGAL TK_INSERT TK_INT
...@@ -106,7 +106,7 @@ static struct expr * EXPR_wildcard( void *info ); ...@@ -106,7 +106,7 @@ static struct expr * EXPR_wildcard( void *info );
%type <column_list> selcollist column column_and_type column_def table_def %type <column_list> selcollist column column_and_type column_def table_def
%type <column_list> column_assignment update_assign_list constlist %type <column_list> column_assignment update_assign_list constlist
%type <query> query from fromtable selectfrom unorderedsel %type <query> query from fromtable selectfrom unorderedsel
%type <query> oneupdate onedelete oneselect onequery onecreate oneinsert onealter %type <query> oneupdate onedelete oneselect onequery onecreate oneinsert onealter onedrop
%type <expr> expr val column_val const_val %type <expr> expr val column_val const_val
%type <column_type> column_type data_type data_type_l data_count %type <column_type> column_type data_type data_type_l data_count
%type <integer> number alterop %type <integer> number alterop
...@@ -135,6 +135,7 @@ onequery: ...@@ -135,6 +135,7 @@ onequery:
| oneupdate | oneupdate
| onedelete | onedelete
| onealter | onealter
| onedrop
; ;
oneinsert: oneinsert:
...@@ -267,6 +268,19 @@ alterop: ...@@ -267,6 +268,19 @@ alterop:
} }
; ;
onedrop:
TK_DROP TK_TABLE table
{
SQL_input* sql = (SQL_input*) info;
UINT r;
$$ = NULL;
r = DROP_CreateView( sql->db, &$$, $3 );
if( r != ERROR_SUCCESS || !$$ )
YYABORT;
}
;
table_def: table_def:
column_def TK_PRIMARY TK_KEY selcollist column_def TK_PRIMARY TK_KEY selcollist
{ {
......
...@@ -474,6 +474,7 @@ static const MSIVIEWOPS storages_ops = ...@@ -474,6 +474,7 @@ static const MSIVIEWOPS storages_ops =
NULL, NULL,
NULL, NULL,
NULL, NULL,
NULL,
}; };
static INT add_storages_to_table(MSISTORAGESVIEW *sv) static INT add_storages_to_table(MSISTORAGESVIEW *sv)
......
...@@ -438,6 +438,7 @@ static const MSIVIEWOPS streams_ops = ...@@ -438,6 +438,7 @@ static const MSIVIEWOPS streams_ops =
NULL, NULL,
NULL, NULL,
NULL, NULL,
NULL,
}; };
static INT add_streams_to_table(MSISTREAMSVIEW *sv) static INT add_streams_to_table(MSISTREAMSVIEW *sv)
......
...@@ -109,7 +109,7 @@ static const MSICOLUMNINFO _Columns_cols[4] = { ...@@ -109,7 +109,7 @@ static const MSICOLUMNINFO _Columns_cols[4] = {
}; };
static const MSICOLUMNINFO _Tables_cols[1] = { static const MSICOLUMNINFO _Tables_cols[1] = {
{ szTables, 1, szName, MSITYPE_VALID | MSITYPE_STRING | 64, 0, 0, NULL }, { szTables, 1, szName, MSITYPE_VALID | MSITYPE_STRING | MSITYPE_KEY | 64, 0, 0, NULL },
}; };
#define MAX_STREAM_NAME 0x1f #define MAX_STREAM_NAME 0x1f
...@@ -1073,20 +1073,12 @@ BOOL TABLE_Exists( MSIDATABASE *db, LPCWSTR name ) ...@@ -1073,20 +1073,12 @@ BOOL TABLE_Exists( MSIDATABASE *db, LPCWSTR name )
count = table->row_count; count = table->row_count;
for( i=0; i<count; i++ ) for( i=0; i<count; i++ )
if( table->data[ i ][ 0 ] == table_id ) if( table->data[ i ][ 0 ] == table_id )
break; return TRUE;
if (i!=count)
return TRUE;
count = table->nonpersistent_row_count; count = table->nonpersistent_row_count;
for( i=0; i<count; i++ ) for( i=0; i<count; i++ )
if( table->nonpersistent_data[ i ][ 0 ] == table_id ) if( table->nonpersistent_data[ i ][ 0 ] == table_id )
break; return TRUE;
if (i!=count)
return TRUE;
TRACE("Searched %d tables, but %d was not found\n", count, table_id );
return FALSE; return FALSE;
} }
...@@ -2058,6 +2050,52 @@ static UINT TABLE_sort(struct tagMSIVIEW *view, column_info *columns) ...@@ -2058,6 +2050,52 @@ static UINT TABLE_sort(struct tagMSIVIEW *view, column_info *columns)
return ERROR_SUCCESS; return ERROR_SUCCESS;
} }
static UINT TABLE_drop(struct tagMSIVIEW *view)
{
MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
MSIVIEW *tables = NULL;
MSIRECORD *rec = NULL;
UINT i, r, row;
TRACE("dropping table %s\n", debugstr_w(tv->name));
for (i = 0; i < tv->table->col_count; i++)
{
r = TABLE_remove_column(view, tv->table->colinfo[i].tablename,
tv->table->colinfo[i].number);
if (r != ERROR_SUCCESS)
return r;
}
rec = MSI_CreateRecord(1);
if (!rec)
return ERROR_OUTOFMEMORY;
MSI_RecordSetStringW(rec, 1, tv->name);
r = TABLE_CreateView(tv->db, szTables, &tables);
if (r != ERROR_SUCCESS)
return r;
r = msi_table_find_row((MSITABLEVIEW *)tables, rec, &row);
if (r != ERROR_SUCCESS)
goto done;
r = TABLE_delete_row(tables, row);
if (r != ERROR_SUCCESS)
goto done;
list_remove(&tv->table->entry);
free_table(tv->table);
TABLE_delete(view);
done:
msiobj_release(&rec->hdr);
tables->ops->delete(tables);
return r;
}
static const MSIVIEWOPS table_ops = static const MSIVIEWOPS table_ops =
{ {
TABLE_fetch_int, TABLE_fetch_int,
...@@ -2078,6 +2116,7 @@ static const MSIVIEWOPS table_ops = ...@@ -2078,6 +2116,7 @@ static const MSIVIEWOPS table_ops =
TABLE_add_column, TABLE_add_column,
TABLE_remove_column, TABLE_remove_column,
TABLE_sort, TABLE_sort,
TABLE_drop,
}; };
UINT TABLE_CreateView( MSIDATABASE *db, LPCWSTR name, MSIVIEW **view ) UINT TABLE_CreateView( MSIDATABASE *db, LPCWSTR name, MSIVIEW **view )
......
...@@ -6313,6 +6313,94 @@ static void test_dbtopackage(void) ...@@ -6313,6 +6313,94 @@ static void test_dbtopackage(void)
DeleteFileA(msifile); DeleteFileA(msifile);
} }
static void test_droptable(void)
{
MSIHANDLE hdb, hview, hrec;
LPCSTR query;
UINT r;
r = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb);
ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r);
query = "CREATE TABLE `One` ( `A` INT PRIMARY KEY `A` )";
r = run_query(hdb, 0, query);
ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r);
query = "SELECT * FROM `One`";
r = do_query(hdb, query, &hrec);
ok(r == ERROR_NO_MORE_ITEMS, "Expected ERROR_NO_MORE_ITEMS, got %d\n", r);
query = "SELECT * FROM `_Tables` WHERE `Name` = 'One'";
r = do_query(hdb, query, &hrec);
ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r);
MsiCloseHandle(hrec);
query = "SELECT * FROM `_Columns` WHERE `Table` = 'One'";
r = do_query(hdb, query, &hrec);
ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r);
MsiCloseHandle(hrec);
query = "DROP `One`";
r = run_query(hdb, 0, query);
ok(r == ERROR_BAD_QUERY_SYNTAX,
"Expected ERROR_BAD_QUERY_SYNTAX, got %d\n", r);
query = "DROP TABLE";
r = run_query(hdb, 0, query);
ok(r == ERROR_BAD_QUERY_SYNTAX,
"Expected ERROR_BAD_QUERY_SYNTAX, got %d\n", r);
query = "DROP TABLE `One`";
hview = 0;
r = MsiDatabaseOpenViewA(hdb, query, &hview);
ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r);
r = MsiViewExecute(hview, 0);
ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r);
r = MsiViewFetch(hview, &hrec);
ok(r == ERROR_FUNCTION_FAILED,
"Expected ERROR_FUNCTION_FAILED, got %d\n", r);
MsiViewClose(hview);
MsiCloseHandle(hview);
query = "SELECT * FROM `IDontExist`";
r = do_query(hdb, query, &hrec);
ok(r == ERROR_BAD_QUERY_SYNTAX,
"Expected ERROR_BAD_QUERY_SYNTAX, got %d\n", r);
query = "SELECT * FROM `One`";
r = do_query(hdb, query, &hrec);
ok(r == ERROR_BAD_QUERY_SYNTAX,
"Expected ERROR_BAD_QUERY_SYNTAX, got %d\n", r);
query = "CREATE TABLE `One` ( `A` INT PRIMARY KEY `A` )";
r = run_query(hdb, 0, query);
ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r);
query = "DROP TABLE One";
r = run_query(hdb, 0, query);
ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r);
query = "SELECT * FROM `One`";
r = do_query(hdb, query, &hrec);
ok(r == ERROR_BAD_QUERY_SYNTAX,
"Expected ERROR_BAD_QUERY_SYNTAX, got %d\n", r);
query = "SELECT * FROM `_Tables` WHERE `Name` = 'One'";
r = do_query(hdb, query, &hrec);
ok(r == ERROR_NO_MORE_ITEMS, "Expected ERROR_NO_MORE_ITEMS, got %d\n", r);
query = "SELECT * FROM `_Columns` WHERE `Table` = 'One'";
r = do_query(hdb, query, &hrec);
ok(r == ERROR_NO_MORE_ITEMS, "Expected ERROR_NO_MORE_ITEMS, got %d\n", r);
MsiCloseHandle(hdb);
DeleteFileA(msifile);
}
START_TEST(db) START_TEST(db)
{ {
test_msidatabase(); test_msidatabase();
...@@ -6352,4 +6440,5 @@ START_TEST(db) ...@@ -6352,4 +6440,5 @@ START_TEST(db)
test_where_viewmodify(); test_where_viewmodify();
test_storages_table(); test_storages_table();
test_dbtopackage(); test_dbtopackage();
test_droptable();
} }
...@@ -47,6 +47,7 @@ static const WCHAR CHARACTER_W[] = { 'C','H','A','R','A','C','T','E','R',0 }; ...@@ -47,6 +47,7 @@ static const WCHAR CHARACTER_W[] = { 'C','H','A','R','A','C','T','E','R',0 };
static const WCHAR CREATE_W[] = { 'C','R','E','A','T','E',0 }; static const WCHAR CREATE_W[] = { 'C','R','E','A','T','E',0 };
static const WCHAR DELETE_W[] = { 'D','E','L','E','T','E',0 }; static const WCHAR DELETE_W[] = { 'D','E','L','E','T','E',0 };
static const WCHAR DISTINCT_W[] = { 'D','I','S','T','I','N','C','T',0 }; static const WCHAR DISTINCT_W[] = { 'D','I','S','T','I','N','C','T',0 };
static const WCHAR DROP_W[] = { 'D','R','O','P',0 };
static const WCHAR FREE_W[] = { 'F','R','E','E',0 }; static const WCHAR FREE_W[] = { 'F','R','E','E',0 };
static const WCHAR FROM_W[] = { 'F','R','O','M',0 }; static const WCHAR FROM_W[] = { 'F','R','O','M',0 };
static const WCHAR HOLD_W[] = { 'H','O','L','D',0 }; static const WCHAR HOLD_W[] = { 'H','O','L','D',0 };
...@@ -89,6 +90,7 @@ static const Keyword aKeywordTable[] = { ...@@ -89,6 +90,7 @@ static const Keyword aKeywordTable[] = {
{ CREATE_W, TK_CREATE }, { CREATE_W, TK_CREATE },
{ DELETE_W, TK_DELETE }, { DELETE_W, TK_DELETE },
{ DISTINCT_W, TK_DISTINCT }, { DISTINCT_W, TK_DISTINCT },
{ DROP_W, TK_DROP },
{ FREE_W, TK_FREE }, { FREE_W, TK_FREE },
{ FROM_W, TK_FROM }, { FROM_W, TK_FROM },
{ HOLD_W, TK_HOLD }, { HOLD_W, TK_HOLD },
......
...@@ -222,6 +222,7 @@ static const MSIVIEWOPS update_ops = ...@@ -222,6 +222,7 @@ static const MSIVIEWOPS update_ops =
NULL, NULL,
NULL, NULL,
NULL, NULL,
NULL,
}; };
UINT UPDATE_CreateView( MSIDATABASE *db, MSIVIEW **view, LPCWSTR table, UINT UPDATE_CreateView( MSIDATABASE *db, MSIVIEW **view, LPCWSTR table,
......
...@@ -585,6 +585,7 @@ static const MSIVIEWOPS where_ops = ...@@ -585,6 +585,7 @@ static const MSIVIEWOPS where_ops =
NULL, NULL,
NULL, NULL,
WHERE_sort, WHERE_sort,
NULL,
}; };
static UINT WHERE_VerifyCondition( MSIDATABASE *db, MSIVIEW *table, struct expr *cond, static UINT WHERE_VerifyCondition( MSIDATABASE *db, MSIVIEW *table, struct expr *cond,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment