* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Header: /cvsroot/pgsql/src/backend/access/transam/xlog.c,v 1.33 2000/11/21 22:27:26 petere Exp $
+ * $Header: /cvsroot/pgsql/src/backend/access/transam/xlog.c,v 1.34 2000/11/25 20:33:50 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include
#include
#include
+#ifdef USE_LOCALE
+#include
+#endif
#include "access/transam.h"
#include "access/xact.h"
#include "storage/s_lock.h"
#include "access/xlog.h"
#include "access/xlogutils.h"
+#include "utils/builtins.h"
#include "utils/relcache.h"
#include "miscadmin.h"
-char XLogDir[MAXPGPATH];
-char ControlFilePath[MAXPGPATH];
int XLOGbuffers = 8;
XLogRecPtr MyLastRecPtr = {0, 0};
bool StopIfError = false;
/* To generate new xid */
SPINLOCK XidGenLockId;
+static char XLogDir[MAXPGPATH];
+static char ControlFilePath[MAXPGPATH];
+
#define MinXLOGbuffers 4
typedef struct XLgwrRqst
static XLogCtlData *XLogCtl = NULL;
+/*
+ * Contents of pg_control
+ */
+
typedef enum DBState
{
DB_STARTUP = 0,
DB_IN_PRODUCTION
} DBState;
+#define LOCALE_NAME_BUFLEN 128
+
typedef struct ControlFileData
{
+ /*
+ * XLOG state
+ */
uint32 logId; /* current log file id */
uint32 logSeg; /* current log file segment (1-based) */
XLogRecPtr checkPoint; /* last check point record ptr */
time_t time; /* time stamp of last modification */
- DBState state; /* */
+ DBState state; /* see enum above */
/*
* this data is used to make sure that configuration of this DB is
- * compatible with the current backend
+ * compatible with the backend executable
*/
uint32 blcksz; /* block size for this DB */
uint32 relseg_size; /* blocks per segment of large relation */
uint32 catalog_version_no; /* internal version number */
- char archdir[MAXPGPATH]; /* where to move offline log files */
+ /* active locales --- "C" if compiled without USE_LOCALE: */
+ char lc_collate[LOCALE_NAME_BUFLEN];
+ char lc_ctype[LOCALE_NAME_BUFLEN];
/*
- * MORE DATA FOLLOWS AT THE END OF THIS STRUCTURE - locations of data
- * dirs
+ * important directory locations
*/
+ char archdir[MAXPGPATH]; /* where to move offline log files */
} ControlFileData;
static ControlFileData *ControlFile = NULL;
+
typedef struct CheckPoint
{
XLogRecPtr redo; /* next RecPtr available when we */
static int XLogFileInit(uint32 log, uint32 seg, bool *usexistent);
static int XLogFileOpen(uint32 log, uint32 seg, bool econt);
static XLogRecord *ReadRecord(XLogRecPtr *RecPtr, char *buffer);
+static void WriteControlFile(void);
+static void ReadControlFile(void);
static char *str_time(time_t tnow);
static void xlog_outrec(char *buf, XLogRecord *record);
return (record);
}
+/*
+ * I/O routines for pg_control
+ *
+ * *ControlFile is a buffer in shared memory that holds an image of the
+ * contents of pg_control. WriteControlFile() initializes pg_control
+ * given a preloaded buffer, ReadControlFile() loads the buffer from
+ * the pg_control file (during postmaster or standalone-backend startup),
+ * and UpdateControlFile() rewrites pg_control after we modify xlog state.
+ *
+ * For simplicity, WriteControlFile() initializes the fields of pg_control
+ * that are related to checking backend/database compatibility, and
+ * ReadControlFile() verifies they are correct. We could split out the
+ * I/O and compatibility-check functions, but there seems no need currently.
+ */
+
+void
+XLOGPathInit(void)
+{
+ /* Init XLOG file paths */
+ snprintf(XLogDir, MAXPGPATH, "%s/pg_xlog", DataDir);
+ snprintf(ControlFilePath, MAXPGPATH, "%s/global/pg_control", DataDir);
+}
+
+static void
+WriteControlFile(void)
+{
+ int fd;
+ char buffer[BLCKSZ];
+#ifdef USE_LOCALE
+ char *localeptr;
+#endif
+
+ /*
+ * Initialize compatibility-check fields
+ */
+ ControlFile->blcksz = BLCKSZ;
+ ControlFile->relseg_size = RELSEG_SIZE;
+ ControlFile->catalog_version_no = CATALOG_VERSION_NO;
+#ifdef USE_LOCALE
+ localeptr = setlocale(LC_COLLATE, NULL);
+ if (!localeptr)
+ elog(STOP, "Invalid LC_COLLATE setting");
+ StrNCpy(ControlFile->lc_collate, localeptr, LOCALE_NAME_BUFLEN);
+ localeptr = setlocale(LC_CTYPE, NULL);
+ if (!localeptr)
+ elog(STOP, "Invalid LC_CTYPE setting");
+ StrNCpy(ControlFile->lc_ctype, localeptr, LOCALE_NAME_BUFLEN);
+ /*
+ * Issue warning notice if initdb'ing in a locale that will not permit
+ * LIKE index optimization. This is not a clean place to do it, but
+ * I don't see a better place either...
+ */
+ if (!locale_is_like_safe())
+ elog(NOTICE, "Initializing database with %s collation order."
+ "\n\tThis locale setting will prevent use of index optimization for"
+ "\n\tLIKE and regexp searches. If you are concerned about speed of"
+ "\n\tsuch queries, you may wish to set LC_COLLATE to \"C\" and"
+ "\n\tre-initdb. For more information see the Administrator's Guide.",
+ ControlFile->lc_collate);
+#else
+ strcpy(ControlFile->lc_collate, "C");
+ strcpy(ControlFile->lc_ctype, "C");
+#endif
+
+ /*
+ * We write out BLCKSZ bytes into pg_control, zero-padding the
+ * excess over sizeof(ControlFileData). This reduces the odds
+ * of premature-EOF errors when reading pg_control. We'll still
+ * fail when we check the contents of the file, but hopefully with
+ * a more specific error than "couldn't read pg_control".
+ */
+ if (sizeof(ControlFileData) > BLCKSZ)
+ elog(STOP, "sizeof(ControlFileData) is too large ... fix xlog.c");
+ memset(buffer, 0, BLCKSZ);
+ memcpy(buffer, ControlFile, sizeof(ControlFileData));
+
+ fd = BasicOpenFile(ControlFilePath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY, S_IRUSR | S_IWUSR);
+ if (fd < 0)
+ elog(STOP, "WriteControlFile failed to create control file (%s): %m",
+ ControlFilePath);
+
+ if (write(fd, buffer, BLCKSZ) != BLCKSZ)
+ elog(STOP, "WriteControlFile failed to write control file: %m");
+
+ if (fsync(fd) != 0)
+ elog(STOP, "WriteControlFile failed to fsync control file: %m");
+
+ close(fd);
+}
+
+static void
+ReadControlFile(void)
+{
+ int fd;
+
+ /*
+ * Read data...
+ */
+ fd = BasicOpenFile(ControlFilePath, O_RDWR | PG_BINARY, S_IRUSR | S_IWUSR);
+ if (fd < 0)
+ elog(STOP, "open(\"%s\") failed: %m", ControlFilePath);
+
+ if (read(fd, ControlFile, sizeof(ControlFileData)) != sizeof(ControlFileData))
+ elog(STOP, "read(\"%s\") failed: %m", ControlFilePath);
+
+ close(fd);
+
+ /*
+ * Do compatibility checking immediately. We do this here for 2 reasons:
+ *
+ * (1) if the database isn't compatible with the backend executable,
+ * we want to abort before we can possibly do any damage;
+ *
+ * (2) this code is executed in the postmaster, so the setlocale() will
+ * propagate to forked backends, which aren't going to read this file
+ * for themselves. (These locale settings are considered critical
+ * compatibility items because they can affect sort order of indexes.)
+ */
+ if (ControlFile->blcksz != BLCKSZ)
+ elog(STOP, "database was initialized with BLCKSZ %d,\n\tbut the backend was compiled with BLCKSZ %d.\n\tlooks like you need to initdb.",
+ ControlFile->blcksz, BLCKSZ);
+ if (ControlFile->relseg_size != RELSEG_SIZE)
+ elog(STOP, "database was initialized with RELSEG_SIZE %d,\n\tbut the backend was compiled with RELSEG_SIZE %d.\n\tlooks like you need to initdb.",
+ ControlFile->relseg_size, RELSEG_SIZE);
+ if (ControlFile->catalog_version_no != CATALOG_VERSION_NO)
+ elog(STOP, "database was initialized with CATALOG_VERSION_NO %d,\n\tbut the backend was compiled with CATALOG_VERSION_NO %d.\n\tlooks like you need to initdb.",
+ ControlFile->catalog_version_no, CATALOG_VERSION_NO);
+#ifdef USE_LOCALE
+ if (setlocale(LC_COLLATE, ControlFile->lc_collate) == NULL)
+ elog(STOP, "database was initialized with LC_COLLATE '%s',\n\twhich is not recognized by setlocale().\n\tlooks like you need to initdb.",
+ ControlFile->lc_collate);
+ if (setlocale(LC_CTYPE, ControlFile->lc_ctype) == NULL)
+ elog(STOP, "database was initialized with LC_CTYPE '%s',\n\twhich is not recognized by setlocale().\n\tlooks like you need to initdb.",
+ ControlFile->lc_ctype);
+#else
+ if (strcmp(ControlFile->lc_collate, "C") != 0 ||
+ strcmp(ControlFile->lc_ctype, "C") != 0)
+ elog(STOP, "database was initialized with LC_COLLATE '%s' and LC_CTYPE '%s',\n\tbut the backend was compiled without locale support.\n\tlooks like you need to initdb or recompile.",
+ ControlFile->lc_collate, ControlFile->lc_ctype);
+#endif
+}
+
void
-UpdateControlFile()
+UpdateControlFile(void)
{
int fd;
fd = BasicOpenFile(ControlFilePath, O_RDWR | PG_BINARY, S_IRUSR | S_IWUSR);
if (fd < 0)
- elog(STOP, "open(cntlfile) failed: %m");
+ elog(STOP, "open(\"%s\") failed: %m", ControlFilePath);
- if (write(fd, ControlFile, BLCKSZ) != BLCKSZ)
+ if (write(fd, ControlFile, sizeof(ControlFileData)) != sizeof(ControlFileData))
elog(STOP, "write(cntlfile) failed: %m");
if (fsync(fd) != 0)
elog(STOP, "fsync(cntlfile) failed: %m");
close(fd);
-
- return;
}
+/*
+ * Management of shared memory for XLOG
+ */
+
int
XLOGShmemSize(void)
{
XLOGbuffers = MinXLOGbuffers;
return (sizeof(XLogCtlData) + BLCKSZ * XLOGbuffers +
- sizeof(XLogRecPtr) * XLOGbuffers + BLCKSZ);
+ sizeof(XLogRecPtr) * XLOGbuffers +
+ sizeof(ControlFileData));
}
void
{
bool found;
+ /* this must agree with space requested by XLOGShmemSize() */
if (XLOGbuffers < MinXLOGbuffers)
XLOGbuffers = MinXLOGbuffers;
- ControlFile = (ControlFileData *)
- ShmemInitStruct("Control File", BLCKSZ, &found);
- Assert(!found);
XLogCtl = (XLogCtlData *)
ShmemInitStruct("XLOG Ctl", sizeof(XLogCtlData) + BLCKSZ * XLOGbuffers +
sizeof(XLogRecPtr) * XLOGbuffers, &found);
Assert(!found);
+ ControlFile = (ControlFileData *)
+ ShmemInitStruct("Control File", sizeof(ControlFileData), &found);
+ Assert(!found);
+
+ /*
+ * If we are not in bootstrap mode, pg_control should already exist.
+ * Read and validate it immediately (see comments in ReadControlFile()
+ * for the reasons why).
+ */
+ if (!IsBootstrapProcessingMode())
+ ReadControlFile();
}
/*
void
BootStrapXLOG()
{
- int fd;
- char buffer[BLCKSZ];
CheckPoint checkPoint;
-
#ifdef XLOG
+ char buffer[BLCKSZ];
bool usexistent = false;
XLogPageHeader page = (XLogPageHeader) buffer;
XLogRecord *record;
-
#endif
- fd = BasicOpenFile(ControlFilePath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY, S_IRUSR | S_IWUSR);
- if (fd < 0)
- elog(STOP, "BootStrapXLOG failed to create control file (%s): %m",
- ControlFilePath);
-
checkPoint.redo.xlogid = 0;
checkPoint.redo.xrecoff = SizeOfXLogPHD;
checkPoint.undo = checkPoint.redo;
#endif
- memset(ControlFile, 0, BLCKSZ);
+ memset(ControlFile, 0, sizeof(ControlFileData));
ControlFile->logId = 0;
ControlFile->logSeg = 1;
ControlFile->checkPoint = checkPoint.redo;
ControlFile->time = time(NULL);
ControlFile->state = DB_SHUTDOWNED;
- ControlFile->blcksz = BLCKSZ;
- ControlFile->relseg_size = RELSEG_SIZE;
- ControlFile->catalog_version_no = CATALOG_VERSION_NO;
+ /* some additional ControlFile fields are set in WriteControlFile() */
- if (write(fd, ControlFile, BLCKSZ) != BLCKSZ)
- elog(STOP, "BootStrapXLOG failed to write control file: %m");
-
- if (fsync(fd) != 0)
- elog(STOP, "BootStrapXLOG failed to fsync control file: %m");
-
- close(fd);
+ WriteControlFile();
}
static char *
bool sie_saved = false;
#endif
- int fd;
elog(LOG, "starting up");
S_INIT_LOCK(&(XLogCtl->chkp_lck));
/*
- * Open/read Control file
+ * Read control file and check XLOG status looks valid.
+ *
+ * Note: in most control paths, *ControlFile is already valid and we
+ * need not do ReadControlFile() here, but might as well do it to be sure.
*/
- fd = BasicOpenFile(ControlFilePath, O_RDWR | PG_BINARY, S_IRUSR | S_IWUSR);
- if (fd < 0)
- elog(STOP, "open(\"%s\") failed: %m", ControlFilePath);
-
- if (read(fd, ControlFile, BLCKSZ) != BLCKSZ)
- elog(STOP, "read(\"%s\") failed: %m", ControlFilePath);
-
- close(fd);
+ ReadControlFile();
if (ControlFile->logSeg == 0 ||
ControlFile->time <= 0 ||
!XRecOffIsValid(ControlFile->checkPoint.xrecoff))
elog(STOP, "control file context is broken");
- /* Check for incompatible database */
- if (ControlFile->blcksz != BLCKSZ)
- elog(STOP, "database was initialized with BLCKSZ %d,\n\tbut the backend was compiled with BLCKSZ %d.\n\tlooks like you need to initdb.",
- ControlFile->blcksz, BLCKSZ);
- if (ControlFile->relseg_size != RELSEG_SIZE)
- elog(STOP, "database was initialized with RELSEG_SIZE %d,\n\tbut the backend was compiled with RELSEG_SIZE %d.\n\tlooks like you need to initdb.",
- ControlFile->relseg_size, RELSEG_SIZE);
- if (ControlFile->catalog_version_no != CATALOG_VERSION_NO)
- elog(STOP, "database was initialized with CATALOG_VERSION_NO %d,\n\tbut the backend was compiled with CATALOG_VERSION_NO %d.\n\tlooks like you need to initdb.",
- ControlFile->catalog_version_no, CATALOG_VERSION_NO);
-
if (ControlFile->state == DB_SHUTDOWNED)
elog(LOG, "database system was shut down at %s",
str_time(ControlFile->time));
elog(LOG, "database system shutdown was interrupted at %s",
str_time(ControlFile->time));
else if (ControlFile->state == DB_IN_RECOVERY)
- {
elog(LOG, "database system was interrupted being in recovery at %s\n"
"\tThis propably means that some data blocks are corrupted\n"
"\tand you will have to use last backup for recovery.",
str_time(ControlFile->time));
- }
else if (ControlFile->state == DB_IN_PRODUCTION)
elog(LOG, "database system was interrupted at %s",
str_time(ControlFile->time));
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.98 2000/11/16 22:30:24 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.99 2000/11/25 20:33:51 tgl Exp $
*
*-------------------------------------------------------------------------
*/
case OID_BPCHAR_LIKE_OP:
case OID_VARCHAR_LIKE_OP:
case OID_NAME_LIKE_OP:
- /* the right-hand const is type text for all of these */
- patt = DatumGetCString(DirectFunctionCall1(textout,
- constvalue));
- isIndexable = pattern_fixed_prefix(patt, Pattern_Type_Like,
- &prefix, &rest) != Pattern_Prefix_None;
- if (prefix)
- pfree(prefix);
- pfree(patt);
+ if (locale_is_like_safe())
+ {
+ /* the right-hand const is type text for all of these */
+ patt = DatumGetCString(DirectFunctionCall1(textout,
+ constvalue));
+ isIndexable = pattern_fixed_prefix(patt, Pattern_Type_Like,
+ &prefix, &rest) != Pattern_Prefix_None;
+ if (prefix)
+ pfree(prefix);
+ pfree(patt);
+ }
break;
case OID_TEXT_ICLIKE_OP:
case OID_BPCHAR_ICLIKE_OP:
case OID_VARCHAR_ICLIKE_OP:
case OID_NAME_ICLIKE_OP:
- /* the right-hand const is type text for all of these */
- patt = DatumGetCString(DirectFunctionCall1(textout,
- constvalue));
- isIndexable = pattern_fixed_prefix(patt, Pattern_Type_Like_IC,
- &prefix, &rest) != Pattern_Prefix_None;
- if (prefix)
- pfree(prefix);
- pfree(patt);
+ if (locale_is_like_safe())
+ {
+ /* the right-hand const is type text for all of these */
+ patt = DatumGetCString(DirectFunctionCall1(textout,
+ constvalue));
+ isIndexable = pattern_fixed_prefix(patt, Pattern_Type_Like_IC,
+ &prefix, &rest) != Pattern_Prefix_None;
+ if (prefix)
+ pfree(prefix);
+ pfree(patt);
+ }
break;
case OID_TEXT_REGEXEQ_OP:
case OID_BPCHAR_REGEXEQ_OP:
case OID_VARCHAR_REGEXEQ_OP:
case OID_NAME_REGEXEQ_OP:
- /* the right-hand const is type text for all of these */
- patt = DatumGetCString(DirectFunctionCall1(textout,
- constvalue));
- isIndexable = pattern_fixed_prefix(patt, Pattern_Type_Regex,
- &prefix, &rest) != Pattern_Prefix_None;
- if (prefix)
- pfree(prefix);
- pfree(patt);
+ if (locale_is_like_safe())
+ {
+ /* the right-hand const is type text for all of these */
+ patt = DatumGetCString(DirectFunctionCall1(textout,
+ constvalue));
+ isIndexable = pattern_fixed_prefix(patt, Pattern_Type_Regex,
+ &prefix, &rest) != Pattern_Prefix_None;
+ if (prefix)
+ pfree(prefix);
+ pfree(patt);
+ }
break;
case OID_TEXT_ICREGEXEQ_OP:
case OID_BPCHAR_ICREGEXEQ_OP:
case OID_VARCHAR_ICREGEXEQ_OP:
case OID_NAME_ICREGEXEQ_OP:
- /* the right-hand const is type text for all of these */
- patt = DatumGetCString(DirectFunctionCall1(textout,
- constvalue));
- isIndexable = pattern_fixed_prefix(patt, Pattern_Type_Regex_IC,
- &prefix, &rest) != Pattern_Prefix_None;
- if (prefix)
- pfree(prefix);
- pfree(patt);
+ if (locale_is_like_safe())
+ {
+ /* the right-hand const is type text for all of these */
+ patt = DatumGetCString(DirectFunctionCall1(textout,
+ constvalue));
+ isIndexable = pattern_fixed_prefix(patt, Pattern_Type_Regex_IC,
+ &prefix, &rest) != Pattern_Prefix_None;
+ if (prefix)
+ pfree(prefix);
+ pfree(patt);
+ }
break;
}
result = makeList1(expr);
/*
- * If we can create a string larger than the prefix, say "x <
- * greaterstr".
+ * If we can create a string larger than the prefix, we can say
+ * "x < greaterstr".
*/
greaterstr = make_greater_string(prefix, datatype);
if (greaterstr)
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/utils/adt/selfuncs.c,v 1.82 2000/11/16 22:30:31 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/adt/selfuncs.c,v 1.83 2000/11/25 20:33:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include
#include
+#ifdef USE_LOCALE
+#include
+#endif
#include "access/heapam.h"
#include "catalog/catname.h"
*
* A fixed prefix "foo" is estimated as the selectivity of the expression
* "var >= 'foo' AND var < 'fop'" (see also indxqual.c).
+ *
+ * XXX Note: we make use of the upper bound to estimate operator selectivity
+ * even if the locale is such that we cannot rely on the upper-bound string.
+ * The selectivity only needs to be approximately right anyway, so it seems
+ * more useful to use the upper-bound code than not.
*/
static Selectivity
prefix_selectivity(char *prefix,
return result;
}
+/*
+ * Test whether the database's LOCALE setting is safe for LIKE/regexp index
+ * optimization. The key requirement here is that given a prefix string,
+ * say "foo", we must be able to generate another string "fop" that is
+ * greater than all strings "foobar" starting with "foo". Unfortunately,
+ * many non-C locales have bizarre collation rules in which "fop" > "foo"
+ * is not sufficient to ensure "fop" > "foobar". Until we can come up
+ * with a more bulletproof way of generating the upper-bound string,
+ * disable the optimization in locales where it is not known to be safe.
+ */
+bool
+locale_is_like_safe(void)
+{
+#ifdef USE_LOCALE
+ /* Cache result so we only have to compute it once */
+ static int result = -1;
+ char *localeptr;
+
+ if (result >= 0)
+ return (bool) result;
+ localeptr = setlocale(LC_COLLATE, NULL);
+ if (!localeptr)
+ elog(STOP, "Invalid LC_COLLATE setting");
+ /*
+ * Currently we accept only "C" and "POSIX" (do any systems still
+ * return "POSIX"?). Which other locales allow safe optimization?
+ */
+ if (strcmp(localeptr, "C") == 0)
+ result = true;
+ else if (strcmp(localeptr, "POSIX") == 0)
+ result = true;
+ else
+ result = false;
+ return (bool) result;
+#else /* not USE_LOCALE */
+ return true; /* We must be in C locale, which is OK */
+#endif /* USE_LOCALE */
+}
/*
* Try to generate a string greater than the given string or any string it is
* This could be rather slow in the worst case, but in most cases we won't
* have to try more than one or two strings before succeeding.
*
- * XXX in a sufficiently weird locale, this might produce incorrect results?
- * For example, in German I believe "ss" is treated specially --- if we are
- * given "foos" and return "foot", will this actually be greater than "fooss"?
+ * XXX this is actually not sufficient, since it only copes with the case
+ * where individual characters collate in an order different from their
+ * numeric code assignments. It does not handle cases where there are
+ * cross-character effects, such as specially sorted digraphs, multiple
+ * sort passes, etc. For now, we just shut down the whole thing in locales
+ * that do such things :-(
*/
char *
make_greater_string(const char *str, Oid datatype)