virtualdbf.c | virtualdbf.c | |||
---|---|---|---|---|
/* | /* | |||
virtualdbf.c -- SQLite3 extension [VIRTUAL TABLE accessing DBF] | virtualdbf.c -- SQLite3 extension [VIRTUAL TABLE accessing DBF] | |||
version 2.4, 2009 December 12 | version 3.0, 2011 July 20 | |||
Author: Sandro Furieri a.furieri@lqt.it | Author: Sandro Furieri a.furieri@lqt.it | |||
-------------------------------------------------------------------------- --- | -------------------------------------------------------------------------- --- | |||
Version: MPL 1.1/GPL 2.0/LGPL 2.1 | Version: MPL 1.1/GPL 2.0/LGPL 2.1 | |||
The contents of this file are subject to the Mozilla Public License Versio n | The contents of this file are subject to the Mozilla Public License Versio n | |||
1.1 (the "License"); you may not use this file except in compliance with | 1.1 (the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | the License. You may obtain a copy of the License at | |||
skipping to change at line 51 | skipping to change at line 51 | |||
the provisions above, a recipient may use your version of this file under | the provisions above, a recipient may use your version of this file under | |||
the terms of any one of the MPL, the GPL or the LGPL. | the terms of any one of the MPL, the GPL or the LGPL. | |||
*/ | */ | |||
#include <sys/types.h> | #include <sys/types.h> | |||
#include <stdlib.h> | #include <stdlib.h> | |||
#include <stdio.h> | #include <stdio.h> | |||
#include <string.h> | #include <string.h> | |||
#ifdef SPL_AMALGAMATION /* spatialite-amalgamation */ | #ifdef SPL_AMALGAMATION /* spatialite-amalgamation */ | |||
#include <spatialite/sqlite3.h> | #include <spatialite/sqlite3.h> | |||
#else | #else | |||
#include <sqlite3.h> | #include <sqlite3.h> | |||
#endif | #endif | |||
#include <spatialite/spatialite.h> | #include <spatialite/spatialite.h> | |||
#include <spatialite/gaiaaux.h> | #include <spatialite/gaiaaux.h> | |||
#include <spatialite/gaiageo.h> | #include <spatialite/gaiageo.h> | |||
#ifdef _WIN32 | #ifdef _WIN32 | |||
#define strcasecmp _stricmp | #define strcasecmp _stricmp | |||
#endif /* not WIN32 */ | #endif /* not WIN32 */ | |||
#if OMIT_ICONV == 0 /* if ICONV is disabled no DBF support is available */ | #ifndef OMIT_ICONV /* if ICONV is disabled no DBF support is av ailable */ | |||
static struct sqlite3_module my_dbf_module; | static struct sqlite3_module my_dbf_module; | |||
typedef struct VirtualDbfStruct | typedef struct VirtualDbfStruct | |||
{ | { | |||
/* extends the sqlite3_vtab struct */ | /* extends the sqlite3_vtab struct */ | |||
const sqlite3_module *pModule; /* ptr to sqlite module: USED INTERN ALLY BY SQLITE */ | const sqlite3_module *pModule; /* ptr to sqlite module: USED INTERN ALLY BY SQLITE */ | |||
int nRef; /* # references: USED INTERNALLY BY SQLITE * / | int nRef; /* # references: USED INTERNALLY BY SQLITE * / | |||
char *zErrMsg; /* error message: USE INTERNALLY BY SQLITE * / | char *zErrMsg; /* error message: USE INTERNALLY BY SQLITE * / | |||
sqlite3 *db; /* the sqlite db holding the virtual table * / | sqlite3 *db; /* the sqlite db holding the virtual table * / | |||
gaiaDbfPtr dbf; /* the DBF struct */ | gaiaDbfPtr dbf; /* the DBF struct */ | |||
} VirtualDbf; | } VirtualDbf; | |||
typedef VirtualDbf *VirtualDbfPtr; | typedef VirtualDbf *VirtualDbfPtr; | |||
typedef struct VirtualDbfConstraintStruct | ||||
{ | ||||
/* a constraint to be verified for xFilter */ | ||||
int iColumn; /* Column on left-hand side of constraint */ | ||||
int op; /* Constraint operator */ | ||||
char valueType; /* value Type ('I'=int,'D'=double,'T'=text) | ||||
*/ | ||||
sqlite3_int64 intValue; /* Int64 comparison value */ | ||||
double dblValue; /* Double comparison value */ | ||||
char *txtValue; /* Text comparison value */ | ||||
struct VirtualDbfConstraintStruct *next; | ||||
} VirtualDbfConstraint; | ||||
typedef VirtualDbfConstraint *VirtualDbfConstraintPtr; | ||||
typedef struct VirtualDbfCursorStruct | typedef struct VirtualDbfCursorStruct | |||
{ | { | |||
/* extends the sqlite3_vtab_cursor struct */ | /* extends the sqlite3_vtab_cursor struct */ | |||
VirtualDbfPtr pVtab; /* Virtual table of this cursor */ | VirtualDbfPtr pVtab; /* Virtual table of this cursor */ | |||
long current_row; /* the current row ID */ | long current_row; /* the current row ID */ | |||
int eof; /* the EOF marker */ | int eof; /* the EOF marker */ | |||
VirtualDbfConstraintPtr firstConstraint; | ||||
VirtualDbfConstraintPtr lastConstraint; | ||||
} VirtualDbfCursor; | } VirtualDbfCursor; | |||
typedef VirtualDbfCursor *VirtualDbfCursorPtr; | typedef VirtualDbfCursor *VirtualDbfCursorPtr; | |||
static void | static void | |||
vdbf_double_quoted_sql (char *buf) | vdbf_double_quoted_sql (char *buf) | |||
{ | { | |||
/* well-formatting a string to be used as an SQL name */ | /* well-formatting a string to be used as an SQL name */ | |||
char tmp[1024]; | char tmp[1024]; | |||
char *in = tmp; | char *in = tmp; | |||
char *out = buf; | char *out = buf; | |||
strcpy (tmp, buf); | strcpy (tmp, buf); | |||
skipping to change at line 280 | skipping to change at line 296 | |||
sqlite3_vtab ** ppVTab, char **pzErr) | sqlite3_vtab ** ppVTab, char **pzErr) | |||
{ | { | |||
/* connects the virtual table to some DBF - simply aliases vdbf_create() */ | /* connects the virtual table to some DBF - simply aliases vdbf_create() */ | |||
return vdbf_create (db, pAux, argc, argv, ppVTab, pzErr); | return vdbf_create (db, pAux, argc, argv, ppVTab, pzErr); | |||
} | } | |||
static int | static int | |||
vdbf_best_index (sqlite3_vtab * pVTab, sqlite3_index_info * pIndex) | vdbf_best_index (sqlite3_vtab * pVTab, sqlite3_index_info * pIndex) | |||
{ | { | |||
/* best index selection */ | /* best index selection */ | |||
if (pVTab || pIndex) | int i; | |||
int iArg = 0; | ||||
char str[2048]; | ||||
char buf[64]; | ||||
if (pVTab) | ||||
pVTab = pVTab; /* unused arg warning suppression */ | pVTab = pVTab; /* unused arg warning suppression */ | |||
*str = '\0'; | ||||
for (i = 0; i < pIndex->nConstraint; i++) | ||||
{ | ||||
if (pIndex->aConstraint[i].usable) | ||||
{ | ||||
iArg++; | ||||
pIndex->aConstraintUsage[i].argvIndex = iArg; | ||||
pIndex->aConstraintUsage[i].omit = 1; | ||||
sprintf (buf, "%d:%d,", pIndex->aConstraint[i].iColumn, | ||||
pIndex->aConstraint[i].op); | ||||
strcat (str, buf); | ||||
} | ||||
} | ||||
if (*str != '\0') | ||||
{ | ||||
pIndex->idxStr = sqlite3_mprintf ("%s", str); | ||||
pIndex->needToFreeIdxStr = 1; | ||||
} | ||||
return SQLITE_OK; | return SQLITE_OK; | |||
} | } | |||
static int | static int | |||
vdbf_disconnect (sqlite3_vtab * pVTab) | vdbf_disconnect (sqlite3_vtab * pVTab) | |||
{ | { | |||
/* disconnects the virtual table */ | /* disconnects the virtual table */ | |||
VirtualDbfPtr p_vt = (VirtualDbfPtr) pVTab; | VirtualDbfPtr p_vt = (VirtualDbfPtr) pVTab; | |||
if (p_vt->dbf) | if (p_vt->dbf) | |||
gaiaFreeDbf (p_vt->dbf); | gaiaFreeDbf (p_vt->dbf); | |||
skipping to change at line 340 | skipping to change at line 381 | |||
static int | static int | |||
vdbf_open (sqlite3_vtab * pVTab, sqlite3_vtab_cursor ** ppCursor) | vdbf_open (sqlite3_vtab * pVTab, sqlite3_vtab_cursor ** ppCursor) | |||
{ | { | |||
/* opening a new cursor */ | /* opening a new cursor */ | |||
int deleted; | int deleted; | |||
VirtualDbfCursorPtr cursor = | VirtualDbfCursorPtr cursor = | |||
(VirtualDbfCursorPtr) sqlite3_malloc (sizeof (VirtualDbfCursor)); | (VirtualDbfCursorPtr) sqlite3_malloc (sizeof (VirtualDbfCursor)); | |||
if (cursor == NULL) | if (cursor == NULL) | |||
return SQLITE_ERROR; | return SQLITE_ERROR; | |||
cursor->firstConstraint = NULL; | ||||
cursor->lastConstraint = NULL; | ||||
cursor->pVtab = (VirtualDbfPtr) pVTab; | cursor->pVtab = (VirtualDbfPtr) pVTab; | |||
cursor->current_row = 0; | cursor->current_row = 0; | |||
cursor->eof = 0; | cursor->eof = 0; | |||
*ppCursor = (sqlite3_vtab_cursor *) cursor; | *ppCursor = (sqlite3_vtab_cursor *) cursor; | |||
while (1) | while (1) | |||
{ | { | |||
vdbf_read_row (cursor, &deleted); | vdbf_read_row (cursor, &deleted); | |||
if (!deleted) | ||||
break; | ||||
if (cursor->eof) | if (cursor->eof) | |||
break; | break; | |||
if (!deleted) | ||||
break; | ||||
} | } | |||
return SQLITE_OK; | return SQLITE_OK; | |||
} | } | |||
static void | ||||
vdbf_free_constraints (VirtualDbfCursorPtr cursor) | ||||
{ | ||||
/* memory cleanup - cursor constraints */ | ||||
VirtualDbfConstraintPtr pC; | ||||
VirtualDbfConstraintPtr pCn; | ||||
pC = cursor->firstConstraint; | ||||
while (pC) | ||||
{ | ||||
pCn = pC->next; | ||||
if (pC->txtValue) | ||||
sqlite3_free (pC->txtValue); | ||||
sqlite3_free (pC); | ||||
pC = pCn; | ||||
} | ||||
cursor->firstConstraint = NULL; | ||||
cursor->lastConstraint = NULL; | ||||
} | ||||
static int | static int | |||
vdbf_close (sqlite3_vtab_cursor * pCursor) | vdbf_close (sqlite3_vtab_cursor * pCursor) | |||
{ | { | |||
/* closing the cursor */ | /* closing the cursor */ | |||
VirtualDbfCursorPtr cursor = (VirtualDbfCursorPtr) pCursor; | ||||
vdbf_free_constraints (cursor); | ||||
sqlite3_free (pCursor); | sqlite3_free (pCursor); | |||
return SQLITE_OK; | return SQLITE_OK; | |||
} | } | |||
static int | static int | |||
vdbf_parse_constraint (const char *str, int index, int *iColumn, int *op) | ||||
{ | ||||
/* parsing a constraint string */ | ||||
char buf[64]; | ||||
const char *in = str; | ||||
char *out = buf; | ||||
int i = 0; | ||||
int found = 0; | ||||
*out = '\0'; | ||||
while (*in != '\0') | ||||
{ | ||||
if (*in == ',') | ||||
{ | ||||
if (index == i) | ||||
{ | ||||
*out = '\0'; | ||||
found = 1; | ||||
break; | ||||
} | ||||
i++; | ||||
in++; | ||||
continue; | ||||
} | ||||
if (index == i) | ||||
*out++ = *in; | ||||
in++; | ||||
} | ||||
if (!found) | ||||
return 0; | ||||
in = buf; | ||||
for (i = 0; i < (int) strlen (buf); i++) | ||||
{ | ||||
if (buf[i] == ':') | ||||
{ | ||||
buf[i] = '\0'; | ||||
*iColumn = atoi (buf); | ||||
*op = atoi (buf + i + 1); | ||||
return 1; | ||||
} | ||||
in++; | ||||
} | ||||
return 0; | ||||
} | ||||
static int | ||||
vdbf_eval_constraints (VirtualDbfCursorPtr cursor) | ||||
{ | ||||
/* evaluating Filter constraints */ | ||||
int nCol; | ||||
gaiaDbfFieldPtr pFld; | ||||
VirtualDbfConstraintPtr pC = cursor->firstConstraint; | ||||
if (pC == NULL) | ||||
return 1; | ||||
while (pC) | ||||
{ | ||||
int ok = 0; | ||||
if (pC->iColumn == 0) | ||||
{ | ||||
/* the PRIMARY KEY column */ | ||||
if (pC->valueType == 'I') | ||||
{ | ||||
switch (pC->op) | ||||
{ | ||||
case SQLITE_INDEX_CONSTRAINT_EQ: | ||||
if (cursor->current_row == pC->intValue) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_GT: | ||||
if (cursor->current_row > pC->intValue) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_LE: | ||||
if (cursor->current_row <= pC->intValue) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_LT: | ||||
if (cursor->current_row < pC->intValue) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_GE: | ||||
if (cursor->current_row >= pC->intValue) | ||||
ok = 1; | ||||
break; | ||||
}; | ||||
} | ||||
goto done; | ||||
} | ||||
nCol = 1; | ||||
pFld = cursor->pVtab->dbf->Dbf->First; | ||||
while (pFld) | ||||
{ | ||||
if (nCol == pC->iColumn) | ||||
{ | ||||
if ((pFld->Value)) | ||||
{ | ||||
switch (pFld->Value->Type) | ||||
{ | ||||
case GAIA_INT_VALUE: | ||||
if (pC->valueType == 'I') | ||||
{ | ||||
switch (pC->op) | ||||
{ | ||||
case SQLITE_INDEX_CONSTRAINT_EQ: | ||||
if (pFld->Value->IntValue == | ||||
pC->intValue) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_GT: | ||||
if (pFld->Value->IntValue > | ||||
pC->intValue) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_LE: | ||||
if (pFld->Value->IntValue <= | ||||
pC->intValue) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_LT: | ||||
if (pFld->Value->IntValue < | ||||
pC->intValue) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_GE: | ||||
if (pFld->Value->IntValue >= | ||||
pC->intValue) | ||||
ok = 1; | ||||
break; | ||||
}; | ||||
} | ||||
break; | ||||
case GAIA_DOUBLE_VALUE: | ||||
if (pC->valueType == 'I') | ||||
{ | ||||
switch (pC->op) | ||||
{ | ||||
case SQLITE_INDEX_CONSTRAINT_EQ: | ||||
if (pFld->Value->DblValue == | ||||
pC->intValue) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_GT: | ||||
if (pFld->Value->DblValue > | ||||
pC->intValue) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_LE: | ||||
if (pFld->Value->DblValue <= | ||||
pC->intValue) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_LT: | ||||
if (pFld->Value->DblValue < | ||||
pC->intValue) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_GE: | ||||
if (pFld->Value->DblValue >= | ||||
pC->intValue) | ||||
ok = 1; | ||||
break; | ||||
}; | ||||
} | ||||
if (pC->valueType == 'D') | ||||
{ | ||||
switch (pC->op) | ||||
{ | ||||
case SQLITE_INDEX_CONSTRAINT_EQ: | ||||
if (pFld->Value->DblValue == | ||||
pC->dblValue) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_GT: | ||||
if (pFld->Value->DblValue > | ||||
pC->dblValue) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_LE: | ||||
if (pFld->Value->DblValue <= | ||||
pC->dblValue) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_LT: | ||||
if (pFld->Value->DblValue < | ||||
pC->dblValue) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_GE: | ||||
if (pFld->Value->DblValue >= | ||||
pC->dblValue) | ||||
ok = 1; | ||||
break; | ||||
} | ||||
} | ||||
break; | ||||
case GAIA_TEXT_VALUE: | ||||
if (pC->valueType == 'T' && pC->txtValue) | ||||
{ | ||||
int ret; | ||||
ret = | ||||
strcmp (pFld->Value->TxtValue, | ||||
pC->txtValue); | ||||
switch (pC->op) | ||||
{ | ||||
case SQLITE_INDEX_CONSTRAINT_EQ: | ||||
if (ret == 0) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_GT: | ||||
if (ret > 0) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_LE: | ||||
if (ret <= 0) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_LT: | ||||
if (ret < 0) | ||||
ok = 1; | ||||
break; | ||||
case SQLITE_INDEX_CONSTRAINT_GE: | ||||
if (ret >= 0) | ||||
ok = 1; | ||||
break; | ||||
}; | ||||
} | ||||
break; | ||||
}; | ||||
} | ||||
goto done; | ||||
} | ||||
nCol++; | ||||
pFld = pFld->Next; | ||||
} | ||||
done: | ||||
if (!ok) | ||||
return 0; | ||||
pC = pC->next; | ||||
} | ||||
return 1; | ||||
} | ||||
static int | ||||
vdbf_filter (sqlite3_vtab_cursor * pCursor, int idxNum, const char *idxStr, | vdbf_filter (sqlite3_vtab_cursor * pCursor, int idxNum, const char *idxStr, | |||
int argc, sqlite3_value ** argv) | int argc, sqlite3_value ** argv) | |||
{ | { | |||
/* setting up a cursor filter */ | /* setting up a cursor filter */ | |||
if (pCursor || idxNum || idxStr || argc || argv) | int i; | |||
pCursor = pCursor; /* unused arg warning suppression */ | int iColumn; | |||
int op; | ||||
int len; | ||||
int deleted; | ||||
VirtualDbfConstraintPtr pC; | ||||
VirtualDbfCursorPtr cursor = (VirtualDbfCursorPtr) pCursor; | ||||
if (idxNum) | ||||
idxNum = idxNum; /* unused arg warning suppression */ | ||||
/* resetting any previously set filter constraint */ | ||||
vdbf_free_constraints (cursor); | ||||
for (i = 0; i < argc; i++) | ||||
{ | ||||
if (!vdbf_parse_constraint (idxStr, i, &iColumn, &op)) | ||||
continue; | ||||
pC = sqlite3_malloc (sizeof (VirtualDbfConstraint)); | ||||
if (!pC) | ||||
continue; | ||||
pC->iColumn = iColumn; | ||||
pC->op = op; | ||||
pC->valueType = '\0'; | ||||
pC->txtValue = NULL; | ||||
pC->next = NULL; | ||||
if (sqlite3_value_type (argv[i]) == SQLITE_INTEGER) | ||||
{ | ||||
pC->valueType = 'I'; | ||||
pC->intValue = sqlite3_value_int64 (argv[i]); | ||||
} | ||||
if (sqlite3_value_type (argv[i]) == SQLITE_FLOAT) | ||||
{ | ||||
pC->valueType = 'D'; | ||||
pC->dblValue = sqlite3_value_double (argv[i]); | ||||
} | ||||
if (sqlite3_value_type (argv[i]) == SQLITE_TEXT) | ||||
{ | ||||
pC->valueType = 'T'; | ||||
len = sqlite3_value_bytes (argv[i]) + 1; | ||||
pC->txtValue = (char *) sqlite3_malloc (len); | ||||
if (pC->txtValue) | ||||
strcpy (pC->txtValue, | ||||
(char *) sqlite3_value_text (argv[i])); | ||||
} | ||||
if (cursor->firstConstraint == NULL) | ||||
cursor->firstConstraint = pC; | ||||
if (cursor->lastConstraint != NULL) | ||||
cursor->lastConstraint->next = pC; | ||||
cursor->lastConstraint = pC; | ||||
} | ||||
cursor->current_row = 0; | ||||
cursor->eof = 0; | ||||
while (1) | ||||
{ | ||||
vdbf_read_row (cursor, &deleted); | ||||
if (cursor->eof) | ||||
break; | ||||
if (deleted) | ||||
continue; | ||||
if (vdbf_eval_constraints (cursor)) | ||||
break; | ||||
} | ||||
return SQLITE_OK; | return SQLITE_OK; | |||
} | } | |||
static int | static int | |||
vdbf_next (sqlite3_vtab_cursor * pCursor) | vdbf_next (sqlite3_vtab_cursor * pCursor) | |||
{ | { | |||
/* fetching a next row from cursor */ | /* fetching a next row from cursor */ | |||
int deleted; | int deleted; | |||
VirtualDbfCursorPtr cursor = (VirtualDbfCursorPtr) pCursor; | VirtualDbfCursorPtr cursor = (VirtualDbfCursorPtr) pCursor; | |||
while (1) | while (1) | |||
{ | { | |||
vdbf_read_row (cursor, &deleted); | vdbf_read_row (cursor, &deleted); | |||
if (!deleted) | ||||
break; | ||||
if (cursor->eof) | if (cursor->eof) | |||
break; | break; | |||
if (deleted) | ||||
continue; | ||||
if (vdbf_eval_constraints (cursor)) | ||||
break; | ||||
} | } | |||
return SQLITE_OK; | return SQLITE_OK; | |||
} | } | |||
static int | static int | |||
vdbf_eof (sqlite3_vtab_cursor * pCursor) | vdbf_eof (sqlite3_vtab_cursor * pCursor) | |||
{ | { | |||
/* cursor EOF */ | /* cursor EOF */ | |||
VirtualDbfCursorPtr cursor = (VirtualDbfCursorPtr) pCursor; | VirtualDbfCursorPtr cursor = (VirtualDbfCursorPtr) pCursor; | |||
return cursor->eof; | return cursor->eof; | |||
skipping to change at line 539 | skipping to change at line 914 | |||
sqlite3_create_module_v2 (db, "VirtualDbf", &my_dbf_module, NULL, 0); | sqlite3_create_module_v2 (db, "VirtualDbf", &my_dbf_module, NULL, 0); | |||
return rc; | return rc; | |||
} | } | |||
int | int | |||
virtualdbf_extension_init (sqlite3 * db) | virtualdbf_extension_init (sqlite3 * db) | |||
{ | { | |||
return sqlite3VirtualDbfInit (db); | return sqlite3VirtualDbfInit (db); | |||
} | } | |||
#endif /* ICONV enabled/disabled */ | #endif /* ICONV enabled/disabled */ | |||
End of changes. 18 change blocks. | ||||
10 lines changed or deleted | 386 lines changed or added | |||
This html diff was produced by rfcdiff 1.41. The latest version is available from http://tools.ietf.org/tools/rfcdiff/ |