Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gosqlite
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
gosqlite
Commits
d8830656
Commit
d8830656
authored
Mar 20, 2014
by
gwenn
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Move configuation specific functions to a dedicated file.
parent
b4fde85e
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
234 additions
and
198 deletions
+234
-198
config.c
config.c
+14
-0
config.go
config.go
+149
-0
config_test.go
config_test.go
+67
-0
sqlite.go
sqlite.go
+0
-93
sqlite_test.go
sqlite_test.go
+0
-35
trace.c
trace.c
+0
-8
trace.go
trace.go
+0
-42
trace_test.go
trace_test.go
+4
-20
No files found.
config.c
0 → 100644
View file @
d8830656
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include <sqlite3.h>
//#include "_cgo_export.h"
int
goSqlite3ConfigThreadMode
(
int
mode
)
{
return
sqlite3_config
(
mode
);
}
int
goSqlite3Config
(
int
op
,
int
mode
)
{
return
sqlite3_config
(
op
,
mode
);
}
\ No newline at end of file
config.go
0 → 100644
View file @
d8830656
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package
sqlite
/*
#include <sqlite3.h>
#include <stdlib.h>
// cgo doesn't support varargs
static inline int my_db_config(sqlite3 *db, int op, int v, int *ok) {
return sqlite3_db_config(db, op, v, ok);
}
int goSqlite3ConfigThreadMode(int mode);
int goSqlite3Config(int op, int mode);
*/
import
"C"
import
"unsafe"
// ThreadingMode enumerates SQLite threading mode
// See ConfigThreadingMode
type
ThreadingMode
int32
const
(
SingleThread
ThreadingMode
=
C
.
SQLITE_CONFIG_SINGLETHREAD
MultiThread
ThreadingMode
=
C
.
SQLITE_CONFIG_MULTITHREAD
Serialized
ThreadingMode
=
C
.
SQLITE_CONFIG_SERIALIZED
)
// ConfigThreadingMode alters threading mode.
// (See sqlite3_config(SQLITE_CONFIG_SINGLETHREAD|SQLITE_CONFIG_MULTITHREAD|SQLITE_CONFIG_SERIALIZED): http://sqlite.org/c3ref/config.html)
func
ConfigThreadingMode
(
mode
ThreadingMode
)
error
{
rv
:=
C
.
goSqlite3ConfigThreadMode
(
C
.
int
(
mode
))
if
rv
==
C
.
SQLITE_OK
{
return
nil
}
return
Errno
(
rv
)
}
// ConfigMemStatus enables or disables the collection of memory allocation statistics.
// (See sqlite3_config(SQLITE_CONFIG_MEMSTATUS): http://sqlite.org/c3ref/config.html)
func
ConfigMemStatus
(
b
bool
)
error
{
rv
:=
C
.
goSqlite3Config
(
C
.
SQLITE_CONFIG_MEMSTATUS
,
btocint
(
b
))
if
rv
==
C
.
SQLITE_OK
{
return
nil
}
return
Errno
(
rv
)
}
// ConfigUri enables or disables URI handling.
// (See sqlite3_config(SQLITE_CONFIG_URI): http://sqlite.org/c3ref/config.html)
func
ConfigUri
(
b
bool
)
error
{
rv
:=
C
.
goSqlite3Config
(
C
.
SQLITE_CONFIG_URI
,
btocint
(
b
))
if
rv
==
C
.
SQLITE_OK
{
return
nil
}
return
Errno
(
rv
)
}
// EnableSharedCache enables or disables shared pager cache
// (See http://sqlite.org/c3ref/enable_shared_cache.html)
func
EnableSharedCache
(
b
bool
)
error
{
rv
:=
C
.
sqlite3_enable_shared_cache
(
btocint
(
b
))
if
rv
==
C
.
SQLITE_OK
{
return
nil
}
return
Errno
(
rv
)
}
// EnableFKey enables or disables the enforcement of foreign key constraints.
// Calls sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_FKEY, b).
// Another way is PRAGMA foreign_keys = boolean;
//
// (See http://sqlite.org/c3ref/c_dbconfig_enable_fkey.html)
func
(
c
*
Conn
)
EnableFKey
(
b
bool
)
(
bool
,
error
)
{
return
c
.
queryOrSetEnableDbConfig
(
C
.
SQLITE_DBCONFIG_ENABLE_FKEY
,
btocint
(
b
))
}
// IsFKeyEnabled reports if the enforcement of foreign key constraints is enabled or not.
// Calls sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_FKEY, -1).
// Another way is PRAGMA foreign_keys;
//
// (See http://sqlite.org/c3ref/c_dbconfig_enable_fkey.html)
func
(
c
*
Conn
)
IsFKeyEnabled
()
(
bool
,
error
)
{
return
c
.
queryOrSetEnableDbConfig
(
C
.
SQLITE_DBCONFIG_ENABLE_FKEY
,
-
1
)
}
// EnableTriggers enables or disables triggers.
// Calls sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_TRIGGER, b).
//
// (See http://sqlite.org/c3ref/c_dbconfig_enable_fkey.html)
func
(
c
*
Conn
)
EnableTriggers
(
b
bool
)
(
bool
,
error
)
{
return
c
.
queryOrSetEnableDbConfig
(
C
.
SQLITE_DBCONFIG_ENABLE_TRIGGER
,
btocint
(
b
))
}
// AreTriggersEnabled checks if triggers are enabled.
// Calls sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_TRIGGER, -1)
//
// (See http://sqlite.org/c3ref/c_dbconfig_enable_fkey.html)
func
(
c
*
Conn
)
AreTriggersEnabled
()
(
bool
,
error
)
{
return
c
.
queryOrSetEnableDbConfig
(
C
.
SQLITE_DBCONFIG_ENABLE_TRIGGER
,
-
1
)
}
func
(
c
*
Conn
)
queryOrSetEnableDbConfig
(
key
,
i
C
.
int
)
(
bool
,
error
)
{
var
ok
C
.
int
rv
:=
C
.
my_db_config
(
c
.
db
,
key
,
i
,
&
ok
)
if
rv
==
C
.
SQLITE_OK
{
return
(
ok
==
1
),
nil
}
return
false
,
c
.
error
(
rv
)
}
// EnableExtendedResultCodes enables or disables the extended result codes feature of SQLite.
// (See http://sqlite.org/c3ref/extended_result_codes.html)
func
(
c
*
Conn
)
EnableExtendedResultCodes
(
b
bool
)
error
{
return
c
.
error
(
C
.
sqlite3_extended_result_codes
(
c
.
db
,
btocint
(
b
)),
"Conn.EnableExtendedResultCodes"
)
}
// EnableLoadExtension enables or disables extension loading.
// (See http://sqlite.org/c3ref/enable_load_extension.html)
func
(
c
*
Conn
)
EnableLoadExtension
(
b
bool
)
error
{
rv
:=
C
.
sqlite3_enable_load_extension
(
c
.
db
,
btocint
(
b
))
if
rv
==
C
.
SQLITE_OK
{
return
nil
}
return
c
.
error
(
rv
,
"Conn.EnableLoadExtension"
)
}
// LoadExtension loads an extension
// (See http://sqlite.org/c3ref/load_extension.html)
func
(
c
*
Conn
)
LoadExtension
(
file
string
,
proc
...
string
)
error
{
cfile
:=
C
.
CString
(
file
)
defer
C
.
free
(
unsafe
.
Pointer
(
cfile
))
var
cproc
*
C
.
char
if
len
(
proc
)
>
0
{
cproc
=
C
.
CString
(
proc
[
0
])
defer
C
.
free
(
unsafe
.
Pointer
(
cproc
))
}
var
errMsg
*
C
.
char
rv
:=
C
.
sqlite3_load_extension
(
c
.
db
,
cfile
,
cproc
,
&
errMsg
)
if
rv
!=
C
.
SQLITE_OK
{
defer
C
.
sqlite3_free
(
unsafe
.
Pointer
(
errMsg
))
return
c
.
error
(
rv
,
C
.
GoString
(
errMsg
))
}
return
nil
}
config_test.go
0 → 100644
View file @
d8830656
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package
sqlite_test
import
(
"fmt"
"testing"
"github.com/bmizerany/assert"
.
"github.com/gwenn/gosqlite"
)
func
init
()
{
err
:=
ConfigThreadingMode
(
Serialized
)
if
err
!=
nil
{
panic
(
fmt
.
Sprintf
(
"cannot change threading mode: '%s'"
,
err
))
}
err
=
ConfigMemStatus
(
true
)
if
err
!=
nil
{
panic
(
fmt
.
Sprintf
(
"cannot activate mem status: '%s'"
,
err
))
}
err
=
ConfigUri
(
true
)
if
err
!=
nil
{
panic
(
fmt
.
Sprintf
(
"cannot activate uri handling: '%s'"
,
err
))
}
err
=
EnableSharedCache
(
false
)
if
err
!=
nil
{
panic
(
fmt
.
Sprintf
(
"couldn't disable shared cache: '%s'"
,
err
))
}
}
func
TestEnableFKey
(
t
*
testing
.
T
)
{
db
:=
open
(
t
)
defer
checkClose
(
db
,
t
)
b
:=
Must
(
db
.
IsFKeyEnabled
())
if
!
b
{
b
=
Must
(
db
.
EnableFKey
(
true
))
assert
.
T
(
t
,
b
,
"cannot enable FK"
)
}
}
func
TestEnableTriggers
(
t
*
testing
.
T
)
{
db
:=
open
(
t
)
defer
checkClose
(
db
,
t
)
b
:=
Must
(
db
.
AreTriggersEnabled
())
if
!
b
{
b
=
Must
(
db
.
EnableTriggers
(
true
))
assert
.
T
(
t
,
b
,
"cannot enable triggers"
)
}
}
func
TestEnableExtendedResultCodes
(
t
*
testing
.
T
)
{
db
:=
open
(
t
)
defer
checkClose
(
db
,
t
)
checkNoError
(
t
,
db
.
EnableExtendedResultCodes
(
true
),
"cannot enable extended result codes: %s"
)
}
func
TestConnSettings
(
t
*
testing
.
T
)
{
db
:=
open
(
t
)
defer
checkClose
(
db
,
t
)
err
:=
db
.
EnableLoadExtension
(
false
)
checkNoError
(
t
,
err
,
"EnableLoadExtension error: %s"
)
err
=
db
.
SetRecursiveTriggers
(
"main"
,
true
)
checkNoError
(
t
,
err
,
"SetRecursiveTriggers error: %s"
)
}
sqlite.go
View file @
d8830656
...
...
@@ -10,11 +10,6 @@ package sqlite
#include <sqlite3.h>
#include <stdlib.h>
// cgo doesn't support varargs
static inline int my_db_config(sqlite3 *db, int op, int v, int *ok) {
return sqlite3_db_config(db, op, v, ok);
}
*/
import
"C"
...
...
@@ -257,55 +252,6 @@ func (c *Conn) BusyTimeout(d time.Duration) error {
return
c
.
error
(
C
.
sqlite3_busy_timeout
(
c
.
db
,
C
.
int
(
d
/
time
.
Millisecond
)),
"Conn.BusyTimeout"
)
}
// EnableFKey enables or disables the enforcement of foreign key constraints.
// Calls sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_FKEY, b).
// Another way is PRAGMA foreign_keys = boolean;
//
// (See http://sqlite.org/c3ref/c_dbconfig_enable_fkey.html)
func
(
c
*
Conn
)
EnableFKey
(
b
bool
)
(
bool
,
error
)
{
return
c
.
queryOrSetEnableDbConfig
(
C
.
SQLITE_DBCONFIG_ENABLE_FKEY
,
btocint
(
b
))
}
// IsFKeyEnabled reports if the enforcement of foreign key constraints is enabled or not.
// Calls sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_FKEY, -1).
// Another way is PRAGMA foreign_keys;
//
// (See http://sqlite.org/c3ref/c_dbconfig_enable_fkey.html)
func
(
c
*
Conn
)
IsFKeyEnabled
()
(
bool
,
error
)
{
return
c
.
queryOrSetEnableDbConfig
(
C
.
SQLITE_DBCONFIG_ENABLE_FKEY
,
-
1
)
}
// EnableTriggers enables or disables triggers.
// Calls sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_TRIGGER, b).
//
// (See http://sqlite.org/c3ref/c_dbconfig_enable_fkey.html)
func
(
c
*
Conn
)
EnableTriggers
(
b
bool
)
(
bool
,
error
)
{
return
c
.
queryOrSetEnableDbConfig
(
C
.
SQLITE_DBCONFIG_ENABLE_TRIGGER
,
btocint
(
b
))
}
// AreTriggersEnabled checks if triggers are enabled.
// Calls sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_TRIGGER, -1)
//
// (See http://sqlite.org/c3ref/c_dbconfig_enable_fkey.html)
func
(
c
*
Conn
)
AreTriggersEnabled
()
(
bool
,
error
)
{
return
c
.
queryOrSetEnableDbConfig
(
C
.
SQLITE_DBCONFIG_ENABLE_TRIGGER
,
-
1
)
}
func
(
c
*
Conn
)
queryOrSetEnableDbConfig
(
key
,
i
C
.
int
)
(
bool
,
error
)
{
var
ok
C
.
int
rv
:=
C
.
my_db_config
(
c
.
db
,
key
,
i
,
&
ok
)
if
rv
==
C
.
SQLITE_OK
{
return
(
ok
==
1
),
nil
}
return
false
,
c
.
error
(
rv
)
}
// EnableExtendedResultCodes enables or disables the extended result codes feature of SQLite.
// (See http://sqlite.org/c3ref/extended_result_codes.html)
func
(
c
*
Conn
)
EnableExtendedResultCodes
(
b
bool
)
error
{
return
c
.
error
(
C
.
sqlite3_extended_result_codes
(
c
.
db
,
btocint
(
b
)),
"Conn.EnableExtendedResultCodes"
)
}
// Readonly determines if a database is read-only.
// (See http://sqlite.org/c3ref/db_readonly.html)
func
(
c
*
Conn
)
Readonly
(
dbName
string
)
(
bool
,
error
)
{
...
...
@@ -592,42 +538,3 @@ func (c *Conn) Close() error {
func
(
c
*
Conn
)
IsClosed
()
bool
{
return
c
==
nil
||
c
.
db
==
nil
}
// EnableLoadExtension enables or disables extension loading.
// (See http://sqlite.org/c3ref/enable_load_extension.html)
func
(
c
*
Conn
)
EnableLoadExtension
(
b
bool
)
error
{
rv
:=
C
.
sqlite3_enable_load_extension
(
c
.
db
,
btocint
(
b
))
if
rv
==
C
.
SQLITE_OK
{
return
nil
}
return
c
.
error
(
rv
,
"Conn.EnableLoadExtension"
)
}
// LoadExtension loads an extension
// (See http://sqlite.org/c3ref/load_extension.html)
func
(
c
*
Conn
)
LoadExtension
(
file
string
,
proc
...
string
)
error
{
cfile
:=
C
.
CString
(
file
)
defer
C
.
free
(
unsafe
.
Pointer
(
cfile
))
var
cproc
*
C
.
char
if
len
(
proc
)
>
0
{
cproc
=
C
.
CString
(
proc
[
0
])
defer
C
.
free
(
unsafe
.
Pointer
(
cproc
))
}
var
errMsg
*
C
.
char
rv
:=
C
.
sqlite3_load_extension
(
c
.
db
,
cfile
,
cproc
,
&
errMsg
)
if
rv
!=
C
.
SQLITE_OK
{
defer
C
.
sqlite3_free
(
unsafe
.
Pointer
(
errMsg
))
return
c
.
error
(
rv
,
C
.
GoString
(
errMsg
))
}
return
nil
}
// EnableSharedCache enables or disables shared pager cache
// (See http://sqlite.org/c3ref/enable_shared_cache.html)
func
EnableSharedCache
(
b
bool
)
error
{
rv
:=
C
.
sqlite3_enable_shared_cache
(
btocint
(
b
))
if
rv
==
C
.
SQLITE_OK
{
return
nil
}
return
Errno
(
rv
)
}
sqlite_test.go
View file @
d8830656
...
...
@@ -75,32 +75,6 @@ func TestOpenFailure(t *testing.T) {
//println(err.Error())
}
func
TestEnableFKey
(
t
*
testing
.
T
)
{
db
:=
open
(
t
)
defer
checkClose
(
db
,
t
)
b
:=
Must
(
db
.
IsFKeyEnabled
())
if
!
b
{
b
=
Must
(
db
.
EnableFKey
(
true
))
assert
.
T
(
t
,
b
,
"cannot enable FK"
)
}
}
func
TestEnableTriggers
(
t
*
testing
.
T
)
{
db
:=
open
(
t
)
defer
checkClose
(
db
,
t
)
b
:=
Must
(
db
.
AreTriggersEnabled
())
if
!
b
{
b
=
Must
(
db
.
EnableTriggers
(
true
))
assert
.
T
(
t
,
b
,
"cannot enable triggers"
)
}
}
func
TestEnableExtendedResultCodes
(
t
*
testing
.
T
)
{
db
:=
open
(
t
)
defer
checkClose
(
db
,
t
)
checkNoError
(
t
,
db
.
EnableExtendedResultCodes
(
true
),
"cannot enable extended result codes: %s"
)
}
func
TestCreateTable
(
t
*
testing
.
T
)
{
db
:=
open
(
t
)
defer
checkClose
(
db
,
t
)
...
...
@@ -249,15 +223,6 @@ func TestReadonlyMisuse(t *testing.T) {
//println(err.Error())
}
func
TestConnSettings
(
t
*
testing
.
T
)
{
db
:=
open
(
t
)
defer
checkClose
(
db
,
t
)
err
:=
db
.
EnableLoadExtension
(
false
)
checkNoError
(
t
,
err
,
"EnableLoadExtension error: %s"
)
err
=
db
.
SetRecursiveTriggers
(
"main"
,
true
)
checkNoError
(
t
,
err
,
"SetRecursiveTriggers error: %s"
)
}
func
TestComplete
(
t
*
testing
.
T
)
{
assert
.
T
(
t
,
Complete
(
"SELECT 1;"
),
"expected complete statement"
)
}
...
...
trace.c
View file @
d8830656
...
...
@@ -45,11 +45,3 @@ int goSqlite3ConfigLog(void *udp) {
return
sqlite3_config
(
SQLITE_CONFIG_LOG
,
NULL
,
NULL
);
}
}
int
goSqlite3ConfigThreadMode
(
int
mode
)
{
return
sqlite3_config
(
mode
);
}
int
goSqlite3Config
(
int
op
,
int
mode
)
{
return
sqlite3_config
(
op
,
mode
);
}
\ No newline at end of file
trace.go
View file @
d8830656
...
...
@@ -20,8 +20,6 @@ static inline void my_log(int iErrCode, char *msg) {
}
int goSqlite3ConfigLog(void *udp);
int goSqlite3ConfigThreadMode(int mode);
int goSqlite3Config(int op, int mode);
*/
import
"C"
...
...
@@ -393,46 +391,6 @@ func ConfigLog(f Logger, udp interface{}) error {
return
Errno
(
rv
)
}
// ThreadingMode enumerates SQLite threading mode
// See ConfigThreadingMode
type
ThreadingMode
int32
const
(
SingleThread
ThreadingMode
=
C
.
SQLITE_CONFIG_SINGLETHREAD
MultiThread
ThreadingMode
=
C
.
SQLITE_CONFIG_MULTITHREAD
Serialized
ThreadingMode
=
C
.
SQLITE_CONFIG_SERIALIZED
)
// ConfigThreadingMode alters threading mode.
// (See sqlite3_config(SQLITE_CONFIG_SINGLETHREAD|SQLITE_CONFIG_MULTITHREAD|SQLITE_CONFIG_SERIALIZED): http://sqlite.org/c3ref/config.html)
func
ConfigThreadingMode
(
mode
ThreadingMode
)
error
{
rv
:=
C
.
goSqlite3ConfigThreadMode
(
C
.
int
(
mode
))
if
rv
==
C
.
SQLITE_OK
{
return
nil
}
return
Errno
(
rv
)
}
// ConfigMemStatus enables or disables the collection of memory allocation statistics.
// (See sqlite3_config(SQLITE_CONFIG_MEMSTATUS): http://sqlite.org/c3ref/config.html)
func
ConfigMemStatus
(
b
bool
)
error
{
rv
:=
C
.
goSqlite3Config
(
C
.
SQLITE_CONFIG_MEMSTATUS
,
btocint
(
b
))
if
rv
==
C
.
SQLITE_OK
{
return
nil
}
return
Errno
(
rv
)
}
// ConfigUri enables or disables URI handling.
// (See sqlite3_config(SQLITE_CONFIG_URI): http://sqlite.org/c3ref/config.html)
func
ConfigUri
(
b
bool
)
error
{
rv
:=
C
.
goSqlite3Config
(
C
.
SQLITE_CONFIG_URI
,
btocint
(
b
))
if
rv
==
C
.
SQLITE_OK
{
return
nil
}
return
Errno
(
rv
)
}
func
(
s
*
Stmt
)
ExplainQueryPlan
(
w
io
.
Writer
)
error
{
sql
:=
s
.
SQL
()
if
len
(
sql
)
==
0
{
...
...
trace_test.go
View file @
d8830656
...
...
@@ -15,33 +15,17 @@ import (
)
func
init
()
{
err
:=
ConfigThreadingMode
(
Serialized
)
if
err
!=
nil
{
panic
(
fmt
.
Sprintf
(
"cannot change threading mode: '%s'"
,
err
))
}
err
=
ConfigMemStatus
(
true
)
if
err
!=
nil
{
panic
(
fmt
.
Sprintf
(
"cannot activate mem status: '%s'"
,
err
))
}
err
=
ConfigUri
(
true
)
if
err
!=
nil
{
panic
(
fmt
.
Sprintf
(
"cannot activate uri handling: '%s'"
,
err
))
}
if
os
.
Getenv
(
"SQLITE_LOG"
)
==
""
{
err
=
ConfigLog
(
func
(
d
interface
{},
err
error
,
msg
string
)
{
err
:
=
ConfigLog
(
func
(
d
interface
{},
err
error
,
msg
string
)
{
fmt
.
Printf
(
"%s: %s, %s
\n
"
,
d
,
err
,
msg
)
},
"SQLITE"
)
if
err
!=
nil
{
panic
(
fmt
.
Sprintf
(
"couldn't config log: '%s'"
,
err
))
}
err
=
ConfigLog
(
nil
,
""
)
}
if
err
!=
nil
{
panic
(
fmt
.
Sprintf
(
"couldn't unset logger: '%s'"
,
err
))
}
err
=
EnableSharedCache
(
false
)
if
err
!=
nil
{
panic
(
fmt
.
Sprintf
(
"couldn't disable shared cache: '%s'"
,
err
))
if
err
!=
nil
{
panic
(
fmt
.
Sprintf
(
"couldn't unset logger: '%s'"
,
err
))
}
}
}
...
...
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