4 ** The author disclaims copyright to this source code. In place of
5 ** a legal notice, here is a blessing:
7 ** May you do good and not evil.
8 ** May you find forgiveness for yourself and forgive others.
9 ** May you share freely, never taking more than you give.
11 ******************************************************************************
13 ** This file contains an implementation of the "dbstat" virtual table.
15 ** The dbstat virtual table is used to extract low-level formatting
16 ** information from an SQLite database in order to implement the
17 ** "sqlite3_analyzer" utility. See the ../tool/spaceanal.tcl script
18 ** for an example implementation.
20 ** Additional information is available on the "dbstat.html" page of the
21 ** official SQLite documentation.
24 #include "sqliteInt.h" /* Requires access to internal data structures */
25 #if (defined(SQLITE_ENABLE_DBSTAT_VTAB) || defined(SQLITE_TEST)) \
26 && !defined(SQLITE_OMIT_VIRTUALTABLE)
31 ** The value of the 'path' column describes the path taken from the
32 ** root-node of the b-tree structure to each page. The value of the
33 ** root-node path is '/'.
35 ** The value of the path for the left-most child page of the root of
36 ** a b-tree is '/000/'. (Btrees store content ordered from left to right
37 ** so the pages to the left have smaller keys than the pages to the right.)
38 ** The next to left-most child of the root page is
39 ** '/001', and so on, each sibling page identified by a 3-digit hex
40 ** value. The children of the 451st left-most sibling have paths such
41 ** as '/1c2/000/, '/1c2/001/' etc.
43 ** Overflow pages are specified by appending a '+' character and a
44 ** six-digit hexadecimal value to the path to the cell they are linked
45 ** from. For example, the three overflow pages in a chain linked from
46 ** the left-most cell of the 450th child of the root page are identified
49 ** '/1c2/000+000000' // First page in overflow chain
50 ** '/1c2/000+000001' // Second page in overflow chain
51 ** '/1c2/000+000002' // Third page in overflow chain
53 ** If the paths are sorted using the BINARY collation sequence, then
54 ** the overflow pages associated with a cell will appear earlier in the
55 ** sort-order than its child page:
57 ** '/1c2/000/' // Left-most child of 451st child of root
61 " name TEXT, /* Name of table or index */" \
62 " path TEXT, /* Path to page from root */" \
63 " pageno INTEGER, /* Page number */" \
64 " pagetype TEXT, /* 'internal', 'leaf' or 'overflow' */" \
65 " ncell INTEGER, /* Cells on page (0 for overflow) */" \
66 " payload INTEGER, /* Bytes of payload on this page */" \
67 " unused INTEGER, /* Bytes of unused space on this page */" \
68 " mx_payload INTEGER, /* Largest payload size of all cells */" \
69 " pgoffset INTEGER, /* Offset of page in file */" \
70 " pgsize INTEGER, /* Size of the page */" \
71 " schema TEXT HIDDEN /* Database schema being analyzed */" \
75 typedef struct StatTable StatTable
;
76 typedef struct StatCursor StatCursor
;
77 typedef struct StatPage StatPage
;
78 typedef struct StatCell StatCell
;
81 int nLocal
; /* Bytes of local payload */
82 u32 iChildPg
; /* Child node (or 0 if this is a leaf) */
83 int nOvfl
; /* Entries in aOvfl[] */
84 u32
*aOvfl
; /* Array of overflow page numbers */
85 int nLastOvfl
; /* Bytes of payload on final overflow page */
86 int iOvfl
; /* Iterates through aOvfl[] */
94 char *zPath
; /* Path to this page */
96 /* Variables populated by statDecodePage(): */
97 u8 flags
; /* Copy of flags byte */
98 int nCell
; /* Number of cells on page */
99 int nUnused
; /* Number of unused bytes on page */
100 StatCell
*aCell
; /* Array of parsed cells */
101 u32 iRightChildPg
; /* Right-child page number (or 0) */
102 int nMxPayload
; /* Largest payload of any cell on this page */
106 sqlite3_vtab_cursor base
;
107 sqlite3_stmt
*pStmt
; /* Iterates through set of root pages */
108 int isEof
; /* After pStmt has returned SQLITE_DONE */
109 int iDb
; /* Schema used for this query */
112 int iPage
; /* Current entry in aPage[] */
114 /* Values to return. */
115 char *zName
; /* Value of 'name' column */
116 char *zPath
; /* Value of 'path' column */
117 u32 iPageno
; /* Value of 'pageno' column */
118 char *zPagetype
; /* Value of 'pagetype' column */
119 int nCell
; /* Value of 'ncell' column */
120 int nPayload
; /* Value of 'payload' column */
121 int nUnused
; /* Value of 'unused' column */
122 int nMxPayload
; /* Value of 'mx_payload' column */
123 i64 iOffset
; /* Value of 'pgOffset' column */
124 int szPage
; /* Value of 'pgSize' column */
130 int iDb
; /* Index of database to analyze */
134 # define get2byte(x) ((x)[0]<<8 | (x)[1])
138 ** Connect to or create a statvfs virtual table.
140 static int statConnect(
143 int argc
, const char *const*argv
,
144 sqlite3_vtab
**ppVtab
,
153 sqlite3TokenInit(&nm
, (char*)argv
[3]);
154 iDb
= sqlite3FindDb(db
, &nm
);
156 *pzErr
= sqlite3_mprintf("no such database: %s", argv
[3]);
162 rc
= sqlite3_declare_vtab(db
, VTAB_SCHEMA
);
164 pTab
= (StatTable
*)sqlite3_malloc64(sizeof(StatTable
));
165 if( pTab
==0 ) rc
= SQLITE_NOMEM_BKPT
;
168 assert( rc
==SQLITE_OK
|| pTab
==0 );
170 memset(pTab
, 0, sizeof(StatTable
));
175 *ppVtab
= (sqlite3_vtab
*)pTab
;
180 ** Disconnect from or destroy a statvfs virtual table.
182 static int statDisconnect(sqlite3_vtab
*pVtab
){
188 ** There is no "best-index". This virtual table always does a linear
189 ** scan. However, a schema=? constraint should cause this table to
190 ** operate on a different database schema, so check for it.
192 ** idxNum is normally 0, but will be 1 if a schema=? constraint exists.
194 static int statBestIndex(sqlite3_vtab
*tab
, sqlite3_index_info
*pIdxInfo
){
197 pIdxInfo
->estimatedCost
= 1.0e6
; /* Initial cost estimate */
199 /* Look for a valid schema=? constraint. If found, change the idxNum to
200 ** 1 and request the value of that constraint be sent to xFilter. And
201 ** lower the cost estimate to encourage the constrained version to be
204 for(i
=0; i
<pIdxInfo
->nConstraint
; i
++){
205 if( pIdxInfo
->aConstraint
[i
].usable
==0 ) continue;
206 if( pIdxInfo
->aConstraint
[i
].op
!=SQLITE_INDEX_CONSTRAINT_EQ
) continue;
207 if( pIdxInfo
->aConstraint
[i
].iColumn
!=10 ) continue;
208 pIdxInfo
->idxNum
= 1;
209 pIdxInfo
->estimatedCost
= 1.0;
210 pIdxInfo
->aConstraintUsage
[i
].argvIndex
= 1;
211 pIdxInfo
->aConstraintUsage
[i
].omit
= 1;
216 /* Records are always returned in ascending order of (name, path).
217 ** If this will satisfy the client, set the orderByConsumed flag so that
218 ** SQLite does not do an external sort.
220 if( ( pIdxInfo
->nOrderBy
==1
221 && pIdxInfo
->aOrderBy
[0].iColumn
==0
222 && pIdxInfo
->aOrderBy
[0].desc
==0
224 ( pIdxInfo
->nOrderBy
==2
225 && pIdxInfo
->aOrderBy
[0].iColumn
==0
226 && pIdxInfo
->aOrderBy
[0].desc
==0
227 && pIdxInfo
->aOrderBy
[1].iColumn
==1
228 && pIdxInfo
->aOrderBy
[1].desc
==0
231 pIdxInfo
->orderByConsumed
= 1;
238 ** Open a new statvfs cursor.
240 static int statOpen(sqlite3_vtab
*pVTab
, sqlite3_vtab_cursor
**ppCursor
){
241 StatTable
*pTab
= (StatTable
*)pVTab
;
244 pCsr
= (StatCursor
*)sqlite3_malloc64(sizeof(StatCursor
));
246 return SQLITE_NOMEM_BKPT
;
248 memset(pCsr
, 0, sizeof(StatCursor
));
249 pCsr
->base
.pVtab
= pVTab
;
250 pCsr
->iDb
= pTab
->iDb
;
253 *ppCursor
= (sqlite3_vtab_cursor
*)pCsr
;
257 static void statClearPage(StatPage
*p
){
260 for(i
=0; i
<p
->nCell
; i
++){
261 sqlite3_free(p
->aCell
[i
].aOvfl
);
263 sqlite3_free(p
->aCell
);
265 sqlite3PagerUnref(p
->pPg
);
266 sqlite3_free(p
->zPath
);
267 memset(p
, 0, sizeof(StatPage
));
270 static void statResetCsr(StatCursor
*pCsr
){
272 sqlite3_reset(pCsr
->pStmt
);
273 for(i
=0; i
<ArraySize(pCsr
->aPage
); i
++){
274 statClearPage(&pCsr
->aPage
[i
]);
277 sqlite3_free(pCsr
->zPath
);
283 ** Close a statvfs cursor.
285 static int statClose(sqlite3_vtab_cursor
*pCursor
){
286 StatCursor
*pCsr
= (StatCursor
*)pCursor
;
288 sqlite3_finalize(pCsr
->pStmt
);
293 static void getLocalPayload(
294 int nUsable
, /* Usable bytes per page */
295 u8 flags
, /* Page flags */
296 int nTotal
, /* Total record (payload) size */
297 int *pnLocal
/* OUT: Bytes stored locally */
303 if( flags
==0x0D ){ /* Table leaf node */
304 nMinLocal
= (nUsable
- 12) * 32 / 255 - 23;
305 nMaxLocal
= nUsable
- 35;
306 }else{ /* Index interior and leaf nodes */
307 nMinLocal
= (nUsable
- 12) * 32 / 255 - 23;
308 nMaxLocal
= (nUsable
- 12) * 64 / 255 - 23;
311 nLocal
= nMinLocal
+ (nTotal
- nMinLocal
) % (nUsable
- 4);
312 if( nLocal
>nMaxLocal
) nLocal
= nMinLocal
;
316 static int statDecodePage(Btree
*pBt
, StatPage
*p
){
323 u8
*aData
= sqlite3PagerGetData(p
->pPg
);
324 u8
*aHdr
= &aData
[p
->iPgno
==1 ? 100 : 0];
327 p
->nCell
= get2byte(&aHdr
[3]);
330 isLeaf
= (p
->flags
==0x0A || p
->flags
==0x0D);
331 nHdr
= 12 - isLeaf
*4 + (p
->iPgno
==1)*100;
333 nUnused
= get2byte(&aHdr
[5]) - nHdr
- 2*p
->nCell
;
334 nUnused
+= (int)aHdr
[7];
335 iOff
= get2byte(&aHdr
[1]);
337 nUnused
+= get2byte(&aData
[iOff
+2]);
338 iOff
= get2byte(&aData
[iOff
]);
340 p
->nUnused
= nUnused
;
341 p
->iRightChildPg
= isLeaf
? 0 : sqlite3Get4byte(&aHdr
[8]);
342 szPage
= sqlite3BtreeGetPageSize(pBt
);
345 int i
; /* Used to iterate through cells */
346 int nUsable
; /* Usable bytes per page */
348 sqlite3BtreeEnter(pBt
);
349 nUsable
= szPage
- sqlite3BtreeGetReserveNoMutex(pBt
);
350 sqlite3BtreeLeave(pBt
);
351 p
->aCell
= sqlite3_malloc64((p
->nCell
+1) * sizeof(StatCell
));
352 if( p
->aCell
==0 ) return SQLITE_NOMEM_BKPT
;
353 memset(p
->aCell
, 0, (p
->nCell
+1) * sizeof(StatCell
));
355 for(i
=0; i
<p
->nCell
; i
++){
356 StatCell
*pCell
= &p
->aCell
[i
];
358 iOff
= get2byte(&aData
[nHdr
+i
*2]);
360 pCell
->iChildPg
= sqlite3Get4byte(&aData
[iOff
]);
363 if( p
->flags
==0x05 ){
364 /* A table interior node. nPayload==0. */
366 u32 nPayload
; /* Bytes of payload total (local+overflow) */
367 int nLocal
; /* Bytes of payload stored locally */
368 iOff
+= getVarint32(&aData
[iOff
], nPayload
);
369 if( p
->flags
==0x0D ){
371 iOff
+= sqlite3GetVarint(&aData
[iOff
], &dummy
);
373 if( nPayload
>(u32
)p
->nMxPayload
) p
->nMxPayload
= nPayload
;
374 getLocalPayload(nUsable
, p
->flags
, nPayload
, &nLocal
);
375 pCell
->nLocal
= nLocal
;
377 assert( nPayload
>=(u32
)nLocal
);
378 assert( nLocal
<=(nUsable
-35) );
379 if( nPayload
>(u32
)nLocal
){
381 int nOvfl
= ((nPayload
- nLocal
) + nUsable
-4 - 1) / (nUsable
- 4);
382 pCell
->nLastOvfl
= (nPayload
-nLocal
) - (nOvfl
-1) * (nUsable
-4);
383 pCell
->nOvfl
= nOvfl
;
384 pCell
->aOvfl
= sqlite3_malloc64(sizeof(u32
)*nOvfl
);
385 if( pCell
->aOvfl
==0 ) return SQLITE_NOMEM_BKPT
;
386 pCell
->aOvfl
[0] = sqlite3Get4byte(&aData
[iOff
+nLocal
]);
387 for(j
=1; j
<nOvfl
; j
++){
389 u32 iPrev
= pCell
->aOvfl
[j
-1];
391 rc
= sqlite3PagerGet(sqlite3BtreePager(pBt
), iPrev
, &pPg
, 0);
396 pCell
->aOvfl
[j
] = sqlite3Get4byte(sqlite3PagerGetData(pPg
));
397 sqlite3PagerUnref(pPg
);
408 ** Populate the pCsr->iOffset and pCsr->szPage member variables. Based on
409 ** the current value of pCsr->iPageno.
411 static void statSizeAndOffset(StatCursor
*pCsr
){
412 StatTable
*pTab
= (StatTable
*)((sqlite3_vtab_cursor
*)pCsr
)->pVtab
;
413 Btree
*pBt
= pTab
->db
->aDb
[pTab
->iDb
].pBt
;
414 Pager
*pPager
= sqlite3BtreePager(pBt
);
418 /* The default page size and offset */
419 pCsr
->szPage
= sqlite3BtreeGetPageSize(pBt
);
420 pCsr
->iOffset
= (i64
)pCsr
->szPage
* (pCsr
->iPageno
- 1);
422 /* If connected to a ZIPVFS backend, override the page size and
423 ** offset with actual values obtained from ZIPVFS.
425 fd
= sqlite3PagerFile(pPager
);
426 x
[0] = pCsr
->iPageno
;
427 if( sqlite3OsFileControl(fd
, 230440, &x
)==SQLITE_OK
){
428 pCsr
->iOffset
= x
[0];
429 pCsr
->szPage
= (int)x
[1];
434 ** Move a statvfs cursor to the next entry in the file.
436 static int statNext(sqlite3_vtab_cursor
*pCursor
){
440 StatCursor
*pCsr
= (StatCursor
*)pCursor
;
441 StatTable
*pTab
= (StatTable
*)pCursor
->pVtab
;
442 Btree
*pBt
= pTab
->db
->aDb
[pCsr
->iDb
].pBt
;
443 Pager
*pPager
= sqlite3BtreePager(pBt
);
445 sqlite3_free(pCsr
->zPath
);
449 if( pCsr
->aPage
[0].pPg
==0 ){
450 rc
= sqlite3_step(pCsr
->pStmt
);
451 if( rc
==SQLITE_ROW
){
453 u32 iRoot
= (u32
)sqlite3_column_int64(pCsr
->pStmt
, 1);
454 sqlite3PagerPagecount(pPager
, &nPage
);
457 return sqlite3_reset(pCsr
->pStmt
);
459 rc
= sqlite3PagerGet(pPager
, iRoot
, &pCsr
->aPage
[0].pPg
, 0);
460 pCsr
->aPage
[0].iPgno
= iRoot
;
461 pCsr
->aPage
[0].iCell
= 0;
462 pCsr
->aPage
[0].zPath
= z
= sqlite3_mprintf("/");
464 if( z
==0 ) rc
= SQLITE_NOMEM_BKPT
;
467 return sqlite3_reset(pCsr
->pStmt
);
471 /* Page p itself has already been visited. */
472 StatPage
*p
= &pCsr
->aPage
[pCsr
->iPage
];
474 while( p
->iCell
<p
->nCell
){
475 StatCell
*pCell
= &p
->aCell
[p
->iCell
];
476 if( pCell
->iOvfl
<pCell
->nOvfl
){
478 sqlite3BtreeEnter(pBt
);
479 nUsable
= sqlite3BtreeGetPageSize(pBt
) -
480 sqlite3BtreeGetReserveNoMutex(pBt
);
481 sqlite3BtreeLeave(pBt
);
482 pCsr
->zName
= (char *)sqlite3_column_text(pCsr
->pStmt
, 0);
483 pCsr
->iPageno
= pCell
->aOvfl
[pCell
->iOvfl
];
484 pCsr
->zPagetype
= "overflow";
486 pCsr
->nMxPayload
= 0;
487 pCsr
->zPath
= z
= sqlite3_mprintf(
488 "%s%.3x+%.6x", p
->zPath
, p
->iCell
, pCell
->iOvfl
490 if( pCell
->iOvfl
<pCell
->nOvfl
-1 ){
492 pCsr
->nPayload
= nUsable
- 4;
494 pCsr
->nPayload
= pCell
->nLastOvfl
;
495 pCsr
->nUnused
= nUsable
- 4 - pCsr
->nPayload
;
498 statSizeAndOffset(pCsr
);
499 return z
==0 ? SQLITE_NOMEM_BKPT
: SQLITE_OK
;
501 if( p
->iRightChildPg
) break;
505 if( !p
->iRightChildPg
|| p
->iCell
>p
->nCell
){
507 if( pCsr
->iPage
==0 ) return statNext(pCursor
);
509 goto statNextRestart
; /* Tail recursion */
512 assert( p
==&pCsr
->aPage
[pCsr
->iPage
-1] );
514 if( p
->iCell
==p
->nCell
){
515 p
[1].iPgno
= p
->iRightChildPg
;
517 p
[1].iPgno
= p
->aCell
[p
->iCell
].iChildPg
;
519 rc
= sqlite3PagerGet(pPager
, p
[1].iPgno
, &p
[1].pPg
, 0);
521 p
[1].zPath
= z
= sqlite3_mprintf("%s%.3x/", p
->zPath
, p
->iCell
);
523 if( z
==0 ) rc
= SQLITE_NOMEM_BKPT
;
527 /* Populate the StatCursor fields with the values to be returned
528 ** by the xColumn() and xRowid() methods.
532 StatPage
*p
= &pCsr
->aPage
[pCsr
->iPage
];
533 pCsr
->zName
= (char *)sqlite3_column_text(pCsr
->pStmt
, 0);
534 pCsr
->iPageno
= p
->iPgno
;
536 rc
= statDecodePage(pBt
, p
);
538 statSizeAndOffset(pCsr
);
541 case 0x05: /* table internal */
542 case 0x02: /* index internal */
543 pCsr
->zPagetype
= "internal";
545 case 0x0D: /* table leaf */
546 case 0x0A: /* index leaf */
547 pCsr
->zPagetype
= "leaf";
550 pCsr
->zPagetype
= "corrupted";
553 pCsr
->nCell
= p
->nCell
;
554 pCsr
->nUnused
= p
->nUnused
;
555 pCsr
->nMxPayload
= p
->nMxPayload
;
556 pCsr
->zPath
= z
= sqlite3_mprintf("%s", p
->zPath
);
557 if( z
==0 ) rc
= SQLITE_NOMEM_BKPT
;
559 for(i
=0; i
<p
->nCell
; i
++){
560 nPayload
+= p
->aCell
[i
].nLocal
;
562 pCsr
->nPayload
= nPayload
;
569 static int statEof(sqlite3_vtab_cursor
*pCursor
){
570 StatCursor
*pCsr
= (StatCursor
*)pCursor
;
574 static int statFilter(
575 sqlite3_vtab_cursor
*pCursor
,
576 int idxNum
, const char *idxStr
,
577 int argc
, sqlite3_value
**argv
579 StatCursor
*pCsr
= (StatCursor
*)pCursor
;
580 StatTable
*pTab
= (StatTable
*)(pCursor
->pVtab
);
586 const char *zDbase
= (const char*)sqlite3_value_text(argv
[0]);
587 pCsr
->iDb
= sqlite3FindDbName(pTab
->db
, zDbase
);
589 sqlite3_free(pCursor
->pVtab
->zErrMsg
);
590 pCursor
->pVtab
->zErrMsg
= sqlite3_mprintf("no such schema: %s", zDbase
);
591 return pCursor
->pVtab
->zErrMsg
? SQLITE_ERROR
: SQLITE_NOMEM_BKPT
;
594 pCsr
->iDb
= pTab
->iDb
;
597 sqlite3_finalize(pCsr
->pStmt
);
599 zMaster
= pCsr
->iDb
==1 ? "sqlite_temp_master" : "sqlite_master";
600 zSql
= sqlite3_mprintf(
601 "SELECT 'sqlite_master' AS name, 1 AS rootpage, 'table' AS type"
603 "SELECT name, rootpage, type"
604 " FROM \"%w\".%s WHERE rootpage!=0"
605 " ORDER BY name", pTab
->db
->aDb
[pCsr
->iDb
].zDbSName
, zMaster
);
607 return SQLITE_NOMEM_BKPT
;
609 rc
= sqlite3_prepare_v2(pTab
->db
, zSql
, -1, &pCsr
->pStmt
, 0);
614 rc
= statNext(pCursor
);
619 static int statColumn(
620 sqlite3_vtab_cursor
*pCursor
,
621 sqlite3_context
*ctx
,
624 StatCursor
*pCsr
= (StatCursor
*)pCursor
;
627 sqlite3_result_text(ctx
, pCsr
->zName
, -1, SQLITE_TRANSIENT
);
630 sqlite3_result_text(ctx
, pCsr
->zPath
, -1, SQLITE_TRANSIENT
);
633 sqlite3_result_int64(ctx
, pCsr
->iPageno
);
635 case 3: /* pagetype */
636 sqlite3_result_text(ctx
, pCsr
->zPagetype
, -1, SQLITE_STATIC
);
639 sqlite3_result_int(ctx
, pCsr
->nCell
);
641 case 5: /* payload */
642 sqlite3_result_int(ctx
, pCsr
->nPayload
);
645 sqlite3_result_int(ctx
, pCsr
->nUnused
);
647 case 7: /* mx_payload */
648 sqlite3_result_int(ctx
, pCsr
->nMxPayload
);
650 case 8: /* pgoffset */
651 sqlite3_result_int64(ctx
, pCsr
->iOffset
);
654 sqlite3_result_int(ctx
, pCsr
->szPage
);
656 default: { /* schema */
657 sqlite3
*db
= sqlite3_context_db_handle(ctx
);
659 sqlite3_result_text(ctx
, db
->aDb
[iDb
].zDbSName
, -1, SQLITE_STATIC
);
666 static int statRowid(sqlite3_vtab_cursor
*pCursor
, sqlite_int64
*pRowid
){
667 StatCursor
*pCsr
= (StatCursor
*)pCursor
;
668 *pRowid
= pCsr
->iPageno
;
673 ** Invoke this routine to register the "dbstat" virtual table module
675 int sqlite3DbstatRegister(sqlite3
*db
){
676 static sqlite3_module dbstat_module
= {
678 statConnect
, /* xCreate */
679 statConnect
, /* xConnect */
680 statBestIndex
, /* xBestIndex */
681 statDisconnect
, /* xDisconnect */
682 statDisconnect
, /* xDestroy */
683 statOpen
, /* xOpen - open a cursor */
684 statClose
, /* xClose - close a cursor */
685 statFilter
, /* xFilter - configure scan constraints */
686 statNext
, /* xNext - advance a cursor */
687 statEof
, /* xEof - check for end of scan */
688 statColumn
, /* xColumn - read data */
689 statRowid
, /* xRowid - read data */
701 return sqlite3_create_module(db
, "dbstat", &dbstat_module
, 0);
703 #elif defined(SQLITE_ENABLE_DBSTAT_VTAB)
704 int sqlite3DbstatRegister(sqlite3
*db
){ return SQLITE_OK
; }
705 #endif /* SQLITE_ENABLE_DBSTAT_VTAB */