Add a mode to output SQL statements instead of populating a database to the recover extension.
FossilOrigin-Name: 73058416e7da6581000898b7988a7010e2ce6632246f4c12b4398700c7744b83
This commit is contained in:
parent
a768b67dcc
commit
d5e3fe50f8
@ -52,8 +52,27 @@ proc do_recover_test {tn} {
|
||||
sqlite3 db2 test.db2
|
||||
uplevel [list do_test $tn.2 [list compare_dbs db db2] {}]
|
||||
db2 close
|
||||
|
||||
forcedelete test.db2
|
||||
forcedelete rstate.db
|
||||
|
||||
uplevel [list do_test $tn.3 {
|
||||
set ::sqlhook [list]
|
||||
set R [sqlite3_recover_init_sql db main my_sql_hook]
|
||||
$R config testdb rstate.db
|
||||
$R step
|
||||
$R finish
|
||||
} {}]
|
||||
|
||||
sqlite3 db2 test.db2
|
||||
execsql [join $::sqlhook ";"] db2
|
||||
uplevel [list do_test $tn.4 [list compare_dbs db db2] {}]
|
||||
db2 close
|
||||
}
|
||||
|
||||
proc my_sql_hook {sql} {
|
||||
lappend ::sqlhook $sql
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
@ -112,6 +131,7 @@ do_execsql_test 7.1 {
|
||||
SELECT * FROM t2
|
||||
} {10 11 ten}
|
||||
|
||||
breakpoint
|
||||
do_recover_test 7.2
|
||||
|
||||
finish_test
|
||||
|
@ -53,13 +53,37 @@ proc do_recover_test {tn {tsql {}} {res {}}} {
|
||||
sqlite3 db2 test.db2
|
||||
|
||||
if {$tsql==""} {
|
||||
uplevel [list do_test $tn [list compare_dbs db db2] {}]
|
||||
uplevel [list do_test $tn.1 [list compare_dbs db db2] {}]
|
||||
} else {
|
||||
uplevel [list do_execsql_test -db db2 $tn $tsql $res]
|
||||
uplevel [list do_execsql_test -db db2 $tn.1 $tsql $res]
|
||||
}
|
||||
db2 close
|
||||
|
||||
forcedelete test.db2
|
||||
forcedelete rstate.db
|
||||
|
||||
set ::sqlhook [list]
|
||||
set R [sqlite3_recover_init_sql db main my_sql_hook]
|
||||
$R config lostandfound lost_and_found
|
||||
$R step
|
||||
$R finish
|
||||
|
||||
sqlite3 db2 test.db2
|
||||
db2 eval [join $::sqlhook ";"]
|
||||
|
||||
if {$tsql==""} {
|
||||
uplevel [list do_test $tn.sql [list compare_dbs db db2] {}]
|
||||
} else {
|
||||
uplevel [list do_execsql_test -db db2 $tn.sql $tsql $res]
|
||||
}
|
||||
db2 close
|
||||
}
|
||||
|
||||
proc my_sql_hook {sql} {
|
||||
lappend ::sqlhook $sql
|
||||
}
|
||||
|
||||
|
||||
set doc {
|
||||
hello
|
||||
world
|
||||
|
@ -64,12 +64,6 @@ struct RecoverBitmap {
|
||||
**
|
||||
*/
|
||||
#define RECOVERY_SCHEMA \
|
||||
" CREATE TABLE recovery.freelist(" \
|
||||
" pgno INTEGER PRIMARY KEY" \
|
||||
" );" \
|
||||
" CREATE TABLE recovery.dbptr(" \
|
||||
" pgno, child, PRIMARY KEY(child, pgno)" \
|
||||
" ) WITHOUT ROWID;" \
|
||||
" CREATE TABLE recovery.map(" \
|
||||
" pgno INTEGER PRIMARY KEY, parent INT" \
|
||||
" );" \
|
||||
@ -96,6 +90,9 @@ struct sqlite3_recover {
|
||||
char *zLostAndFound; /* Name of lost-and-found table (or NULL) */
|
||||
int bFreelistCorrupt;
|
||||
int bRecoverRowid;
|
||||
|
||||
void *pSqlCtx;
|
||||
int (*xSql)(void*,const char*);
|
||||
};
|
||||
|
||||
/*
|
||||
@ -445,13 +442,6 @@ static int recoverOpenOutput(sqlite3_recover *p){
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int recoverCacheDbptr(sqlite3_recover *p){
|
||||
return recoverExec(p, p->dbOut,
|
||||
"INSERT INTO recovery.dbptr "
|
||||
"SELECT pgno, child FROM sqlite_dbptr('getpage()')"
|
||||
);
|
||||
}
|
||||
|
||||
static int recoverCacheSchema(sqlite3_recover *p){
|
||||
return recoverExec(p, p->dbOut,
|
||||
"WITH RECURSIVE pages(p) AS ("
|
||||
@ -555,6 +545,15 @@ static void recoverAddTable(sqlite3_recover *p, const char *zName, i64 iRoot){
|
||||
}
|
||||
}
|
||||
|
||||
static void recoverSqlCallback(sqlite3_recover *p, const char *zSql){
|
||||
if( p->errCode==SQLITE_OK && p->xSql ){
|
||||
int res = p->xSql(p->pSqlCtx, zSql);
|
||||
if( res ){
|
||||
recoverError(p, SQLITE_ERROR, "callback returned an error - %d", res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
**
|
||||
*/
|
||||
@ -581,6 +580,7 @@ static int recoverWriteSchema1(sqlite3_recover *p){
|
||||
|
||||
int rc = sqlite3_exec(p->dbOut, zSql, 0, 0, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
recoverSqlCallback(p, zSql);
|
||||
if( bTable ){
|
||||
if( SQLITE_ROW==sqlite3_step(pTblname) ){
|
||||
const char *zName = sqlite3_column_text(pTblname, 0);
|
||||
@ -614,6 +614,8 @@ static int recoverWriteSchema2(sqlite3_recover *p){
|
||||
int rc = sqlite3_exec(p->dbOut, zSql, 0, 0, 0);
|
||||
if( rc!=SQLITE_OK && rc!=SQLITE_ERROR ){
|
||||
recoverDbError(p, p->dbOut);
|
||||
}else if( rc==SQLITE_OK ){
|
||||
recoverSqlCallback(p, zSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -644,9 +646,12 @@ static sqlite3_stmt *recoverInsertStmt(
|
||||
int nField
|
||||
){
|
||||
const char *zSep = "";
|
||||
const char *zSqlSep = "";
|
||||
char *zSql = 0;
|
||||
char *zFinal = 0;
|
||||
char *zBind = 0;
|
||||
int ii;
|
||||
int bSql = p->xSql ? 1 : 0;
|
||||
sqlite3_stmt *pRet = 0;
|
||||
|
||||
assert( nField<=pTab->nCol );
|
||||
@ -656,9 +661,16 @@ static sqlite3_stmt *recoverInsertStmt(
|
||||
if( pTab->iRowidBind ){
|
||||
assert( pTab->bIntkey );
|
||||
zSql = recoverMPrintf(p, "%z_rowid_", zSql);
|
||||
zBind = recoverMPrintf(p, "%z?%d", zBind, pTab->iRowidBind);
|
||||
if( bSql ){
|
||||
zBind = recoverMPrintf(p, "%zquote(?%d)", zBind, pTab->iRowidBind);
|
||||
}else{
|
||||
zBind = recoverMPrintf(p, "%z?%d", zBind, pTab->iRowidBind);
|
||||
}
|
||||
zSqlSep = "||', '||";
|
||||
zSep = ", ";
|
||||
}
|
||||
|
||||
|
||||
for(ii=0; ii<nField; ii++){
|
||||
int eHidden = pTab->aCol[ii].eHidden;
|
||||
if( eHidden!=RECOVER_EHIDDEN_VIRTUAL
|
||||
@ -666,14 +678,31 @@ static sqlite3_stmt *recoverInsertStmt(
|
||||
){
|
||||
assert( pTab->aCol[ii].iField>=0 && pTab->aCol[ii].iBind>=1 );
|
||||
zSql = recoverMPrintf(p, "%z%s%Q", zSql, zSep, pTab->aCol[ii].zCol);
|
||||
zBind = recoverMPrintf(p, "%z%s?%d", zBind, zSep, pTab->aCol[ii].iBind);
|
||||
|
||||
if( bSql ){
|
||||
zBind = recoverMPrintf(
|
||||
p, "%z%squote(?%d)", zBind, zSqlSep, pTab->aCol[ii].iBind
|
||||
);
|
||||
zSqlSep = "||', '||";
|
||||
}else{
|
||||
zBind = recoverMPrintf(p, "%z%s?%d", zBind, zSep, pTab->aCol[ii].iBind);
|
||||
}
|
||||
zSep = ", ";
|
||||
}
|
||||
}
|
||||
zSql = recoverMPrintf(p, "%z) VALUES (%z)", zSql, zBind);
|
||||
|
||||
pRet = recoverPrepare(p, p->dbOut, zSql);
|
||||
if( bSql ){
|
||||
zFinal = recoverMPrintf(p, "SELECT %Q || ') VALUES (' || %s || ')'",
|
||||
zSql, zBind
|
||||
);
|
||||
}else{
|
||||
zFinal = recoverMPrintf(p, "%s) VALUES (%s)", zSql, zBind);
|
||||
}
|
||||
|
||||
pRet = recoverPrepare(p, p->dbOut, zFinal);
|
||||
sqlite3_free(zSql);
|
||||
sqlite3_free(zBind);
|
||||
sqlite3_free(zFinal);
|
||||
|
||||
return pRet;
|
||||
}
|
||||
@ -744,6 +773,7 @@ static char *recoverLostAndFoundCreate(
|
||||
sqlite3_free(zField);
|
||||
|
||||
recoverExec(p, p->dbOut, zSql);
|
||||
recoverSqlCallback(p, zSql);
|
||||
sqlite3_free(zSql);
|
||||
}else if( p->errCode==SQLITE_OK ){
|
||||
recoverError(
|
||||
@ -838,6 +868,7 @@ static void recoverLostAndFoundPopulate(
|
||||
sqlite3_value_free(apVal[ii]);
|
||||
apVal[ii] = 0;
|
||||
}
|
||||
sqlite3_clear_bindings(pInsert);
|
||||
bHaveRowid = 0;
|
||||
nVal = -1;
|
||||
}
|
||||
@ -1052,7 +1083,10 @@ static int recoverWriteData(sqlite3_recover *p){
|
||||
sqlite3_bind_int64(pInsert, pTab->iRowidBind, iRowid);
|
||||
}
|
||||
|
||||
sqlite3_step(pInsert);
|
||||
if( SQLITE_ROW==sqlite3_step(pInsert) && p->xSql ){
|
||||
const char *zSql = (const char*)sqlite3_column_text(pInsert, 0);
|
||||
recoverSqlCallback(p, zSql);
|
||||
}
|
||||
recoverReset(p, pInsert);
|
||||
assert( p->errCode || pInsert );
|
||||
if( pInsert ) sqlite3_clear_bindings(pInsert);
|
||||
@ -1098,10 +1132,12 @@ static int recoverWriteData(sqlite3_recover *p){
|
||||
return p->errCode;
|
||||
}
|
||||
|
||||
sqlite3_recover *sqlite3_recover_init(
|
||||
sqlite3_recover *recoverInit(
|
||||
sqlite3* db,
|
||||
const char *zDb,
|
||||
const char *zUri
|
||||
const char *zUri,
|
||||
int (*xSql)(void*, const char*),
|
||||
void *pSqlCtx
|
||||
){
|
||||
sqlite3_recover *pRet = 0;
|
||||
int nDb = 0;
|
||||
@ -1123,11 +1159,30 @@ sqlite3_recover *sqlite3_recover_init(
|
||||
pRet->zUri = &pRet->zDb[nDb+1];
|
||||
memcpy(pRet->zDb, zDb, nDb);
|
||||
memcpy(pRet->zUri, zUri, nUri);
|
||||
pRet->xSql = xSql;
|
||||
pRet->pSqlCtx = pSqlCtx;
|
||||
}
|
||||
|
||||
return pRet;
|
||||
}
|
||||
|
||||
sqlite3_recover *sqlite3_recover_init(
|
||||
sqlite3* db,
|
||||
const char *zDb,
|
||||
const char *zUri
|
||||
){
|
||||
return recoverInit(db, zDb, zUri, 0, 0);
|
||||
}
|
||||
|
||||
sqlite3_recover *sqlite3_recover_init_sql(
|
||||
sqlite3* db,
|
||||
const char *zDb,
|
||||
int (*xSql)(void*, const char*),
|
||||
void *pSqlCtx
|
||||
){
|
||||
return recoverInit(db, zDb, "", xSql, pSqlCtx);
|
||||
}
|
||||
|
||||
const char *sqlite3_recover_errmsg(sqlite3_recover *p){
|
||||
return p ? p->zErrMsg : "not an error";
|
||||
}
|
||||
@ -1176,7 +1231,6 @@ static void recoverStep(sqlite3_recover *p){
|
||||
|
||||
if( p->dbOut==0 ){
|
||||
if( recoverOpenOutput(p) ) return;
|
||||
if( recoverCacheDbptr(p) ) return;
|
||||
if( recoverCacheSchema(p) ) return;
|
||||
if( recoverWriteSchema1(p) ) return;
|
||||
if( recoverWriteData(p) ) return;
|
||||
|
@ -24,7 +24,8 @@ extern "C" {
|
||||
|
||||
typedef struct sqlite3_recover sqlite3_recover;
|
||||
|
||||
/* Create an object to recover data from database zDb (e.g. "main")
|
||||
/*
|
||||
** Create an object to recover data from database zDb (e.g. "main")
|
||||
** opened by handle db. Data will be recovered into the database
|
||||
** identified by parameter zUri. Database zUri is clobbered if it
|
||||
** already exists.
|
||||
@ -35,6 +36,13 @@ sqlite3_recover *sqlite3_recover_init(
|
||||
const char *zUri
|
||||
);
|
||||
|
||||
sqlite3_recover *sqlite3_recover_init_sql(
|
||||
sqlite3* db,
|
||||
const char *zDb,
|
||||
int (*xSql)(void*, const char*),
|
||||
void *pCtx
|
||||
);
|
||||
|
||||
/* Details TBD. */
|
||||
int sqlite3_recover_config(sqlite3_recover*, int op, void *pArg);
|
||||
|
||||
@ -60,6 +68,8 @@ int sqlite3_recover_config(sqlite3_recover*, int op, void *pArg);
|
||||
** database, but often ends up "recovering" deleted records.
|
||||
**
|
||||
** SQLITE_RECOVER_ROWIDS:
|
||||
**
|
||||
** SQLITE_RECOVER_SQLHOOK:
|
||||
*/
|
||||
#define SQLITE_RECOVER_TESTDB 789
|
||||
#define SQLITE_RECOVER_LOST_AND_FOUND 790
|
||||
|
@ -20,8 +20,31 @@
|
||||
typedef struct TestRecover TestRecover;
|
||||
struct TestRecover {
|
||||
sqlite3_recover *p;
|
||||
Tcl_Interp *interp;
|
||||
Tcl_Obj *pScript;
|
||||
};
|
||||
|
||||
static int xSqlCallback(void *pSqlArg, const char *zSql){
|
||||
TestRecover *p = (TestRecover*)pSqlArg;
|
||||
Tcl_Obj *pEval = 0;
|
||||
int res = 0;
|
||||
|
||||
pEval = Tcl_DuplicateObj(p->pScript);
|
||||
Tcl_IncrRefCount(pEval);
|
||||
|
||||
res = Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zSql, -1));
|
||||
if( res==TCL_OK ){
|
||||
res = Tcl_EvalObjEx(p->interp, pEval, 0);
|
||||
}
|
||||
|
||||
Tcl_DecrRefCount(pEval);
|
||||
if( res ){
|
||||
Tcl_BackgroundError(p->interp);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
static int getDbPointer(Tcl_Interp *interp, Tcl_Obj *pObj, sqlite3 **pDb){
|
||||
Tcl_CmdInfo info;
|
||||
if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(pObj), &info) ){
|
||||
@ -171,17 +194,26 @@ static int test_sqlite3_recover_init(
|
||||
const char *zDb = 0;
|
||||
const char *zUri = 0;
|
||||
char zCmd[128];
|
||||
int bSql = clientData ? 1 : 0;
|
||||
|
||||
if( objc!=4 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME URI");
|
||||
const char *zErr = (bSql ? "DB DBNAME SCRIPT" : "DB DBNAME URI");
|
||||
Tcl_WrongNumArgs(interp, 1, objv, zErr);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( getDbPointer(interp, objv[1], &db) ) return TCL_ERROR;
|
||||
zDb = Tcl_GetString(objv[2]);
|
||||
zUri = Tcl_GetString(objv[3]);
|
||||
|
||||
pNew = ckalloc(sizeof(TestRecover));
|
||||
pNew->p = sqlite3_recover_init(db, zDb, zUri);
|
||||
if( bSql==0 ){
|
||||
zUri = Tcl_GetString(objv[3]);
|
||||
pNew->p = sqlite3_recover_init(db, zDb, zUri);
|
||||
}else{
|
||||
pNew->interp = interp;
|
||||
pNew->pScript = objv[3];
|
||||
Tcl_IncrRefCount(pNew->pScript);
|
||||
pNew->p = sqlite3_recover_init_sql(db, zDb, xSqlCallback, (void*)pNew);
|
||||
}
|
||||
|
||||
sprintf(zCmd, "sqlite_recover%d", iTestRecoverCmd++);
|
||||
Tcl_CreateObjCommand(interp, zCmd, testRecoverCmd, (void*)pNew, 0);
|
||||
@ -194,14 +226,16 @@ int TestRecover_Init(Tcl_Interp *interp){
|
||||
struct Cmd {
|
||||
const char *zCmd;
|
||||
Tcl_ObjCmdProc *xProc;
|
||||
void *pArg;
|
||||
} aCmd[] = {
|
||||
{ "sqlite3_recover_init", test_sqlite3_recover_init },
|
||||
{ "sqlite3_recover_init", test_sqlite3_recover_init, 0 },
|
||||
{ "sqlite3_recover_init_sql", test_sqlite3_recover_init, (void*)1 },
|
||||
};
|
||||
int i;
|
||||
|
||||
for(i=0; i<sizeof(aCmd)/sizeof(struct Cmd); i++){
|
||||
struct Cmd *p = &aCmd[i];
|
||||
Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, 0, 0);
|
||||
Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, p->pArg, 0);
|
||||
}
|
||||
|
||||
return TCL_OK;
|
||||
|
20
manifest
20
manifest
@ -1,5 +1,5 @@
|
||||
C Add\sthe\sSQLITE_RECOVER_ROWIDS\soption.\sTo\sspecify\sthat\srowid\svalues\sthat\sare\snot\salso\sexplicit\sINTEGER\sPRIMARY\sKEY\svalues\sshould\sbe\spreserved.
|
||||
D 2022-09-05T15:56:09.391
|
||||
C Add\sa\smode\sto\soutput\sSQL\sstatements\sinstead\sof\spopulating\sa\sdatabase\sto\sthe\srecover\sextension.
|
||||
D 2022-09-05T21:00:22.268
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
||||
@ -387,12 +387,12 @@ F ext/rbu/rbuvacuum4.test a78898e438a44803eb2bc897ba3323373c9f277418e2d6d76e90f2
|
||||
F ext/rbu/sqlite3rbu.c 8737cabdfbee84bb25a7851ecef8b1312be332761238da9be6ddb10c62ad4291
|
||||
F ext/rbu/sqlite3rbu.h 1dc88ab7bd32d0f15890ea08d23476c4198d3da3056985403991f8c9cd389812
|
||||
F ext/rbu/test_rbu.c 03f6f177096a5f822d68d8e4069ad8907fe572c62ff2d19b141f59742821828a
|
||||
F ext/recover/recover1.test a848af8c82fe0731af835ff99475724f8654d2f24f772cc4e6f7ec4eb2ab71ea
|
||||
F ext/recover/recover1.test ae8ce9828210aa6c466bf88e23b0933849d5bb43091abe48cf2e56d636e51e93
|
||||
F ext/recover/recover_common.tcl 6679af7dffc858e345053a91c9b0a897595b4a13007aceffafca75304ccb137c
|
||||
F ext/recover/recoverold.test e7e00c78ec35b60488369ddf99e36a3b30e686566571969b05781e5063bdffe8
|
||||
F ext/recover/sqlite3recover.c 13e78c8a9d5521e06ebe5ac992a90169155e685f5c4b3cebc632c50b41e061c9
|
||||
F ext/recover/sqlite3recover.h ed34bc96befdf581a7de039d99772caabf0b338eb2a841d869acd9f7893ffce7
|
||||
F ext/recover/test_recover.c 9b8144ac94e6a2f3aabfcd24db6416179afdd32a3d654d1ef603c570e0384b2f
|
||||
F ext/recover/recoverold.test f368a6ae2db12b6017257b332a19ab5df527f4061e43f12f5c85d8e2b236f074
|
||||
F ext/recover/sqlite3recover.c 47767b52f09fb1bba47009236285f09bcf68b6b5d1ec0de96b1a30b508e536a4
|
||||
F ext/recover/sqlite3recover.h 32f89b66f0235c0d94dfee0f1c3e9ed1ad726b3c4f3716ef0262b31cc33e8301
|
||||
F ext/recover/test_recover.c be0d74f0eba44fe7964e22d287dba0f3fa2baf197f630d51f0f9066af9b5eb2a
|
||||
F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15
|
||||
F ext/repair/checkfreelist.c e21f06995ff4efdc1622dcceaea4dcba2caa83ca2f31a1607b98a8509168a996
|
||||
F ext/repair/checkindex.c 4383e4469c21e5b9ae321d0d63cec53e981af9d7a6564be6374f0eeb93dfc890
|
||||
@ -2005,8 +2005,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P 253e498f5200b8b3e2bc309587af108dd1cec8a884b3d2a49d5406525c9a4b4c
|
||||
R e5ada01eb624b55d2f64c4994830b06d
|
||||
P 69cc9aba56a196bbd159bd24868aa5ccc60bed0dc612d09ed8a3ae898f156809
|
||||
R fe94b03379801a454a2e4362e6c6eea7
|
||||
U dan
|
||||
Z a7e3c99975f0369d94d22f82581262dd
|
||||
Z bbf845b5205dd27415c5eec6b9356a28
|
||||
# Remove this line to create a well-formed Fossil manifest.
|
||||
|
@ -1 +1 @@
|
||||
69cc9aba56a196bbd159bd24868aa5ccc60bed0dc612d09ed8a3ae898f156809
|
||||
73058416e7da6581000898b7988a7010e2ce6632246f4c12b4398700c7744b83
|
Loading…
x
Reference in New Issue
Block a user