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
1d4f09bb
Commit
1d4f09bb
authored
Aug 11, 2012
by
gwenn
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Make possible to specify the database name in PRAGMA methods.
parent
6f574615
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
100 additions
and
46 deletions
+100
-46
backup.go
backup.go
+3
-3
blob.go
blob.go
+7
-7
busy_test.go
busy_test.go
+3
-3
cache.go
cache.go
+2
-1
date.go
date.go
+4
-0
driver.go
driver.go
+1
-0
example_test.go
example_test.go
+1
-1
meta.go
meta.go
+54
-14
meta_test.go
meta_test.go
+6
-6
pragma.go
pragma.go
+15
-7
pragma_test.go
pragma_test.go
+3
-3
stmt.go
stmt.go
+1
-1
No files found.
backup.go
View file @
1d4f09bb
...
...
@@ -42,7 +42,7 @@ type Backup struct {
dst
,
src
*
Conn
}
//
Copy
up to N pages between the source and destination databases
//
Step copies
up to N pages between the source and destination databases
// (See http://sqlite.org/c3ref/backup_finish.html#sqlite3backupstep)
func
(
b
*
Backup
)
Step
(
npage
int
)
error
{
if
b
==
nil
{
...
...
@@ -61,7 +61,7 @@ type BackupStatus struct {
PageCount
int
}
//
Return
the number of pages still to be backed up and the total number of pages in the source database file.
//
Status returns
the number of pages still to be backed up and the total number of pages in the source database file.
// (See http://sqlite.org/c3ref/backup_finish.html#sqlite3backupremaining)
func
(
b
*
Backup
)
Status
()
BackupStatus
{
return
BackupStatus
{
int
(
C
.
sqlite3_backup_remaining
(
b
.
sb
)),
int
(
C
.
sqlite3_backup_pagecount
(
b
.
sb
))}
...
...
@@ -96,7 +96,7 @@ func (b *Backup) Run(npage int, sleepNs time.Duration, c chan<- BackupStatus) er
return
nil
}
//
Finish/stop
the backup
//
Close finishes/stops
the backup
// (See http://sqlite.org/c3ref/backup_finish.html#sqlite3backupfinish)
func
(
b
*
Backup
)
Close
()
error
{
if
b
==
nil
{
...
...
blob.go
View file @
1d4f09bb
...
...
@@ -31,7 +31,7 @@ type BlobReadWriter struct {
// Zeroblobs are used to reserve space for a BLOB that is later written.
type
ZeroBlobLength
int
//
Open
a BLOB for incremental I/O
//
NewBlobReader opens
a BLOB for incremental I/O
//
// (See http://sqlite.org/c3ref/blob_open.html)
func
(
c
*
Conn
)
NewBlobReader
(
db
,
table
,
column
string
,
row
int64
)
(
*
BlobReader
,
error
)
{
...
...
@@ -42,7 +42,7 @@ func (c *Conn) NewBlobReader(db, table, column string, row int64) (*BlobReader,
return
&
BlobReader
{
c
,
bl
,
0
},
nil
}
//
Open a BLOB F
or incremental I/O
//
NewBlobReadWriter open a BLOB f
or incremental I/O
// (See http://sqlite.org/c3ref/blob_open.html)
func
(
c
*
Conn
)
NewBlobReadWriter
(
db
,
table
,
column
string
,
row
int64
)
(
*
BlobReadWriter
,
error
)
{
bl
,
err
:=
c
.
blob_open
(
db
,
table
,
column
,
row
,
true
)
...
...
@@ -73,7 +73,7 @@ func (c *Conn) blob_open(db, table, column string, row int64, write bool) (*C.sq
return
bl
,
nil
}
// Close a BLOB handle
// Close
closes
a BLOB handle
// (See http://sqlite.org/c3ref/blob_close.html)
func
(
r
*
BlobReader
)
Close
()
error
{
if
r
==
nil
{
...
...
@@ -87,7 +87,7 @@ func (r *BlobReader) Close() error {
return
nil
}
// Read data from a BLOB incrementally
// Read
reads
data from a BLOB incrementally
// (See http://sqlite.org/c3ref/blob_read.html)
func
(
r
*
BlobReader
)
Read
(
v
[]
byte
)
(
int
,
error
)
{
var
p
*
byte
...
...
@@ -102,14 +102,14 @@ func (r *BlobReader) Read(v []byte) (int, error) {
return
len
(
v
),
nil
}
//
Return the size of an open
BLOB
//
Size returns the size of an opened
BLOB
// (See http://sqlite.org/c3ref/blob_bytes.html)
func
(
r
*
BlobReader
)
Size
()
(
int
,
error
)
{
s
:=
C
.
sqlite3_blob_bytes
(
r
.
bl
)
return
int
(
s
),
nil
}
// Write data into a BLOB incrementally
// Write
writes
data into a BLOB incrementally
// (See http://sqlite.org/c3ref/blob_write.html)
func
(
w
*
BlobReadWriter
)
Write
(
v
[]
byte
)
(
int
,
error
)
{
var
p
*
byte
...
...
@@ -124,7 +124,7 @@ func (w *BlobReadWriter) Write(v []byte) (int, error) {
return
len
(
v
),
nil
}
//
Move
a BLOB handle to a new row
//
Reopen moves
a BLOB handle to a new row
// (See http://sqlite.org/c3ref/blob_reopen.html)
func
(
r
*
BlobReader
)
Reopen
(
rowid
int64
)
error
{
rv
:=
C
.
sqlite3_blob_reopen
(
r
.
bl
,
C
.
sqlite3_int64
(
rowid
))
...
...
busy_test.go
View file @
1d4f09bb
...
...
@@ -51,7 +51,7 @@ func TestDefaultBusy(t *testing.T) {
checkNoError
(
t
,
db1
.
BeginTransaction
(
EXCLUSIVE
),
"couldn't begin transaction: %s"
)
defer
db1
.
Rollback
()
_
,
err
:=
db2
.
SchemaVersion
()
_
,
err
:=
db2
.
SchemaVersion
(
""
)
if
err
==
nil
{
t
.
Fatalf
(
"Expected lock but got %v"
,
err
)
}
...
...
@@ -75,7 +75,7 @@ func TestBusyTimeout(t *testing.T) {
//join <- true
}()
_
,
err
:=
db2
.
SchemaVersion
()
_
,
err
:=
db2
.
SchemaVersion
(
""
)
checkNoError
(
t
,
err
,
"couldn't query schema version: %#v"
)
//<- join
}
...
...
@@ -104,7 +104,7 @@ func TestBusyHandler(t *testing.T) {
db1
.
Rollback
()
}()
_
,
err
=
db2
.
SchemaVersion
()
_
,
err
=
db2
.
SchemaVersion
(
""
)
checkNoError
(
t
,
err
,
"couldn't query schema version: %#v"
)
assert
(
t
,
"busy handler not called!"
,
called
)
}
cache.go
View file @
1d4f09bb
...
...
@@ -86,7 +86,7 @@ func (c *cache) flush() {
}
}
//
Return
(current, max) sizes.
//
CacheSize returns
(current, max) sizes.
// Cache is turned off when max size is 0
func
(
c
*
Conn
)
CacheSize
()
(
int
,
int
)
{
if
c
.
stmtCache
.
maxSize
<=
0
{
...
...
@@ -95,6 +95,7 @@ func (c *Conn) CacheSize() (int, int) {
return
c
.
stmtCache
.
l
.
Len
(),
c
.
stmtCache
.
maxSize
}
// SetCacheSize sets the size of prepared statements cache.
// Cache is turned off (and flushed) when size <= 0
func
(
c
*
Conn
)
SetCacheSize
(
size
int
)
{
stmtCache
:=
c
.
stmtCache
...
...
date.go
View file @
1d4f09bb
...
...
@@ -13,17 +13,21 @@ const (
DAY_IN_SECONDS
=
60
*
60
*
24
)
// JulianDayToUTC transforms a julian day number into an UTC Time.
func
JulianDayToUTC
(
jd
float64
)
time
.
Time
{
jd
-=
JULIAN_DAY
jd
*=
DAY_IN_SECONDS
return
time
.
Unix
(
int64
(
jd
),
0
)
.
UTC
()
}
// JulianDayToLocalTime transforms a julian day number into a local Time.
func
JulianDayToLocalTime
(
jd
float64
)
time
.
Time
{
jd
-=
JULIAN_DAY
jd
*=
DAY_IN_SECONDS
return
time
.
Unix
(
int64
(
jd
),
0
)
}
// JulianDay converts a Time into a julian day number.
func
JulianDay
(
t
time
.
Time
)
float64
{
ns
:=
float64
(
t
.
Unix
())
if
ns
>=
0
{
...
...
driver.go
View file @
1d4f09bb
...
...
@@ -21,6 +21,7 @@ func init() {
}
}
// Adapter to database/sql/driver
type
Driver
struct
{
}
type
connImpl
struct
{
...
...
example_test.go
View file @
1d4f09bb
...
...
@@ -55,7 +55,7 @@ func ExampleConn_Exec() {
err
=
db
.
Exec
(
"CREATE TABLE test1 (content TEXT); CREATE TABLE test2 (content TEXT); INSERT INTO test1 VALUES ('DATA')"
)
check
(
err
)
tables
,
err
:=
db
.
Tables
()
tables
,
err
:=
db
.
Tables
(
""
)
check
(
err
)
fmt
.
Printf
(
"%d tables
\n
"
,
len
(
tables
))
// Output: 2 tables
...
...
meta.go
View file @
1d4f09bb
...
...
@@ -12,6 +12,9 @@ package sqlite
static char *my_mprintf(char *zFormat, char *arg) {
return sqlite3_mprintf(zFormat, arg);
}
static char *my_mprintf2(char *zFormat, char *arg1, char *arg2) {
return sqlite3_mprintf(zFormat, arg1, arg2);
}
// just to get ride of warning
static int my_table_column_metadata(
...
...
@@ -57,9 +60,14 @@ func (c *Conn) Databases() (map[string]string, error) {
}
// Selects tables (no view) from 'sqlite_master' and filters system tables out.
// TODO Make possible to specified the database name (main.sqlite_master)
func
(
c
*
Conn
)
Tables
()
([]
string
,
error
)
{
s
,
err
:=
c
.
prepare
(
"SELECT name FROM sqlite_master WHERE type IN ('table') AND name NOT LIKE 'sqlite_%'"
)
func
(
c
*
Conn
)
Tables
(
dbName
string
)
([]
string
,
error
)
{
var
sql
string
if
len
(
dbName
)
==
0
{
sql
=
"SELECT name FROM sqlite_master WHERE type IN ('table') AND name NOT LIKE 'sqlite_%'"
}
else
{
sql
=
Mprintf
(
"SELECT name FROM %Q.sqlite_master WHERE type IN ('table') AND name NOT LIKE 'sqlite_%%'"
,
dbName
)
}
s
,
err
:=
c
.
prepare
(
sql
)
if
err
!=
nil
{
return
nil
,
err
}
...
...
@@ -89,9 +97,14 @@ type Column struct {
}
// Executes pragma 'table_info'
// TODO Make possible to specify the database-name (PRAGMA %Q.table_info(%Q))
func
(
c
*
Conn
)
Columns
(
table
string
)
([]
Column
,
error
)
{
s
,
err
:=
c
.
prepare
(
Mprintf
(
"PRAGMA table_info(%Q)"
,
table
))
func
(
c
*
Conn
)
Columns
(
dbName
,
table
string
)
([]
Column
,
error
)
{
var
pragma
string
if
len
(
dbName
)
==
0
{
pragma
=
Mprintf
(
"PRAGMA table_info(%Q)"
,
table
)
}
else
{
pragma
=
Mprintf2
(
"PRAGMA %Q.table_info(%Q)"
,
dbName
,
table
)
}
s
,
err
:=
c
.
prepare
(
pragma
)
if
err
!=
nil
{
return
nil
,
err
}
...
...
@@ -142,9 +155,14 @@ type ForeignKey struct {
}
// Executes pragma 'foreign_key_list'
// TODO Make possible to specify the database-name (PRAGMA %Q.foreign_key_list(%Q))
func
(
c
*
Conn
)
ForeignKeys
(
table
string
)
(
map
[
int
]
*
ForeignKey
,
error
)
{
s
,
err
:=
c
.
prepare
(
Mprintf
(
"PRAGMA foreign_key_list(%Q)"
,
table
))
func
(
c
*
Conn
)
ForeignKeys
(
dbName
,
table
string
)
(
map
[
int
]
*
ForeignKey
,
error
)
{
var
pragma
string
if
len
(
dbName
)
==
0
{
pragma
=
Mprintf
(
"PRAGMA foreign_key_list(%Q)"
,
table
)
}
else
{
pragma
=
Mprintf2
(
"PRAGMA %Q.foreign_key_list(%Q)"
,
dbName
,
table
)
}
s
,
err
:=
c
.
prepare
(
pragma
)
if
err
!=
nil
{
return
nil
,
err
}
...
...
@@ -179,9 +197,14 @@ type Index struct {
}
// Executes pragma 'index_list'
// TODO Make possible to specify the database-name (PRAGMA %Q.index_list(%Q))
func
(
c
*
Conn
)
Indexes
(
table
string
)
([]
Index
,
error
)
{
s
,
err
:=
c
.
prepare
(
Mprintf
(
"PRAGMA index_list(%Q)"
,
table
))
func
(
c
*
Conn
)
Indexes
(
dbName
,
table
string
)
([]
Index
,
error
)
{
var
pragma
string
if
len
(
dbName
)
==
0
{
pragma
=
Mprintf
(
"PRAGMA index_list(%Q)"
,
table
)
}
else
{
pragma
=
Mprintf2
(
"PRAGMA %Q.index_list(%Q)"
,
dbName
,
table
)
}
s
,
err
:=
c
.
prepare
(
pragma
)
if
err
!=
nil
{
return
nil
,
err
}
...
...
@@ -203,8 +226,14 @@ func (c *Conn) Indexes(table string) ([]Index, error) {
// Executes pragma 'index_info'
// Only Column.Cid and Column.Name are specified. All other fields are unspecifed.
func
(
c
*
Conn
)
IndexColumns
(
index
string
)
([]
Column
,
error
)
{
s
,
err
:=
c
.
prepare
(
Mprintf
(
"PRAGMA index_info(%Q)"
,
index
))
func
(
c
*
Conn
)
IndexColumns
(
dbName
,
index
string
)
([]
Column
,
error
)
{
var
pragma
string
if
len
(
dbName
)
==
0
{
pragma
=
Mprintf
(
"PRAGMA index_info(%Q)"
,
index
)
}
else
{
pragma
=
Mprintf2
(
"PRAGMA %Q.index_info(%Q)"
,
dbName
,
index
)
}
s
,
err
:=
c
.
prepare
(
pragma
)
if
err
!=
nil
{
return
nil
,
err
}
...
...
@@ -234,3 +263,14 @@ func Mprintf(format string, arg string) string {
defer
C
.
sqlite3_free
(
unsafe
.
Pointer
(
zSQL
))
return
C
.
GoString
(
zSQL
)
}
func
Mprintf2
(
format
string
,
arg1
,
arg2
string
)
string
{
cf
:=
C
.
CString
(
format
)
defer
C
.
free
(
unsafe
.
Pointer
(
cf
))
ca1
:=
C
.
CString
(
arg1
)
defer
C
.
free
(
unsafe
.
Pointer
(
ca1
))
ca2
:=
C
.
CString
(
arg2
)
defer
C
.
free
(
unsafe
.
Pointer
(
ca2
))
zSQL
:=
C
.
my_mprintf2
(
cf
,
ca1
,
ca2
)
defer
C
.
sqlite3_free
(
unsafe
.
Pointer
(
zSQL
))
return
C
.
GoString
(
zSQL
)
}
meta_test.go
View file @
1d4f09bb
...
...
@@ -33,11 +33,11 @@ func TestTables(t *testing.T) {
db
:=
open
(
t
)
defer
db
.
Close
()
tables
,
err
:=
db
.
Tables
()
tables
,
err
:=
db
.
Tables
(
""
)
checkNoError
(
t
,
err
,
"error looking for tables: %s"
)
assertEquals
(
t
,
"expected %d table but got %d"
,
0
,
len
(
tables
))
createTable
(
db
,
t
)
tables
,
err
=
db
.
Tables
()
tables
,
err
=
db
.
Tables
(
"main"
)
checkNoError
(
t
,
err
,
"error looking for tables: %s"
)
assertEquals
(
t
,
"expected %d table but got %d"
,
1
,
len
(
tables
))
assertEquals
(
t
,
"wrong table name: %q <> %q"
,
"test"
,
tables
[
0
])
...
...
@@ -48,7 +48,7 @@ func TestColumns(t *testing.T) {
defer
db
.
Close
()
createTable
(
db
,
t
)
columns
,
err
:=
db
.
Columns
(
"test"
)
columns
,
err
:=
db
.
Columns
(
"
"
,
"
test"
)
checkNoError
(
t
,
err
,
"error listing columns: %s"
)
if
len
(
columns
)
!=
4
{
t
.
Fatalf
(
"Expected 4 columns <> %d"
,
len
(
columns
))
...
...
@@ -77,7 +77,7 @@ func TestForeignKeys(t *testing.T) {
"CREATE TABLE child (id INTEGER PRIMARY KEY NOT NULL, parentId INTEGER, "
+
"FOREIGN KEY (parentId) REFERENCES parent(id));"
)
checkNoError
(
t
,
err
,
"error creating tables: %s"
)
fks
,
err
:=
db
.
ForeignKeys
(
"child"
)
fks
,
err
:=
db
.
ForeignKeys
(
"
"
,
"
child"
)
checkNoError
(
t
,
err
,
"error listing FKs: %s"
)
if
len
(
fks
)
!=
1
{
t
.
Fatalf
(
"expected 1 FK <> %d"
,
len
(
fks
))
...
...
@@ -94,7 +94,7 @@ func TestIndexes(t *testing.T) {
createTable
(
db
,
t
)
createIndex
(
db
,
t
)
indexes
,
err
:=
db
.
Indexes
(
"test"
)
indexes
,
err
:=
db
.
Indexes
(
"
"
,
"
test"
)
checkNoError
(
t
,
err
,
"error listing indexes: %s"
)
if
len
(
indexes
)
!=
1
{
t
.
Fatalf
(
"Expected one index <> %d"
,
len
(
indexes
))
...
...
@@ -103,7 +103,7 @@ func TestIndexes(t *testing.T) {
assertEquals
(
t
,
"wrong index name: %q <> %q"
,
"test_index"
,
index
.
Name
)
assert
(
t
,
"index 'test_index' is not unique"
,
!
index
.
Unique
)
columns
,
err
:=
db
.
IndexColumns
(
"test_index"
)
columns
,
err
:=
db
.
IndexColumns
(
"
"
,
"
test_index"
)
checkNoError
(
t
,
err
,
"error listing index columns: %s"
)
if
len
(
columns
)
!=
1
{
t
.
Fatalf
(
"expected one column <> %d"
,
len
(
columns
))
...
...
pragma.go
View file @
1d4f09bb
...
...
@@ -9,10 +9,11 @@ import (
)
// Check database integrity
// Database name is optional
// (See http://www.sqlite.org/pragma.html#pragma_integrity_check
// and http://www.sqlite.org/pragma.html#pragma_quick_check)
// TODO Make possible to specify the database-name (PRAGMA %Q.integrity_check(.))
func
(
c
*
Conn
)
IntegrityCheck
(
max
int
,
quick
bool
)
error
{
func
(
c
*
Conn
)
IntegrityCheck
(
dbName
string
,
max
int
,
quick
bool
)
error
{
var
pragma
string
if
quick
{
pragma
=
"quick"
...
...
@@ -30,23 +31,23 @@ func (c *Conn) IntegrityCheck(max int, quick bool) error {
return
nil
}
// Database name is optional
// Returns the text encoding used by the main database
// (See http://sqlite.org/pragma.html#pragma_encoding)
// TODO Make possible to specify the database-name (PRAGMA %Q.encoding)
func
(
c
*
Conn
)
Encoding
()
(
string
,
error
)
{
func
(
c
*
Conn
)
Encoding
(
dbName
string
)
(
string
,
error
)
{
var
encoding
string
err
:=
c
.
OneValue
(
"PRAGMA encoding"
,
&
encoding
)
err
:=
c
.
OneValue
(
pragma
(
dbName
,
"PRAGMA encoding"
,
"PRAGMA %Q.encoding"
)
,
&
encoding
)
if
err
!=
nil
{
return
""
,
err
}
return
encoding
,
nil
}
// Database name is optional
// (See http://sqlite.org/pragma.html#pragma_schema_version)
// TODO Make possible to specify the database-name (PRAGMA %Q.schema_version)
func
(
c
*
Conn
)
SchemaVersion
()
(
int
,
error
)
{
func
(
c
*
Conn
)
SchemaVersion
(
dbName
string
)
(
int
,
error
)
{
var
version
int
err
:=
c
.
OneValue
(
"PRAGMA schema_version"
,
&
version
)
err
:=
c
.
OneValue
(
pragma
(
dbName
,
"PRAGMA schema_version"
,
"PRAGMA %Q.schema_version"
)
,
&
version
)
if
err
!=
nil
{
return
-
1
,
err
}
...
...
@@ -58,3 +59,10 @@ func (c *Conn) SchemaVersion() (int, error) {
func
(
c
*
Conn
)
SetRecursiveTriggers
(
on
bool
)
error
{
return
c
.
exec
(
fmt
.
Sprintf
(
"PRAGMA recursive_triggers=%t"
,
on
))
}
func
pragma
(
dbName
,
unqualified
,
qualified
string
)
string
{
if
len
(
dbName
)
==
0
{
return
unqualified
}
return
Mprintf
(
qualified
,
dbName
)
}
pragma_test.go
View file @
1d4f09bb
...
...
@@ -11,13 +11,13 @@ import (
func
TestIntegrityCheck
(
t
*
testing
.
T
)
{
db
:=
open
(
t
)
defer
db
.
Close
()
checkNoError
(
t
,
db
.
IntegrityCheck
(
1
,
true
),
"Error checking integrity of database: %s"
)
checkNoError
(
t
,
db
.
IntegrityCheck
(
""
,
1
,
true
),
"Error checking integrity of database: %s"
)
}
func
TestEncoding
(
t
*
testing
.
T
)
{
db
:=
open
(
t
)
defer
db
.
Close
()
encoding
,
err
:=
db
.
Encoding
()
encoding
,
err
:=
db
.
Encoding
(
""
)
checkNoError
(
t
,
err
,
"Error reading encoding of database: %s"
)
assertEquals
(
t
,
"Expecting %s but got %s"
,
"UTF-8"
,
encoding
)
}
...
...
@@ -25,7 +25,7 @@ func TestEncoding(t *testing.T) {
func
TestSchemaVersion
(
t
*
testing
.
T
)
{
db
:=
open
(
t
)
defer
db
.
Close
()
version
,
err
:=
db
.
SchemaVersion
()
version
,
err
:=
db
.
SchemaVersion
(
""
)
checkNoError
(
t
,
err
,
"Error reading schema version of database: %s"
)
assertEquals
(
t
,
"expecting %d but got %d"
,
0
,
version
)
}
stmt.go
View file @
1d4f09bb
...
...
@@ -311,7 +311,7 @@ func (s *Stmt) BindByIndex(index int, value interface{}) error {
cstr
:=
C
.
CString
(
value
)
rv
=
C
.
my_bind_text
(
s
.
stmt
,
i
,
cstr
,
C
.
int
(
len
(
value
)))
C
.
free
(
unsafe
.
Pointer
(
cstr
))
//rv = C.my_bind_text(s.stmt, i,
*((**C.char)(unsafe.Pointer(&value)
)), C.int(len(value)))
//rv = C.my_bind_text(s.stmt, i,
(*C.char)(unsafe.Pointer(&value
)), C.int(len(value)))
case
int
:
rv
=
C
.
sqlite3_bind_int
(
s
.
stmt
,
i
,
C
.
int
(
value
))
case
int64
:
...
...
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