stasis-aries-wal/stasis/page/page-impl.h
Sears Russell 7660467f8e Refactor page implementation to use multiple include trick (so the backing representation can be changed).
Also, fix apparent bug in stasis_page_fixed_set_record_type().

Otherwise, this change should not make any semantic changes.
2011-11-09 21:37:38 +00:00

296 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