297 lines
10 KiB
C
297 lines
10 KiB
C
|
/*
|
||
|
* page-impl.h
|
||
|
*
|
||
|
* Created on: Nov 7, 2011
|
||
|
* Author: sears
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @file
|
||
|
*
|
||
|
* interface for dealing with generic, LSN based pages
|
||
|
*
|
||
|
* This file provides a re-entrant interface for pages that are labeled
|
||
|
* with an LSN and a page type.
|
||
|
*
|
||
|
* @ingroup PAGE_FORMATS
|
||
|
*
|
||
|
* $Id: page.h 1526 2011-06-13 11:26:25Z sears.russell@gmail.com $
|
||
|
*/
|
||
|
|
||
|
#ifndef stasis_page
|
||
|
#define stasis_page_memaddr(p) (p->memAddr)
|
||
|
#define stasis_page(x) stasis_page_##x
|
||
|
#define PAGE Page
|
||
|
#define STASIS_PAGE_IMPL_NEED_UNDEF
|
||
|
#endif
|
||
|
|
||
|
/**
|
||
|
@defgroup PAGE_FORMATS Page layouts
|
||
|
|
||
|
Stasis allows developers to define their own on-disk page formats.
|
||
|
Currently, each page format must end with a hard-coded header
|
||
|
containing an LSN and a page type. (This restriction will be
|
||
|
removed in the future.)
|
||
|
|
||
|
This section explains how new page formats can be implemented in
|
||
|
Stasis, and documents the currently available page types.
|
||
|
|
||
|
A number of callbacks are invoked on existing pages as they are read
|
||
|
from disk, flushed back, and ultimately, evicted from cache:
|
||
|
|
||
|
-# stasis_page_loaded() is invoked when the page is read from disk. It
|
||
|
should set the Page::LSN field appropriately, and
|
||
|
perhaps allocate any data structures that will be stored in the
|
||
|
Page::impl field.
|
||
|
-# stasis_page_flushed() is invoked when a dirty page is written back to
|
||
|
disk. It should make sure that all updates are applied to the
|
||
|
physical representation of the page. (Implementations of this
|
||
|
callback usually copy the Page::LSN field into the page header.)
|
||
|
-# stasis_page_cleanup() is invoked before a page is evicted from cache.
|
||
|
It should free any memory associated with the page, such as
|
||
|
that allocated by stasis_page_loaded(), or pointed to by Page::impl.
|
||
|
|
||
|
When an uninitialized page is read from disk, Stasis has no way of
|
||
|
knowing which stasis_page_loaded() callback should be invoked. Therefore,
|
||
|
when a new page is initialized the page initialization code should
|
||
|
perform the work that would normally be performed by stasis_page_loaded().
|
||
|
Similarly, before a page is freed (and therefore, will be treated as
|
||
|
uninitialized data) stasis_page_cleanup() should be called.
|
||
|
|
||
|
Page implementations are free to define their own access methods
|
||
|
and APIs. However, Stasis's record oriented page interface
|
||
|
provides a default set of methods for page access.
|
||
|
|
||
|
@see PAGE_RECORD_INTERFACE
|
||
|
|
||
|
@todo Page deallocators should call stasis_page_cleanup()
|
||
|
@todo Create variant of loadPage() that takes a page type
|
||
|
@todo Add support for LSN free pages.
|
||
|
|
||
|
*/
|
||
|
/*@{*/
|
||
|
|
||
|
/*@{*/
|
||
|
static inline lsn_t* stasis_page(lsn_ptr)(PAGE *p) {
|
||
|
return ((lsn_t*)(&(stasis_page(memaddr)(p)[PAGE_SIZE])))-1;
|
||
|
}
|
||
|
static inline const lsn_t* stasis_page(lsn_cptr)(const PAGE *p) {
|
||
|
return ((const lsn_t*)(&(stasis_page(memaddr)(p)[PAGE_SIZE])))-1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Returns a pointer to the page's type. This information is stored with the LSN.
|
||
|
Stasis uses it to determine which page implementation should handle each
|
||
|
page.
|
||
|
|
||
|
@param p Any page that contains an LSN header.
|
||
|
@see stasis_page_impl_register
|
||
|
@todo Need to typedef page_type_t
|
||
|
*/
|
||
|
static inline int* stasis_page(type_ptr)(PAGE *p) {
|
||
|
return ((int*)stasis_page(lsn_ptr)(p))-1;
|
||
|
}
|
||
|
static inline const int* stasis_page(type_cptr)(const PAGE *p) {
|
||
|
return ((const int*)stasis_page(lsn_cptr)(p))-1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* assumes that the page is already loaded in memory. It takes as a
|
||
|
* parameter a Page. The Page struct contains the new LSN and the
|
||
|
* page number to which the new LSN must be written to. Furthermore,
|
||
|
* this function updates the dirtyPages table, if necessary. The
|
||
|
* dirtyPages table is needed for log truncation. (If the page->id is
|
||
|
* null, this function assumes the page is not in the buffer pool, and
|
||
|
* does not update dirtyPages. Similarly, if the page is already
|
||
|
* dirty, there is no need to update dirtyPages.
|
||
|
*
|
||
|
* @param xid The transaction that is writing to the page, or -1 if
|
||
|
* outside of a transaction.
|
||
|
*
|
||
|
* @param page You must have a writelock on page before calling this
|
||
|
* function.
|
||
|
*
|
||
|
* @param lsn The new lsn of the page. If the new lsn is less than
|
||
|
* the page's current LSN, then the page's LSN will not be changed.
|
||
|
* If the page is clean, the new LSN must be greater than the old LSN.
|
||
|
*/
|
||
|
void stasis_page(lsn_write)(int xid, PAGE * page, lsn_t lsn);
|
||
|
|
||
|
/**
|
||
|
* assumes that the page is already loaded in memory. It takes
|
||
|
* as a parameter a Page and returns the LSN that is currently written on that
|
||
|
* page in memory.
|
||
|
*/
|
||
|
lsn_t stasis_page(lsn_read)(const PAGE * page);
|
||
|
|
||
|
/*@}*/
|
||
|
|
||
|
/**
|
||
|
@defgroup PAGE_UTIL Byte-level page manipulation
|
||
|
|
||
|
These methods make it easy to manipulate pages that use a standard
|
||
|
Stasis header (one with an LSN and page type).
|
||
|
|
||
|
Each one counts bytes from the beginning or end of the page's
|
||
|
usable space. Methods with "_cptr_" in their names return const
|
||
|
pointers (and can accept const Page pointers as arguments).
|
||
|
Methods with "_ptr_" in their names take non-const pages, and
|
||
|
return non-const pointers.
|
||
|
|
||
|
@par Implementing new pointer arithmetic macros
|
||
|
|
||
|
Stasis page type implementations typically do little more than
|
||
|
pointer arithmetic. However, implementing page types cleanly and
|
||
|
portably is a bit tricky. Stasis has settled upon a compromise in
|
||
|
this matter. Its page file formats are compatible within a single
|
||
|
architecture, but not across systems with varying lengths of
|
||
|
primitive types, or that vary in endianness.
|
||
|
|
||
|
Over time, types that vary in length such as "int", "long", etc
|
||
|
will be removed from Stasis, but their usage still exists in a few
|
||
|
places. Once they have been removed, file compatibility problems
|
||
|
should be limited to endianness (though application code will still
|
||
|
be free to serialize objects in a non-portable manner).
|
||
|
|
||
|
Most page implementations leverage C's pointer manipulation
|
||
|
semantics to lay out pages. Rather than casting pointers to
|
||
|
char*'s and then manually calculating byte offsets using sizeof(),
|
||
|
the existing page types prefer to cast pointers to appropriate
|
||
|
types, and then add or subtract the appropriate number of values.
|
||
|
|
||
|
For example, instead of doing this:
|
||
|
|
||
|
@code
|
||
|
// p points to an int, followed by a two bars, then the foo whose address
|
||
|
// we want to calculate
|
||
|
|
||
|
int * p;
|
||
|
foo* f = (foo*)( ((char*)p) + sizeof(int) + 2 * sizeof(bar))
|
||
|
@endcode
|
||
|
|
||
|
the implementations would do this:
|
||
|
|
||
|
@code
|
||
|
int * p;
|
||
|
foo * f = (foo*)( ((bar*)(p+1)) + 2 )
|
||
|
@endcode
|
||
|
|
||
|
The main disadvantage of this approach is the large number of ()'s
|
||
|
involved. However, it lets the compiler deal with the underlying
|
||
|
multiplications, and often reduces the number of casts, leading to
|
||
|
slightly more readable code. Take this implementation of
|
||
|
stasis_page_type_ptr(), for example:
|
||
|
|
||
|
@code
|
||
|
int * stasis_page_type_ptr(Page *p) {
|
||
|
return ( (int*)stasis_page_lsn_ptr(Page *p) ) - 1;
|
||
|
}
|
||
|
@endcode
|
||
|
|
||
|
Here, the page type is stored as an integer immediately before the
|
||
|
LSN pointer. Using arithmetic over char*'s would require an extra
|
||
|
cast to char*, and a multiplication by sizeof(int).
|
||
|
|
||
|
*/
|
||
|
/*@{*/
|
||
|
static inline byte*
|
||
|
stasis_page(byte_ptr_from_start)(PAGE *p, int count) {
|
||
|
return ((byte*)(stasis_page(memaddr)(p)))+count;
|
||
|
}
|
||
|
static inline byte*
|
||
|
stasis_page(byte_ptr_from_end)(PAGE *p, int count) {
|
||
|
return ((byte*)stasis_page(type_ptr)(p))-count;
|
||
|
}
|
||
|
|
||
|
static inline int16_t*
|
||
|
stasis_page(int16_ptr_from_start)(PAGE *p, int count) {
|
||
|
return ((int16_t*)(stasis_page(memaddr)(p)))+count;
|
||
|
}
|
||
|
|
||
|
static inline int16_t*
|
||
|
stasis_page(int16_ptr_from_end)(PAGE *p, int count) {
|
||
|
return ((int16_t*)stasis_page(type_ptr)(p))-count;
|
||
|
}
|
||
|
static inline int32_t*
|
||
|
stasis_page(int32_ptr_from_start)(PAGE *p, int count) {
|
||
|
return ((int32_t*)(stasis_page(memaddr)(p)))+count;
|
||
|
}
|
||
|
|
||
|
static inline int32_t*
|
||
|
stasis_page(int32_ptr_from_end)(PAGE *p, int count) {
|
||
|
return ((int32_t*)stasis_page(type_ptr)(p))-count;
|
||
|
}
|
||
|
static inline pageid_t*
|
||
|
stasis_page(pageid_t_ptr_from_start)(PAGE *p, int count) {
|
||
|
return ((pageid_t*)(stasis_page(memaddr)(p)))+count;
|
||
|
}
|
||
|
|
||
|
static inline pageid_t*
|
||
|
stasis_page(pageid_t_ptr_from_end)(PAGE *p, int count) {
|
||
|
return ((pageid_t*)stasis_page(type_ptr)(p))-count;
|
||
|
}
|
||
|
// Const methods
|
||
|
static inline const byte*
|
||
|
stasis_page(byte_cptr_from_start)(const PAGE *p, int count) {
|
||
|
return (const byte*)stasis_page(byte_ptr_from_start)((PAGE*)p, count);
|
||
|
}
|
||
|
static inline const byte*
|
||
|
stasis_page(byte_cptr_from_end)(const PAGE *p, int count) {
|
||
|
return (const byte*)stasis_page(byte_ptr_from_end)((PAGE*)p, count);
|
||
|
}
|
||
|
|
||
|
static inline const int16_t*
|
||
|
stasis_page(int16_cptr_from_start)(const PAGE *p, int count) {
|
||
|
return (const int16_t*)stasis_page(int16_ptr_from_start)((PAGE*)p,count);
|
||
|
}
|
||
|
|
||
|
static inline const int16_t*
|
||
|
stasis_page(int16_cptr_from_end)(const PAGE *p, int count) {
|
||
|
return ((int16_t*)stasis_page(type_cptr)(p))-count;
|
||
|
}
|
||
|
static inline const int32_t*
|
||
|
stasis_page(int32_cptr_from_start)(const PAGE *p, int count) {
|
||
|
return ((const int32_t*)(stasis_page(memaddr)(p)))+count;
|
||
|
}
|
||
|
|
||
|
static inline const int32_t*
|
||
|
stasis_page(int32_cptr_from_end)(const PAGE *p, int count) {
|
||
|
return (const int32_t*)stasis_page(int32_ptr_from_end)((PAGE*)p,count);
|
||
|
}
|
||
|
static inline const pageid_t*
|
||
|
stasis_page(pageid_t_cptr_from_start)(const PAGE *p, int count) {
|
||
|
return ((const pageid_t*)(stasis_page(memaddr)(p)))+count;
|
||
|
}
|
||
|
|
||
|
static inline const pageid_t*
|
||
|
stasis_page(pageid_t_cptr_from_end)(const PAGE *p, int count) {
|
||
|
return (const pageid_t*)stasis_page(pageid_t_cptr_from_end)(p,count);
|
||
|
}
|
||
|
/*@}*/
|
||
|
|
||
|
// -------------------- Page specific, general purpose methods
|
||
|
|
||
|
|
||
|
/**
|
||
|
Initialize a new page
|
||
|
|
||
|
@param p The page that will be turned into a new slotted page.
|
||
|
Its contents will be overwritten. It was probably
|
||
|
returned by loadPage()
|
||
|
*/
|
||
|
void stasis_page(slotted_initialize_page)(PAGE * p);
|
||
|
void stasis_page(slotted_latch_free_initialize_page)(PAGE * page);
|
||
|
void stasis_page(slotted_lsn_free_initialize_page)(Page * p);
|
||
|
void stasis_page(indirect_initialize_page)(Page * p, int height);
|
||
|
void stasis_page(blob_initialize_page)(PAGE * p);
|
||
|
|
||
|
#include "fixed-impl.h"
|
||
|
#include "slotted-impl.h"
|
||
|
|
||
|
#ifdef STASIS_PAGE_IMPL_NEED_UNDEF
|
||
|
#undef stasis_page_memaddr
|
||
|
#undef stasis_page
|
||
|
#undef PAGE
|
||
|
#undef STASIS_PAGE_IMPL_NEED_UNDEF
|
||
|
#endif
|