mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-08-03 16:30:29 +00:00
Upgrade SQLite to 3.40 (#699)
This commit is contained in:
parent
bcae817215
commit
0dc0758574
151 changed files with 27917 additions and 22169 deletions
159
third_party/sqlite3/vtab.c
vendored
159
third_party/sqlite3/vtab.c
vendored
|
@ -12,8 +12,7 @@
|
|||
** This file contains code used to help implement virtual tables.
|
||||
*/
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
#include "third_party/sqlite3/sqliteInt.inc"
|
||||
/* clang-format off */
|
||||
#include "third_party/sqlite3/sqliteInt.h"
|
||||
|
||||
/*
|
||||
** Before a virtual table xCreate() or xConnect() method is invoked, the
|
||||
|
@ -193,7 +192,7 @@ void sqlite3VtabLock(VTable *pVTab){
|
|||
VTable *sqlite3GetVTable(sqlite3 *db, Table *pTab){
|
||||
VTable *pVtab;
|
||||
assert( IsVirtual(pTab) );
|
||||
for(pVtab=pTab->pVTable; pVtab && pVtab->db!=db; pVtab=pVtab->pNext);
|
||||
for(pVtab=pTab->u.vtab.p; pVtab && pVtab->db!=db; pVtab=pVtab->pNext);
|
||||
return pVtab;
|
||||
}
|
||||
|
||||
|
@ -206,7 +205,8 @@ void sqlite3VtabUnlock(VTable *pVTab){
|
|||
|
||||
assert( db );
|
||||
assert( pVTab->nRef>0 );
|
||||
assert( db->magic==SQLITE_MAGIC_OPEN || db->magic==SQLITE_MAGIC_ZOMBIE );
|
||||
assert( db->eOpenState==SQLITE_STATE_OPEN
|
||||
|| db->eOpenState==SQLITE_STATE_ZOMBIE );
|
||||
|
||||
pVTab->nRef--;
|
||||
if( pVTab->nRef==0 ){
|
||||
|
@ -221,21 +221,24 @@ void sqlite3VtabUnlock(VTable *pVTab){
|
|||
|
||||
/*
|
||||
** Table p is a virtual table. This function moves all elements in the
|
||||
** p->pVTable list to the sqlite3.pDisconnect lists of their associated
|
||||
** p->u.vtab.p list to the sqlite3.pDisconnect lists of their associated
|
||||
** database connections to be disconnected at the next opportunity.
|
||||
** Except, if argument db is not NULL, then the entry associated with
|
||||
** connection db is left in the p->pVTable list.
|
||||
** connection db is left in the p->u.vtab.p list.
|
||||
*/
|
||||
static VTable *vtabDisconnectAll(sqlite3 *db, Table *p){
|
||||
VTable *pRet = 0;
|
||||
VTable *pVTable = p->pVTable;
|
||||
p->pVTable = 0;
|
||||
VTable *pVTable;
|
||||
|
||||
assert( IsVirtual(p) );
|
||||
pVTable = p->u.vtab.p;
|
||||
p->u.vtab.p = 0;
|
||||
|
||||
/* Assert that the mutex (if any) associated with the BtShared database
|
||||
** that contains table p is held by the caller. See header comments
|
||||
** above function sqlite3VtabUnlockList() for an explanation of why
|
||||
** this makes it safe to access the sqlite3.pDisconnect list of any
|
||||
** database connection that may have an entry in the p->pVTable list.
|
||||
** database connection that may have an entry in the p->u.vtab.p list.
|
||||
*/
|
||||
assert( db==0 || sqlite3SchemaMutexHeld(db, 0, p->pSchema) );
|
||||
|
||||
|
@ -245,7 +248,7 @@ static VTable *vtabDisconnectAll(sqlite3 *db, Table *p){
|
|||
assert( db2 );
|
||||
if( db2==db ){
|
||||
pRet = pVTable;
|
||||
p->pVTable = pRet;
|
||||
p->u.vtab.p = pRet;
|
||||
pRet->pNext = 0;
|
||||
}else{
|
||||
pVTable->pNext = db2->pDisconnect;
|
||||
|
@ -273,7 +276,7 @@ void sqlite3VtabDisconnect(sqlite3 *db, Table *p){
|
|||
assert( sqlite3BtreeHoldsAllMutexes(db) );
|
||||
assert( sqlite3_mutex_held(db->mutex) );
|
||||
|
||||
for(ppVTab=&p->pVTable; *ppVTab; ppVTab=&(*ppVTab)->pNext){
|
||||
for(ppVTab=&p->u.vtab.p; *ppVTab; ppVTab=&(*ppVTab)->pNext){
|
||||
if( (*ppVTab)->db==db ){
|
||||
VTable *pVTab = *ppVTab;
|
||||
*ppVTab = pVTab->pNext;
|
||||
|
@ -336,37 +339,42 @@ void sqlite3VtabUnlockList(sqlite3 *db){
|
|||
** database connection.
|
||||
*/
|
||||
void sqlite3VtabClear(sqlite3 *db, Table *p){
|
||||
if( !db || db->pnBytesFreed==0 ) vtabDisconnectAll(0, p);
|
||||
if( p->azModuleArg ){
|
||||
assert( IsVirtual(p) );
|
||||
assert( db!=0 );
|
||||
if( db->pnBytesFreed==0 ) vtabDisconnectAll(0, p);
|
||||
if( p->u.vtab.azArg ){
|
||||
int i;
|
||||
for(i=0; i<p->nModuleArg; i++){
|
||||
if( i!=1 ) sqlite3DbFree(db, p->azModuleArg[i]);
|
||||
for(i=0; i<p->u.vtab.nArg; i++){
|
||||
if( i!=1 ) sqlite3DbFree(db, p->u.vtab.azArg[i]);
|
||||
}
|
||||
sqlite3DbFree(db, p->azModuleArg);
|
||||
sqlite3DbFree(db, p->u.vtab.azArg);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Add a new module argument to pTable->azModuleArg[].
|
||||
** Add a new module argument to pTable->u.vtab.azArg[].
|
||||
** The string is not copied - the pointer is stored. The
|
||||
** string will be freed automatically when the table is
|
||||
** deleted.
|
||||
*/
|
||||
static void addModuleArgument(Parse *pParse, Table *pTable, char *zArg){
|
||||
sqlite3_int64 nBytes = sizeof(char *)*(2+pTable->nModuleArg);
|
||||
sqlite3_int64 nBytes;
|
||||
char **azModuleArg;
|
||||
sqlite3 *db = pParse->db;
|
||||
if( pTable->nModuleArg+3>=db->aLimit[SQLITE_LIMIT_COLUMN] ){
|
||||
|
||||
assert( IsVirtual(pTable) );
|
||||
nBytes = sizeof(char *)*(2+pTable->u.vtab.nArg);
|
||||
if( pTable->u.vtab.nArg+3>=db->aLimit[SQLITE_LIMIT_COLUMN] ){
|
||||
sqlite3ErrorMsg(pParse, "too many columns on %s", pTable->zName);
|
||||
}
|
||||
azModuleArg = sqlite3DbRealloc(db, pTable->azModuleArg, nBytes);
|
||||
azModuleArg = sqlite3DbRealloc(db, pTable->u.vtab.azArg, nBytes);
|
||||
if( azModuleArg==0 ){
|
||||
sqlite3DbFree(db, zArg);
|
||||
}else{
|
||||
int i = pTable->nModuleArg++;
|
||||
int i = pTable->u.vtab.nArg++;
|
||||
azModuleArg[i] = zArg;
|
||||
azModuleArg[i+1] = 0;
|
||||
pTable->azModuleArg = azModuleArg;
|
||||
pTable->u.vtab.azArg = azModuleArg;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -389,10 +397,11 @@ void sqlite3VtabBeginParse(
|
|||
pTable = pParse->pNewTable;
|
||||
if( pTable==0 ) return;
|
||||
assert( 0==pTable->pIndex );
|
||||
pTable->eTabType = TABTYP_VTAB;
|
||||
|
||||
db = pParse->db;
|
||||
|
||||
assert( pTable->nModuleArg==0 );
|
||||
assert( pTable->u.vtab.nArg==0 );
|
||||
addModuleArgument(pParse, pTable, sqlite3NameFromToken(db, pModuleName));
|
||||
addModuleArgument(pParse, pTable, 0);
|
||||
addModuleArgument(pParse, pTable, sqlite3DbStrDup(db, pTable->zName));
|
||||
|
@ -409,11 +418,11 @@ void sqlite3VtabBeginParse(
|
|||
** sqlite_schema table, has already been made by sqlite3StartTable().
|
||||
** The second call, to obtain permission to create the table, is made now.
|
||||
*/
|
||||
if( pTable->azModuleArg ){
|
||||
if( pTable->u.vtab.azArg ){
|
||||
int iDb = sqlite3SchemaToIndex(db, pTable->pSchema);
|
||||
assert( iDb>=0 ); /* The database the table is being created in */
|
||||
sqlite3AuthCheck(pParse, SQLITE_CREATE_VTABLE, pTable->zName,
|
||||
pTable->azModuleArg[0], pParse->db->aDb[iDb].zDbSName);
|
||||
pTable->u.vtab.azArg[0], pParse->db->aDb[iDb].zDbSName);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -441,9 +450,10 @@ void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){
|
|||
sqlite3 *db = pParse->db; /* The database connection */
|
||||
|
||||
if( pTab==0 ) return;
|
||||
assert( IsVirtual(pTab) );
|
||||
addArgumentToVtab(pParse);
|
||||
pParse->sArg.z = 0;
|
||||
if( pTab->nModuleArg<1 ) return;
|
||||
if( pTab->u.vtab.nArg<1 ) return;
|
||||
|
||||
/* If the CREATE VIRTUAL TABLE statement is being entered for the
|
||||
** first time (in other words if the virtual table is actually being
|
||||
|
@ -476,7 +486,7 @@ void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){
|
|||
*/
|
||||
iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
|
||||
sqlite3NestedParse(pParse,
|
||||
"UPDATE %Q." DFLT_SCHEMA_TABLE " "
|
||||
"UPDATE %Q." LEGACY_SCHEMA_TABLE " "
|
||||
"SET type='table', name=%Q, tbl_name=%Q, rootpage=0, sql=%Q "
|
||||
"WHERE rowid=#%d",
|
||||
db->aDb[iDb].zDbSName,
|
||||
|
@ -496,18 +506,14 @@ void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){
|
|||
iReg = ++pParse->nMem;
|
||||
sqlite3VdbeLoadString(v, iReg, pTab->zName);
|
||||
sqlite3VdbeAddOp2(v, OP_VCreate, iDb, iReg);
|
||||
}
|
||||
|
||||
/* If we are rereading the sqlite_schema table create the in-memory
|
||||
** record of the table. The xConnect() method is not called until
|
||||
** the first time the virtual table is used in an SQL statement. This
|
||||
** allows a schema that contains virtual tables to be loaded before
|
||||
** the required virtual table implementations are registered. */
|
||||
else {
|
||||
}else{
|
||||
/* If we are rereading the sqlite_schema table create the in-memory
|
||||
** record of the table. */
|
||||
Table *pOld;
|
||||
Schema *pSchema = pTab->pSchema;
|
||||
const char *zName = pTab->zName;
|
||||
assert( sqlite3SchemaMutexHeld(db, 0, pSchema) );
|
||||
assert( zName!=0 );
|
||||
sqlite3MarkAllShadowTablesOf(db, pTab);
|
||||
pOld = sqlite3HashInsert(&pSchema->tblHash, zName, pTab);
|
||||
if( pOld ){
|
||||
sqlite3OomFault(db);
|
||||
|
@ -558,13 +564,16 @@ static int vtabCallConstructor(
|
|||
VtabCtx sCtx;
|
||||
VTable *pVTable;
|
||||
int rc;
|
||||
const char *const*azArg = (const char *const*)pTab->azModuleArg;
|
||||
int nArg = pTab->nModuleArg;
|
||||
const char *const*azArg;
|
||||
int nArg = pTab->u.vtab.nArg;
|
||||
char *zErr = 0;
|
||||
char *zModuleName;
|
||||
int iDb;
|
||||
VtabCtx *pCtx;
|
||||
|
||||
assert( IsVirtual(pTab) );
|
||||
azArg = (const char *const*)pTab->u.vtab.azArg;
|
||||
|
||||
/* Check that the virtual-table is not already being initialized */
|
||||
for(pCtx=db->pVtabCtx; pCtx; pCtx=pCtx->pPrior){
|
||||
if( pCtx->pTab==pTab ){
|
||||
|
@ -591,7 +600,7 @@ static int vtabCallConstructor(
|
|||
pVTable->eVtabRisk = SQLITE_VTABRISK_Normal;
|
||||
|
||||
iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
|
||||
pTab->azModuleArg[1] = db->aDb[iDb].zDbSName;
|
||||
pTab->u.vtab.azArg[1] = db->aDb[iDb].zDbSName;
|
||||
|
||||
/* Invoke the virtual table constructor */
|
||||
assert( &db->pVtabCtx );
|
||||
|
@ -630,12 +639,12 @@ static int vtabCallConstructor(
|
|||
int iCol;
|
||||
u16 oooHidden = 0;
|
||||
/* If everything went according to plan, link the new VTable structure
|
||||
** into the linked list headed by pTab->pVTable. Then loop through the
|
||||
** into the linked list headed by pTab->u.vtab.p. Then loop through the
|
||||
** columns of the table to see if any of them contain the token "hidden".
|
||||
** If so, set the Column COLFLAG_HIDDEN flag and remove the token from
|
||||
** the type string. */
|
||||
pVTable->pNext = pTab->pVTable;
|
||||
pTab->pVTable = pVTable;
|
||||
pVTable->pNext = pTab->u.vtab.p;
|
||||
pTab->u.vtab.p = pVTable;
|
||||
|
||||
for(iCol=0; iCol<pTab->nCol; iCol++){
|
||||
char *zType = sqlite3ColumnType(&pTab->aCol[iCol], "");
|
||||
|
@ -688,16 +697,17 @@ int sqlite3VtabCallConnect(Parse *pParse, Table *pTab){
|
|||
int rc;
|
||||
|
||||
assert( pTab );
|
||||
if( !IsVirtual(pTab) || sqlite3GetVTable(db, pTab) ){
|
||||
assert( IsVirtual(pTab) );
|
||||
if( sqlite3GetVTable(db, pTab) ){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/* Locate the required virtual table module */
|
||||
zMod = pTab->azModuleArg[0];
|
||||
zMod = pTab->u.vtab.azArg[0];
|
||||
pMod = (Module*)sqlite3HashFind(&db->aModule, zMod);
|
||||
|
||||
if( !pMod ){
|
||||
const char *zModule = pTab->azModuleArg[0];
|
||||
const char *zModule = pTab->u.vtab.azArg[0];
|
||||
sqlite3ErrorMsg(pParse, "no such module: %s", zModule);
|
||||
rc = SQLITE_ERROR;
|
||||
}else{
|
||||
|
@ -760,10 +770,10 @@ int sqlite3VtabCallCreate(sqlite3 *db, int iDb, const char *zTab, char **pzErr){
|
|||
const char *zMod;
|
||||
|
||||
pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zDbSName);
|
||||
assert( pTab && IsVirtual(pTab) && !pTab->pVTable );
|
||||
assert( pTab && IsVirtual(pTab) && !pTab->u.vtab.p );
|
||||
|
||||
/* Locate the required virtual table module */
|
||||
zMod = pTab->azModuleArg[0];
|
||||
zMod = pTab->u.vtab.azArg[0];
|
||||
pMod = (Module*)sqlite3HashFind(&db->aModule, zMod);
|
||||
|
||||
/* If the module has been registered and includes a Create method,
|
||||
|
@ -798,8 +808,8 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
|
|||
VtabCtx *pCtx;
|
||||
int rc = SQLITE_OK;
|
||||
Table *pTab;
|
||||
char *zErr = 0;
|
||||
Parse sParse;
|
||||
int initBusy;
|
||||
|
||||
#ifdef SQLITE_ENABLE_API_ARMOR
|
||||
if( !sqlite3SafetyCheckOk(db) || zCreateTable==0 ){
|
||||
|
@ -816,20 +826,27 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
|
|||
pTab = pCtx->pTab;
|
||||
assert( IsVirtual(pTab) );
|
||||
|
||||
memset(&sParse, 0, sizeof(sParse));
|
||||
sqlite3ParseObjectInit(&sParse, db);
|
||||
sParse.eParseMode = PARSE_MODE_DECLARE_VTAB;
|
||||
sParse.db = db;
|
||||
sParse.disableTriggers = 1;
|
||||
/* We should never be able to reach this point while loading the
|
||||
** schema. Nevertheless, defend against that (turn off db->init.busy)
|
||||
** in case a bug arises. */
|
||||
assert( db->init.busy==0 );
|
||||
initBusy = db->init.busy;
|
||||
db->init.busy = 0;
|
||||
sParse.nQueryLoop = 1;
|
||||
if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable, &zErr)
|
||||
&& sParse.pNewTable
|
||||
&& !db->mallocFailed
|
||||
&& !sParse.pNewTable->pSelect
|
||||
&& !IsVirtual(sParse.pNewTable)
|
||||
if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable)
|
||||
&& ALWAYS(sParse.pNewTable!=0)
|
||||
&& ALWAYS(!db->mallocFailed)
|
||||
&& IsOrdinaryTable(sParse.pNewTable)
|
||||
){
|
||||
assert( sParse.zErrMsg==0 );
|
||||
if( !pTab->aCol ){
|
||||
Table *pNew = sParse.pNewTable;
|
||||
Index *pIdx;
|
||||
pTab->aCol = pNew->aCol;
|
||||
sqlite3ExprListDelete(db, pNew->u.tab.pDfltList);
|
||||
pTab->nNVCol = pTab->nCol = pNew->nCol;
|
||||
pTab->tabFlags |= pNew->tabFlags & (TF_WithoutRowid|TF_NoVisibleRowid);
|
||||
pNew->nCol = 0;
|
||||
|
@ -854,8 +871,9 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
|
|||
}
|
||||
pCtx->bDeclared = 1;
|
||||
}else{
|
||||
sqlite3ErrorWithMsg(db, SQLITE_ERROR, (zErr ? "%s" : 0), zErr);
|
||||
sqlite3DbFree(db, zErr);
|
||||
sqlite3ErrorWithMsg(db, SQLITE_ERROR,
|
||||
(sParse.zErrMsg ? "%s" : 0), sParse.zErrMsg);
|
||||
sqlite3DbFree(db, sParse.zErrMsg);
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
sParse.eParseMode = PARSE_MODE_NORMAL;
|
||||
|
@ -864,7 +882,8 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
|
|||
sqlite3VdbeFinalize(sParse.pVdbe);
|
||||
}
|
||||
sqlite3DeleteTable(db, sParse.pNewTable);
|
||||
sqlite3ParserReset(&sParse);
|
||||
sqlite3ParseObjectReset(&sParse);
|
||||
db->init.busy = initBusy;
|
||||
|
||||
assert( (rc&0xff)==rc );
|
||||
rc = sqlite3ApiExit(db, rc);
|
||||
|
@ -884,10 +903,13 @@ int sqlite3VtabCallDestroy(sqlite3 *db, int iDb, const char *zTab){
|
|||
Table *pTab;
|
||||
|
||||
pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zDbSName);
|
||||
if( pTab!=0 && ALWAYS(pTab->pVTable!=0) ){
|
||||
if( ALWAYS(pTab!=0)
|
||||
&& ALWAYS(IsVirtual(pTab))
|
||||
&& ALWAYS(pTab->u.vtab.p!=0)
|
||||
){
|
||||
VTable *p;
|
||||
int (*xDestroy)(sqlite3_vtab *);
|
||||
for(p=pTab->pVTable; p; p=p->pNext){
|
||||
for(p=pTab->u.vtab.p; p; p=p->pNext){
|
||||
assert( p->pVtab );
|
||||
if( p->pVtab->nRef>0 ){
|
||||
return SQLITE_LOCKED;
|
||||
|
@ -901,9 +923,9 @@ int sqlite3VtabCallDestroy(sqlite3 *db, int iDb, const char *zTab){
|
|||
rc = xDestroy(p->pVtab);
|
||||
/* Remove the sqlite3_vtab* from the aVTrans[] array, if applicable */
|
||||
if( rc==SQLITE_OK ){
|
||||
assert( pTab->pVTable==p && p->pNext==0 );
|
||||
assert( pTab->u.vtab.p==p && p->pNext==0 );
|
||||
p->pVtab = 0;
|
||||
pTab->pVTable = 0;
|
||||
pTab->u.vtab.p = 0;
|
||||
sqlite3VtabUnlock(p);
|
||||
}
|
||||
sqlite3DeleteTable(db, pTab);
|
||||
|
@ -1117,8 +1139,9 @@ FuncDef *sqlite3VtabOverloadFunction(
|
|||
/* Check to see the left operand is a column in a virtual table */
|
||||
if( NEVER(pExpr==0) ) return pDef;
|
||||
if( pExpr->op!=TK_COLUMN ) return pDef;
|
||||
assert( ExprUseYTab(pExpr) );
|
||||
pTab = pExpr->y.pTab;
|
||||
if( pTab==0 ) return pDef;
|
||||
if( NEVER(pTab==0) ) return pDef;
|
||||
if( !IsVirtual(pTab) ) return pDef;
|
||||
pVtab = sqlite3GetVTable(db, pTab)->pVtab;
|
||||
assert( pVtab!=0 );
|
||||
|
@ -1191,8 +1214,9 @@ void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){
|
|||
/*
|
||||
** Check to see if virtual table module pMod can be have an eponymous
|
||||
** virtual table instance. If it can, create one if one does not already
|
||||
** exist. Return non-zero if the eponymous virtual table instance exists
|
||||
** when this routine returns, and return zero if it does not exist.
|
||||
** exist. Return non-zero if either the eponymous virtual table instance
|
||||
** exists when this routine returns or if an attempt to create it failed
|
||||
** and an error message was left in pParse.
|
||||
**
|
||||
** An eponymous virtual table instance is one that is named after its
|
||||
** module, and more importantly, does not require a CREATE VIRTUAL TABLE
|
||||
|
@ -1219,9 +1243,11 @@ int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){
|
|||
}
|
||||
pMod->pEpoTab = pTab;
|
||||
pTab->nTabRef = 1;
|
||||
pTab->eTabType = TABTYP_VTAB;
|
||||
pTab->pSchema = db->aDb[0].pSchema;
|
||||
assert( pTab->nModuleArg==0 );
|
||||
assert( pTab->u.vtab.nArg==0 );
|
||||
pTab->iPKey = -1;
|
||||
pTab->tabFlags |= TF_Eponymous;
|
||||
addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName));
|
||||
addModuleArgument(pParse, pTab, 0);
|
||||
addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName));
|
||||
|
@ -1230,7 +1256,6 @@ int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){
|
|||
sqlite3ErrorMsg(pParse, "%s", zErr);
|
||||
sqlite3DbFree(db, zErr);
|
||||
sqlite3VtabEponymousTableClear(db, pMod);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue