Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wine-winehq
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wine
wine-winehq
Commits
0fd733bf
Commit
0fd733bf
authored
Jul 20, 2007
by
James Hawkins
Committed by
Alexandre Julliard
Jul 23, 2007
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
msi: Implement adding columns using the ALTER command.
parent
52cc7273
Show whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
167 additions
and
42 deletions
+167
-42
alter.c
dlls/msi/alter.c
+81
-1
create.c
dlls/msi/create.c
+1
-0
delete.c
dlls/msi/delete.c
+1
-0
distinct.c
dlls/msi/distinct.c
+1
-0
insert.c
dlls/msi/insert.c
+1
-0
join.c
dlls/msi/join.c
+1
-0
msipriv.h
dlls/msi/msipriv.h
+5
-0
order.c
dlls/msi/order.c
+1
-0
query.h
dlls/msi/query.h
+1
-1
select.c
dlls/msi/select.c
+1
-0
sql.y
dlls/msi/sql.y
+12
-2
streams.c
dlls/msi/streams.c
+1
-0
table.c
dlls/msi/table.c
+49
-22
db.c
dlls/msi/tests/db.c
+6
-15
package.c
dlls/msi/tests/package.c
+1
-1
tokenize.c
dlls/msi/tokenize.c
+2
-0
update.c
dlls/msi/update.c
+1
-0
where.c
dlls/msi/where.c
+1
-0
No files found.
dlls/msi/alter.c
View file @
0fd733bf
...
...
@@ -39,6 +39,7 @@ typedef struct tagMSIALTERVIEW
MSIVIEW
view
;
MSIDATABASE
*
db
;
MSIVIEW
*
table
;
column_info
*
colinfo
;
INT
hold
;
}
MSIALTERVIEW
;
...
...
@@ -60,6 +61,78 @@ static UINT ALTER_fetch_stream( struct tagMSIVIEW *view, UINT row, UINT col, ISt
return
ERROR_FUNCTION_FAILED
;
}
static
UINT
ITERATE_columns
(
MSIRECORD
*
row
,
LPVOID
param
)
{
(
*
(
UINT
*
)
param
)
++
;
return
ERROR_SUCCESS
;
}
static
BOOL
check_column_exists
(
MSIDATABASE
*
db
,
MSIVIEW
*
columns
,
LPCWSTR
table
,
LPCWSTR
column
)
{
MSIQUERY
*
view
;
MSIRECORD
*
rec
;
UINT
r
;
static
const
WCHAR
query
[]
=
{
'S'
,
'E'
,
'L'
,
'E'
,
'C'
,
'T'
,
' '
,
'*'
,
' '
,
'F'
,
'R'
,
'O'
,
'M'
,
' '
,
'`'
,
'_'
,
'C'
,
'o'
,
'l'
,
'u'
,
'm'
,
'n'
,
's'
,
'`'
,
' '
,
'W'
,
'H'
,
'E'
,
'R'
,
'E'
,
' '
,
'`'
,
'T'
,
'a'
,
'b'
,
'l'
,
'e'
,
'`'
,
'='
,
'\''
,
'%'
,
's'
,
'\''
,
' '
,
'A'
,
'N'
,
'D'
,
' '
,
'`'
,
'N'
,
'a'
,
'm'
,
'e'
,
'`'
,
'='
,
'\''
,
'%'
,
's'
,
'\''
,
0
};
r
=
MSI_OpenQuery
(
db
,
&
view
,
query
,
table
,
column
);
if
(
r
!=
ERROR_SUCCESS
)
return
FALSE
;
r
=
MSI_ViewExecute
(
view
,
NULL
);
if
(
r
!=
ERROR_SUCCESS
)
goto
done
;
r
=
MSI_ViewFetch
(
view
,
&
rec
);
if
(
r
==
ERROR_SUCCESS
)
msiobj_release
(
&
rec
->
hdr
);
done:
msiobj_release
(
&
view
->
hdr
);
return
(
r
==
ERROR_SUCCESS
);
}
static
UINT
alter_add_column
(
MSIALTERVIEW
*
av
)
{
UINT
r
,
colnum
=
1
;
MSIQUERY
*
view
;
MSIVIEW
*
columns
;
static
const
WCHAR
szColumns
[]
=
{
'_'
,
'C'
,
'o'
,
'l'
,
'u'
,
'm'
,
'n'
,
's'
,
0
};
static
const
WCHAR
query
[]
=
{
'S'
,
'E'
,
'L'
,
'E'
,
'C'
,
'T'
,
' '
,
'*'
,
' '
,
'F'
,
'R'
,
'O'
,
'M'
,
' '
,
'`'
,
'_'
,
'C'
,
'o'
,
'l'
,
'u'
,
'm'
,
'n'
,
's'
,
'`'
,
' '
,
'W'
,
'H'
,
'E'
,
'R'
,
'E'
,
' '
,
'`'
,
'T'
,
'a'
,
'b'
,
'l'
,
'e'
,
'`'
,
'='
,
'\''
,
'%'
,
's'
,
'\''
,
' '
,
'O'
,
'R'
,
'D'
,
'E'
,
'R'
,
' '
,
'B'
,
'Y'
,
' '
,
'`'
,
'N'
,
'u'
,
'm'
,
'b'
,
'e'
,
'r'
,
'`'
,
0
};
r
=
TABLE_CreateView
(
av
->
db
,
szColumns
,
&
columns
);
if
(
r
!=
ERROR_SUCCESS
)
return
r
;
if
(
check_column_exists
(
av
->
db
,
columns
,
av
->
colinfo
->
table
,
av
->
colinfo
->
column
))
return
ERROR_BAD_QUERY_SYNTAX
;
r
=
MSI_OpenQuery
(
av
->
db
,
&
view
,
query
,
av
->
colinfo
->
table
,
av
->
colinfo
->
column
);
if
(
r
==
ERROR_SUCCESS
)
{
r
=
MSI_IterateRecords
(
view
,
NULL
,
ITERATE_columns
,
&
colnum
);
msiobj_release
(
&
view
->
hdr
);
}
r
=
columns
->
ops
->
add_column
(
columns
,
av
->
colinfo
->
table
,
colnum
,
av
->
colinfo
->
column
,
av
->
colinfo
->
type
);
msiobj_release
(
&
columns
->
hdr
);
return
r
;
}
static
UINT
ALTER_execute
(
struct
tagMSIVIEW
*
view
,
MSIRECORD
*
record
)
{
MSIALTERVIEW
*
av
=
(
MSIALTERVIEW
*
)
view
;
...
...
@@ -70,6 +143,8 @@ static UINT ALTER_execute( struct tagMSIVIEW *view, MSIRECORD *record )
av
->
table
->
ops
->
add_ref
(
av
->
table
);
else
if
(
av
->
hold
==
-
1
)
av
->
table
->
ops
->
release
(
av
->
table
);
else
return
alter_add_column
(
av
);
return
ERROR_SUCCESS
;
}
...
...
@@ -147,9 +222,10 @@ static const MSIVIEWOPS alter_ops =
ALTER_find_matching_rows
,
NULL
,
NULL
,
NULL
,
};
UINT
ALTER_CreateView
(
MSIDATABASE
*
db
,
MSIVIEW
**
view
,
LPCWSTR
name
,
int
hold
)
UINT
ALTER_CreateView
(
MSIDATABASE
*
db
,
MSIVIEW
**
view
,
LPCWSTR
name
,
column_info
*
colinfo
,
int
hold
)
{
MSIALTERVIEW
*
av
;
UINT
r
;
...
...
@@ -164,10 +240,14 @@ UINT ALTER_CreateView( MSIDATABASE *db, MSIVIEW **view, LPCWSTR name, int hold )
if
(
r
!=
ERROR_SUCCESS
||
!
av
->
table
)
return
r
;
if
(
colinfo
)
colinfo
->
table
=
name
;
/* fill the structure */
av
->
view
.
ops
=
&
alter_ops
;
av
->
db
=
db
;
av
->
hold
=
hold
;
av
->
colinfo
=
colinfo
;
*
view
=
&
av
->
view
;
...
...
dlls/msi/create.c
View file @
0fd733bf
...
...
@@ -132,6 +132,7 @@ static const MSIVIEWOPS create_ops =
CREATE_delete
,
NULL
,
NULL
,
NULL
,
};
static
UINT
check_columns
(
column_info
*
col_info
)
...
...
dlls/msi/delete.c
View file @
0fd733bf
...
...
@@ -195,6 +195,7 @@ static const MSIVIEWOPS delete_ops =
DELETE_find_matching_rows
,
NULL
,
NULL
,
NULL
,
};
UINT
DELETE_CreateView
(
MSIDATABASE
*
db
,
MSIVIEW
**
view
,
MSIVIEW
*
table
)
...
...
dlls/msi/distinct.c
View file @
0fd733bf
...
...
@@ -284,6 +284,7 @@ static const MSIVIEWOPS distinct_ops =
DISTINCT_find_matching_rows
,
NULL
,
NULL
,
NULL
,
};
UINT
DISTINCT_CreateView
(
MSIDATABASE
*
db
,
MSIVIEW
**
view
,
MSIVIEW
*
table
)
...
...
dlls/msi/insert.c
View file @
0fd733bf
...
...
@@ -235,6 +235,7 @@ static const MSIVIEWOPS insert_ops =
INSERT_find_matching_rows
,
NULL
,
NULL
,
NULL
,
};
static
UINT
count_column_info
(
const
column_info
*
ci
)
...
...
dlls/msi/join.c
View file @
0fd733bf
...
...
@@ -255,6 +255,7 @@ static const MSIVIEWOPS join_ops =
JOIN_find_matching_rows
,
NULL
,
NULL
,
NULL
,
};
UINT
JOIN_CreateView
(
MSIDATABASE
*
db
,
MSIVIEW
**
view
,
...
...
dlls/msi/msipriv.h
View file @
0fd733bf
...
...
@@ -231,6 +231,11 @@ typedef struct tagMSIVIEWOPS
* release - decreases the reference count of the table
*/
UINT
(
*
release
)(
struct
tagMSIVIEW
*
view
);
/*
* add_column - adds a column to the table
*/
UINT
(
*
add_column
)(
struct
tagMSIVIEW
*
view
,
LPCWSTR
table
,
UINT
number
,
LPCWSTR
column
,
UINT
type
);
}
MSIVIEWOPS
;
struct
tagMSIVIEW
...
...
dlls/msi/order.c
View file @
0fd733bf
...
...
@@ -284,6 +284,7 @@ static const MSIVIEWOPS order_ops =
ORDER_find_matching_rows
,
NULL
,
NULL
,
NULL
,
};
static
UINT
ORDER_AddColumn
(
MSIORDERVIEW
*
ov
,
LPCWSTR
name
)
...
...
dlls/msi/query.h
View file @
0fd733bf
...
...
@@ -122,7 +122,7 @@ UINT DELETE_CreateView( MSIDATABASE *db, MSIVIEW **view, MSIVIEW *table );
UINT
JOIN_CreateView
(
MSIDATABASE
*
db
,
MSIVIEW
**
view
,
LPCWSTR
left
,
LPCWSTR
right
);
UINT
ALTER_CreateView
(
MSIDATABASE
*
db
,
MSIVIEW
**
view
,
LPCWSTR
name
,
int
hold
);
UINT
ALTER_CreateView
(
MSIDATABASE
*
db
,
MSIVIEW
**
view
,
LPCWSTR
name
,
column_info
*
colinfo
,
int
hold
);
UINT
STREAMS_CreateView
(
MSIDATABASE
*
db
,
MSIVIEW
**
view
);
...
...
dlls/msi/select.c
View file @
0fd733bf
...
...
@@ -277,6 +277,7 @@ static const MSIVIEWOPS select_ops =
SELECT_find_matching_rows
,
NULL
,
NULL
,
NULL
,
};
static
UINT
SELECT_AddColumn
(
MSISELECTVIEW
*
sv
,
LPCWSTR
name
)
...
...
dlls/msi/sql.y
View file @
0fd733bf
...
...
@@ -83,7 +83,7 @@ static struct expr * EXPR_wildcard( void *info );
}
%token TK_ALTER TK_AND TK_BY TK_CHAR TK_COMMA TK_CREATE TK_DELETE
%token TK_DISTINCT TK_DOT TK_EQ TK_FREE TK_FROM TK_GE TK_GT TK_HOLD
%token TK_DISTINCT TK_DOT TK_EQ TK_FREE TK_FROM TK_GE TK_GT TK_HOLD
TK_ADD
%token <str> TK_ID
%token TK_ILLEGAL TK_INSERT TK_INT
%token <str> TK_INTEGER
...
...
@@ -231,11 +231,21 @@ onealter:
SQL_input* sql = (SQL_input*) info;
MSIVIEW *alter = NULL;
ALTER_CreateView( sql->db, &alter, $3, $4 );
ALTER_CreateView( sql->db, &alter, $3,
NULL,
$4 );
if( !alter )
YYABORT;
$$ = alter;
}
| TK_ALTER TK_TABLE table TK_ADD column_and_type
{
SQL_input *sql = (SQL_input *)info;
MSIVIEW *alter = NULL;
ALTER_CreateView( sql->db, &alter, $3, $5, 0 );
if (!alter)
YYABORT;
$$ = alter;
}
;
alterop:
...
...
dlls/msi/streams.c
View file @
0fd733bf
...
...
@@ -339,6 +339,7 @@ static const MSIVIEWOPS streams_ops =
STREAMS_find_matching_rows
,
NULL
,
NULL
,
NULL
,
};
static
UINT
add_streams_to_table
(
MSISTREAMSVIEW
*
sv
)
...
...
dlls/msi/table.c
View file @
0fd733bf
...
...
@@ -97,8 +97,8 @@ static const WCHAR szType[] = { 'T','y','p','e',0 };
* Do not mark them const.
*/
static
MSICOLUMNINFO
_Columns_cols
[
4
]
=
{
{
szColumns
,
1
,
szTable
,
MSITYPE_VALID
|
MSITYPE_STRING
|
64
,
0
},
{
szColumns
,
2
,
szNumber
,
MSITYPE_VALID
|
2
,
2
},
{
szColumns
,
1
,
szTable
,
MSITYPE_VALID
|
MSITYPE_STRING
|
MSITYPE_KEY
|
64
,
0
},
{
szColumns
,
2
,
szNumber
,
MSITYPE_VALID
|
MSITYPE_KEY
|
2
,
2
},
{
szColumns
,
3
,
szName
,
MSITYPE_VALID
|
MSITYPE_STRING
|
64
,
4
},
{
szColumns
,
4
,
szType
,
MSITYPE_VALID
|
2
,
6
},
};
...
...
@@ -1035,6 +1035,26 @@ static UINT get_tablecolumns( MSIDATABASE *db,
return
ERROR_SUCCESS
;
}
static
void
msi_update_table_columns
(
MSIDATABASE
*
db
,
LPCWSTR
name
)
{
MSITABLE
*
table
;
UINT
size
,
offset
;
int
n
;
table
=
find_cached_table
(
db
,
name
);
msi_free
(
table
->
colinfo
);
table_get_column_info
(
db
,
name
,
&
table
->
colinfo
,
&
table
->
col_count
);
size
=
msi_table_get_row_size
(
table
->
colinfo
,
table
->
col_count
);
offset
=
table
->
colinfo
[
table
->
col_count
-
1
].
offset
;
for
(
n
=
0
;
n
<
table
->
row_count
;
n
++
)
{
table
->
data
[
n
]
=
msi_realloc
(
table
->
data
[
n
],
size
);
table
->
data
[
n
][
offset
]
=
(
BYTE
)
MSI_NULL_INTEGER
;
}
}
/* try to find the table name in the _Tables table */
BOOL
TABLE_Exists
(
MSIDATABASE
*
db
,
LPCWSTR
name
)
{
...
...
@@ -1674,6 +1694,32 @@ static UINT TABLE_release(struct tagMSIVIEW *view)
return
ref
;
}
static
UINT
TABLE_add_column
(
struct
tagMSIVIEW
*
view
,
LPCWSTR
table
,
UINT
number
,
LPCWSTR
column
,
UINT
type
)
{
MSITABLEVIEW
*
tv
=
(
MSITABLEVIEW
*
)
view
;
MSIRECORD
*
rec
;
UINT
r
;
rec
=
MSI_CreateRecord
(
4
);
if
(
!
rec
)
return
ERROR_OUTOFMEMORY
;
MSI_RecordSetStringW
(
rec
,
1
,
table
);
MSI_RecordSetInteger
(
rec
,
2
,
number
);
MSI_RecordSetStringW
(
rec
,
3
,
column
);
MSI_RecordSetInteger
(
rec
,
4
,
type
);
r
=
TABLE_insert_row
(
&
tv
->
view
,
rec
,
FALSE
);
if
(
r
!=
ERROR_SUCCESS
)
goto
done
;
msi_update_table_columns
(
tv
->
db
,
table
);
done:
msiobj_release
(
&
rec
->
hdr
);
return
r
;
}
static
const
MSIVIEWOPS
table_ops
=
{
TABLE_fetch_int
,
...
...
@@ -1690,6 +1736,7 @@ static const MSIVIEWOPS table_ops =
TABLE_find_matching_rows
,
TABLE_add_ref
,
TABLE_release
,
TABLE_add_column
,
};
UINT
TABLE_CreateView
(
MSIDATABASE
*
db
,
LPCWSTR
name
,
MSIVIEW
**
view
)
...
...
@@ -1977,26 +2024,6 @@ static UINT msi_table_find_row( MSITABLEVIEW *tv, MSIRECORD *rec, UINT *row )
return
r
;
}
static
void
msi_update_table_columns
(
MSIDATABASE
*
db
,
LPWSTR
name
)
{
MSITABLE
*
table
;
UINT
size
,
offset
;
int
n
;
table
=
find_cached_table
(
db
,
name
);
msi_free
(
table
->
colinfo
);
table_get_column_info
(
db
,
name
,
&
table
->
colinfo
,
&
table
->
col_count
);
size
=
msi_table_get_row_size
(
table
->
colinfo
,
table
->
col_count
);
offset
=
table
->
colinfo
[
table
->
col_count
-
1
].
offset
;
for
(
n
=
0
;
n
<
table
->
row_count
;
n
++
)
{
table
->
data
[
n
]
=
msi_realloc
(
table
->
data
[
n
],
size
);
table
->
data
[
n
][
offset
]
=
(
BYTE
)
MSI_NULL_INTEGER
;
}
}
typedef
struct
{
struct
list
entry
;
...
...
dlls/msi/tests/db.c
View file @
0fd733bf
...
...
@@ -2962,10 +2962,7 @@ static void test_alter(void)
query
=
"ALTER TABLE `U` ADD `C` INTEGER"
;
r
=
run_query
(
hdb
,
0
,
query
);
todo_wine
{
ok
(
r
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
r
);
}
/* add column C again */
query
=
"ALTER TABLE `U` ADD `C` INTEGER"
;
...
...
@@ -2974,17 +2971,11 @@ static void test_alter(void)
query
=
"ALTER TABLE `U` ADD `D` INTEGER TEMPORARY"
;
r
=
run_query
(
hdb
,
0
,
query
);
todo_wine
{
ok
(
r
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
r
);
}
query
=
"INSERT INTO `U` ( `A`, `B`, `C`, `D` ) VALUES ( 1, 2, 3, 4 )"
;
r
=
run_query
(
hdb
,
0
,
query
);
todo_wine
{
ok
(
r
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
r
);
}
query
=
"ALTER TABLE `U` ADD `D` INTEGER TEMPORARY HOLD"
;
r
=
run_query
(
hdb
,
0
,
query
);
...
...
@@ -2992,17 +2983,11 @@ static void test_alter(void)
query
=
"INSERT INTO `U` ( `A`, `B`, `C`, `D` ) VALUES ( 5, 6, 7, 8 )"
;
r
=
run_query
(
hdb
,
0
,
query
);
todo_wine
{
ok
(
r
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
r
);
}
query
=
"SELECT * FROM `U` WHERE `D` = 8"
;
r
=
run_query
(
hdb
,
0
,
query
);
todo_wine
{
ok
(
r
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
r
);
}
query
=
"ALTER TABLE `U` ADD `D` INTEGER TEMPORARY FREE"
;
r
=
run_query
(
hdb
,
0
,
query
);
...
...
@@ -3025,11 +3010,17 @@ static void test_alter(void)
/* column D is removed */
query
=
"SELECT * FROM `U` WHERE `D` = 8"
;
r
=
run_query
(
hdb
,
0
,
query
);
todo_wine
{
ok
(
r
==
ERROR_BAD_QUERY_SYNTAX
,
"Expected ERROR_BAD_QUERY_SYNTAX, got %d
\n
"
,
r
);
}
query
=
"INSERT INTO `U` ( `A`, `B`, `C`, `D` ) VALUES ( 9, 10, 11, 12 )"
;
r
=
run_query
(
hdb
,
0
,
query
);
todo_wine
{
ok
(
r
==
ERROR_BAD_QUERY_SYNTAX
,
"Expected ERROR_BAD_QUERY_SYNTAX, got %d
\n
"
,
r
);
}
/* add the column again */
query
=
"ALTER TABLE `U` ADD `E` INTEGER TEMPORARY HOLD"
;
...
...
dlls/msi/tests/package.c
View file @
0fd733bf
...
...
@@ -1701,7 +1701,7 @@ static void test_property_table(void)
query
=
"ALTER TABLE `_Property` ADD `extra` INTEGER"
;
r
=
run_query
(
hdb
,
query
);
todo_wine
ok
(
r
==
ERROR_SUCCESS
,
"failed to add column
\n
"
);
ok
(
r
==
ERROR_SUCCESS
,
"failed to add column
\n
"
);
hpkg
=
package_from_db
(
hdb
);
todo_wine
...
...
dlls/msi/tokenize.c
View file @
0fd733bf
...
...
@@ -38,6 +38,7 @@ struct Keyword {
#define MAX_TOKEN_LEN 11
static
const
WCHAR
ADD_W
[]
=
{
'A'
,
'D'
,
'D'
,
0
};
static
const
WCHAR
ALTER_W
[]
=
{
'A'
,
'L'
,
'T'
,
'E'
,
'R'
,
0
};
static
const
WCHAR
AND_W
[]
=
{
'A'
,
'N'
,
'D'
,
0
};
static
const
WCHAR
BY_W
[]
=
{
'B'
,
'Y'
,
0
};
...
...
@@ -78,6 +79,7 @@ static const WCHAR WHERE_W[] = { 'W','H','E','R','E',0 };
** These are the keywords
*/
static
const
Keyword
aKeywordTable
[]
=
{
{
ADD_W
,
TK_ADD
},
{
ALTER_W
,
TK_ALTER
},
{
AND_W
,
TK_AND
},
{
BY_W
,
TK_BY
},
...
...
dlls/msi/update.c
View file @
0fd733bf
...
...
@@ -187,6 +187,7 @@ static const MSIVIEWOPS update_ops =
UPDATE_find_matching_rows
,
NULL
,
NULL
,
NULL
,
};
UINT
UPDATE_CreateView
(
MSIDATABASE
*
db
,
MSIVIEW
**
view
,
LPCWSTR
table
,
...
...
dlls/msi/where.c
View file @
0fd733bf
...
...
@@ -449,6 +449,7 @@ static const MSIVIEWOPS where_ops =
WHERE_find_matching_rows
,
NULL
,
NULL
,
NULL
,
};
static
UINT
WHERE_VerifyCondition
(
MSIDATABASE
*
db
,
MSIVIEW
*
table
,
struct
expr
*
cond
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment