DELETED ajax/README
Index: ajax/README
==================================================================
--- ajax/README
+++ /dev/null
@@ -1,38 +0,0 @@
-This is the README for how to set up the Fossil/JSON test web page
-under Apache on Unix systems. This is only intended only for
-Fossil/JSON developers/tinkerers:
-
-First, copy cgi-bin/fossil-json.cgi.example to
-cgi-bin/fossil-json.cgi. Edit it and correct the paths to the fossil
-binary and the repo you want to serve. Make it executable.
-
-MAKE SURE that the fossil repo you use is world-writable OR that your
-Web/CGI server is set up to run as the user ID of the owner of the
-fossil file. ALSO: the DIRECTORY CONTAINING the repo file must be
-writable by the CGI process.
-
-Next, set up an apache vhost entry. Mine looks like:
-
-
POST data | -Request AJAJ options | -
---|---|
- - - | -- - - | -
Response | -|
- - | -
Page List | -Content | -
---|---|
- - - |
-
- - - |
-
Response | -|
- - | -
- It is allowed, not required. It is allowed so that JSON can be - safely embedded in HTML, which can freak out when seeing - strings containing "". JSON tolerates "<\/" for this reason. -- - (from an email on 2011-04-08) - - The default value is 0 (because it's just damned ugly). - */ - char escapeForwardSlashes; -}; -typedef struct cson_output_opt cson_output_opt; - -/** - Empty-initialized cson_output_opt object. -*/ -#define cson_output_opt_empty_m { 0/*indentation*/,\ - 25/*maxDepth*/, \ - 0/*addNewline*/, \ - 0/*addSpaceAfterColon*/, \ - 0/*indentSingleMemberValues*/, \ - 0/*escapeForwardSlashes*/ \ - } - -/** - Empty-initialized cson_output_opt object. -*/ -extern const cson_output_opt cson_output_opt_empty; - -/** - Typedef for functions which act as an input source for - the cson JSON parser. - - The arguments are: - - - state: implementation-specific state needed by the function. - - - n: when called, *n will be the number of bytes the function - should read and copy to dest. The function MUST NOT copy more than - *n bytes to dest. Before returning, *n must be set to the number of - bytes actually copied to dest. If that number is smaller than the - original *n value, the input is assumed to be completed (thus this - is not useful with non-blocking readers). - - - dest: the destination memory to copy the data do. - - Must return 0 on success, non-0 on error (preferably a value from - cson_rc). - - The parser allows this routine to return a partial character from a - UTF multi-byte character. The input routine does not need to - concern itself with character boundaries. -*/ -typedef int (*cson_data_source_f)( void * state, void * dest, unsigned int * n ); - -/** - Typedef for functions which act as an output destination for - generated JSON. - - The arguments are: - - - state: implementation-specific state needed by the function. - - - n: the length, in bytes, of src. - - - src: the source bytes which the output function should consume. - The src pointer will be invalidated shortly after this function - returns, so the implementation must copy or ignore the data, but not - hold a copy of the src pointer. - - Must return 0 on success, non-0 on error (preferably a value from - cson_rc). - - These functions are called relatively often during the JSON-output - process, and should try to be fast. -*/ -typedef int (*cson_data_dest_f)( void * state, void const * src, unsigned int n ); - -/** - Reads JSON-formatted string data (in ASCII, UTF8, or UTF16), using the - src function to fetch all input. This function fetches each input character - from the source function, which is calls like src(srcState, buffer, bufferSize), - and processes them. If anything is not JSON-kosher then this function - fails and returns one of the non-0 cson_rc codes. - - This function is only intended to read root nodes of a JSON tree, either - a single object or a single array, containing any number of child elements. - - On success, *tgt is assigned the value of the root node of the - JSON input, and the caller takes over ownership of that memory. - On error, *tgt is not modified and the caller need not do any - special cleanup, except possibly for the input source. - - - The opt argument may point to an initialized cson_parse_opt object - which contains any settings the caller wants. If it is NULL then - default settings (the values defined in cson_parse_opt_empty) are - used. - - The info argument may be NULL. If it is not NULL then the parser - populates it with information which is useful in error - reporting. Namely, it contains the line/column of parse errors. - - The srcState argument is ignored by this function but is passed on to src, - so any output-destination-specific state can be stored there and accessed - via the src callback. - - Non-parse error conditions include: - - - (!tgt) or !src: cson_rc.ArgError - - cson_rc.AllocError can happen at any time during the input phase - - Here's a complete example of using a custom input source: - - @code - // Internal type to hold state for a JSON input string. - typedef struct - { - char const * str; // start of input string - char const * pos; // current internal cursor position - char const * end; // logical EOF (one-past-the-end) - } StringSource; - - // cson_data_source_f() impl which uses StringSource. - static int cson_data_source_StringSource( void * state, void * dest, - unsigned int * n ) - { - StringSource * ss = (StringSource*) state; - unsigned int i; - unsigned char * tgt = (unsigned char *)dest; - if( ! ss || ! n || !dest ) return cson_rc.ArgError; - else if( !*n ) return cson_rc.RangeError; - for( i = 0; - (i < *n) && (ss->pos < ss->end); - ++i, ++ss->pos, ++tgt ) - { - *tgt = *ss->pos; - } - *n = i; - return 0; - } - - ... - // Now use StringSource together with cson_parse() - StringSource ss; - cson_value * root = NULL; - char const * json = "{\"k1\":123}"; - ss.str = ss.pos = json; - ss.end = json + strlen(json); - int rc = cson_parse( &root, cson_data_source_StringSource, &ss, NULL, NULL ); - @endcode - - It is recommended that clients wrap such utility code into - type-safe wrapper functions which also initialize the internal - state object and check the user-provided parameters for legality - before passing them on to cson_parse(). For examples of this, see - cson_parse_FILE() or cson_parse_string(). - - TODOs: - - - Buffer the input in larger chunks. We currently read - byte-by-byte, but i'm too tired to write/test the looping code for - the buffering. - - @see cson_parse_FILE() - @see cson_parse_string() -*/ -int cson_parse( cson_value ** tgt, cson_data_source_f src, void * srcState, - cson_parse_opt const * opt, cson_parse_info * info ); -/** - A cson_data_source_f() implementation which requires the state argument - to be a readable (FILE*) handle. -*/ -int cson_data_source_FILE( void * state, void * dest, unsigned int * n ); - -/** - Equivalent to cson_parse( tgt, cson_data_source_FILE, src, opt ). - - @see cson_parse_filename() -*/ -int cson_parse_FILE( cson_value ** tgt, FILE * src, - cson_parse_opt const * opt, cson_parse_info * info ); - -/** - Convenience wrapper around cson_parse_FILE() which opens the given filename. - - Returns cson_rc.IOError if the file cannot be opened. - - @see cson_parse_FILE() -*/ -int cson_parse_filename( cson_value ** tgt, char const * src, - cson_parse_opt const * opt, cson_parse_info * info ); - -/** - Uses an internal helper class to pass src through cson_parse(). - See that function for the return value and argument semantics. - - src must be a string containing JSON code, at least len bytes long, - and the parser will attempt to parse exactly len bytes from src. - - If len is less than 2 (the minimum length of a legal top-node JSON - object) then cson_rc.RangeError is returned. -*/ -int cson_parse_string( cson_value ** tgt, char const * src, unsigned int len, - cson_parse_opt const * opt, cson_parse_info * info ); - - - -/** - Outputs the given value as a JSON-formatted string, sending all - output to the given callback function. It is intended for top-level - objects or arrays, but can be used with any cson_value. - - If opt is NULL then default options (the values defined in - cson_output_opt_empty) are used. - - If opt->maxDepth is exceeded while traversing the value tree, - cson_rc.RangeError is returned. - - The destState parameter is ignored by this function and is passed - on to the dest function. - - Returns 0 on success. On error, any amount of output might have been - generated before the error was triggered. - - Example: - - @code - int rc = cson_output( myValue, cson_data_dest_FILE, stdout, NULL ); - // basically equivalent to: cson_output_FILE( myValue, stdout, NULL ); - // but note that cson_output_FILE() actually uses different defaults - // for the output options. - @endcode -*/ -int cson_output( cson_value const * src, cson_data_dest_f dest, void * destState, cson_output_opt const * opt ); - - -/** - A cson_data_dest_f() implementation which requires the state argument - to be a writable (FILE*) handle. -*/ -int cson_data_dest_FILE( void * state, void const * src, unsigned int n ); - -/** - Almost equivalent to cson_output( src, cson_data_dest_FILE, dest, opt ), - with one minor difference: if opt is NULL then the default options - always include the addNewline option, since that is normally desired - for FILE output. - - @see cson_output_filename() -*/ -int cson_output_FILE( cson_value const * src, FILE * dest, cson_output_opt const * opt ); -/** - Convenience wrapper around cson_output_FILE() which writes to the given filename, destroying - any existing contents. Returns cson_rc.IOError if the file cannot be opened. - - @see cson_output_FILE() -*/ -int cson_output_filename( cson_value const * src, char const * dest, cson_output_opt const * fmt ); - -/** Returns true if v is null, v->api is NULL, or v holds the special undefined value. */ -char cson_value_is_undef( cson_value const * v ); -/** Returns true if v contains a null value. */ -char cson_value_is_null( cson_value const * v ); -/** Returns true if v contains a bool value. */ -char cson_value_is_bool( cson_value const * v ); -/** Returns true if v contains an integer value. */ -char cson_value_is_integer( cson_value const * v ); -/** Returns true if v contains a double value. */ -char cson_value_is_double( cson_value const * v ); -/** Returns true if v contains a number (double, integer) value. */ -char cson_value_is_number( cson_value const * v ); -/** Returns true if v contains a string value. */ -char cson_value_is_string( cson_value const * v ); -/** Returns true if v contains an array value. */ -char cson_value_is_array( cson_value const * v ); -/** Returns true if v contains an object value. */ -char cson_value_is_object( cson_value const * v ); - -/** @struct cson_object - - cson_object is an opaque handle to an Object value. - - They are used like: - - @code - cson_object * obj = cson_value_get_object(myValue); - ... - @endcode - - They can be created like: - - @code - cson_value * objV = cson_value_new_object(); - cson_object * obj = cson_value_get_object(objV); - // obj is owned by objV and objV must eventually be freed - // using cson_value_free() or added to a container - // object/array (which transfers ownership to that container). - @endcode - - @see cson_value_new_object() - @see cson_value_get_object() - @see cson_value_free() -*/ - -typedef struct cson_object cson_object; - -/** @struct cson_array - - cson_array is an opaque handle to an Array value. - - They are used like: - - @code - cson_array * obj = cson_value_get_array(myValue); - ... - @endcode - - They can be created like: - - @code - cson_value * arV = cson_value_new_array(); - cson_array * ar = cson_value_get_array(arV); - // ar is owned by arV and arV must eventually be freed - // using cson_value_free() or added to a container - // object/array (which transfers ownership to that container). - @endcode - - @see cson_value_new_array() - @see cson_value_get_array() - @see cson_value_free() - -*/ -typedef struct cson_array cson_array; - -/** @struct cson_string - - cson-internal string type, opaque to client code. Strings in cson - are immutable and allocated only by library internals, never - directly by client code. - - The actual string bytes are to be allocated together in the same - memory chunk as the cson_string object, which saves us 1 malloc() - and 1 pointer member in this type (because we no longer have a - direct pointer to the memory). - - Potential TODOs: - - @see cson_string_cstr() -*/ -typedef struct cson_string cson_string; - -/** - Converts the given value to a boolean, using JavaScript semantics depending - on the concrete type of val: - - undef or null: false - - boolean: same - - integer, double: 0 or 0.0 == false, else true - - object, array: true - - Returns 0 on success and assigns *v (if v is not NULL) to either 0 or 1. - On error (val is NULL) then v is not modified. -*/ -int cson_value_fetch_bool( cson_value const * val, char * v ); -/** - Similar to cson_value_fetch_bool(), but fetches an integer value. - - The conversion, if any, depends on the concrete type of val: - - NULL, null, undefined: *v is set to 0 and 0 is returned. - - string, object, array: *v is set to 0 and - cson_rc.TypeError is returned. The error may normally be safely - ignored, but it is provided for those wanted to know whether a direct - conversion was possible. - - integer: *v is set to the int value and 0 is returned. - - double: *v is set to the value truncated to int and 0 is returned. -*/ -int cson_value_fetch_integer( cson_value const * val, cson_int_t * v ); -/** - The same conversions and return values as - cson_value_fetch_integer(), except that the roles of int/double are - swapped. -*/ -int cson_value_fetch_double( cson_value const * val, cson_double_t * v ); - -/** - If cson_value_is_string(val) then this function assigns *str to the - contents of the string. str may be NULL, in which case this function - functions like cson_value_is_string() but returns 0 on success. - - Returns 0 if val is-a string, else non-0, in which case *str is not - modified. - - The bytes are owned by the given value and may be invalidated in any of - the following ways: - - - The value is cleaned up or freed. - - - An array or object containing the value peforms a re-allocation - (it shrinks or grows). - - And thus the bytes should be consumed before any further operations - on val or any container which holds it. - - Note that this routine does not convert non-String values to their - string representations. (Adding that ability would add more - overhead to every cson_value instance.) -*/ -int cson_value_fetch_string( cson_value const * val, cson_string const ** str ); - -/** - If cson_value_is_object(val) then this function assigns *obj to the underlying - object value and returns 0, otherwise non-0 is returned and *obj is not modified. - - obj may be NULL, in which case this function works like cson_value_is_object() - but with inverse return value semantics (0==success) (and it's a few - CPU cycles slower). - - The *obj pointer is owned by val, and will be invalidated when val - is cleaned up. - - Achtung: for best results, ALWAYS pass a pointer to NULL as the - second argument, e.g.: - - @code - cson_object * obj = NULL; - int rc = cson_value_fetch_object( val, &obj ); - - // Or, more simply: - obj = cson_value_get_object( val ); - @endcode - - @see cson_value_get_object() -*/ -int cson_value_fetch_object( cson_value const * val, cson_object ** obj ); - -/** - Identical to cson_value_fetch_object(), but works on array values. - - @see cson_value_get_array() -*/ -int cson_value_fetch_array( cson_value const * val, cson_array ** tgt ); - -/** - Simplified form of cson_value_fetch_bool(). Returns 0 if val - is NULL. -*/ -char cson_value_get_bool( cson_value const * val ); - -/** - Simplified form of cson_value_fetch_integer(). Returns 0 if val - is NULL. -*/ -cson_int_t cson_value_get_integer( cson_value const * val ); - -/** - Simplified form of cson_value_fetch_double(). Returns 0.0 if val - is NULL. -*/ -cson_double_t cson_value_get_double( cson_value const * val ); - -/** - Simplified form of cson_value_fetch_string(). Returns NULL if val - is-not-a string value. -*/ -cson_string const * cson_value_get_string( cson_value const * val ); - -/** - Returns a pointer to the NULL-terminated string bytes of str. - The bytes are owned by string and will be invalided when it - is cleaned up. - - If str is NULL then NULL is returned. - - @see cson_string_length_bytes() - @see cson_value_get_string() -*/ -char const * cson_string_cstr( cson_string const * str ); - -/** - Convenience function which returns the string bytes of - the given value if it is-a string, otherwise it returns - NULL. Note that this does no conversion of non-string types - to strings. - - Equivalent to cson_string_cstr(cson_value_get_string(val)). -*/ -char const * cson_value_get_cstr( cson_value const * val ); - -/** - Equivalent to cson_string_cmp_cstr_n(lhs, cson_string_cstr(rhs), cson_string_length_bytes(rhs)). -*/ -int cson_string_cmp( cson_string const * lhs, cson_string const * rhs ); - -/** - Compares lhs to rhs using memcmp()/strcmp() semantics. Generically - speaking it returns a negative number if lhs is less-than rhs, 0 if - they are equivalent, or a positive number if lhs is greater-than - rhs. It has the following rules for equivalence: - - - The maximum number of bytes compared is the lesser of rhsLen and - the length of lhs. If the strings do not match, but compare equal - up to the just-described comparison length, the shorter string is - considered to be less-than the longer one. - - - If lhs and rhs are both NULL, or both have a length of 0 then they will - compare equal. - - - If lhs is null/length-0 but rhs is not then lhs is considered to be less-than - rhs. - - - If rhs is null/length-0 but lhs is not then rhs is considered to be less-than - rhs. - - - i have no clue if the results are exactly correct for UTF strings. - -*/ -int cson_string_cmp_cstr_n( cson_string const * lhs, char const * rhs, unsigned int rhsLen ); - -/** - Equivalent to cson_string_cmp_cstr_n( lhs, rhs, (rhs&&*rhs)?strlen(rhs):0 ). -*/ -int cson_string_cmp_cstr( cson_string const * lhs, char const * rhs ); - -/** - Returns the length, in bytes, of str, or 0 if str is NULL. This is - an O(1) operation. - - TODO: add cson_string_length_chars() (is O(N) unless we add another - member to store the char length). - - @see cson_string_cstr() -*/ -unsigned int cson_string_length_bytes( cson_string const * str ); - -/** - Returns the number of UTF8 characters in str. This value will - be at most as long as cson_string_length_bytes() for the - same string, and less if it has multi-byte characters. - - Returns 0 if str is NULL. -*/ -unsigned int cson_string_length_utf8( cson_string const * str ); - -/** - Like cson_value_get_string(), but returns a copy of the underying - string bytes, which the caller owns and must eventually free - using free(). -*/ -char * cson_value_get_string_copy( cson_value const * val ); - -/** - Simplified form of cson_value_fetch_object(). Returns NULL if val - is-not-a object value. -*/ -cson_object * cson_value_get_object( cson_value const * val ); - -/** - Simplified form of cson_value_fetch_array(). Returns NULL if val - is-not-a array value. -*/ -cson_array * cson_value_get_array( cson_value const * val ); - -/** - Const-correct form of cson_value_get_array(). -*/ -cson_array const * cson_value_get_array_c( cson_value const * val ); - -/** - If ar is-a array and is at least (pos+1) entries long then *v (if v is not NULL) - is assigned to the value at that position (which may be NULL). - - Ownership of the *v return value is unchanged by this call. (The - containing array may share ownership of the value with other - containers.) - - If pos is out of range, non-0 is returned and *v is not modified. - - If v is NULL then this function returns 0 if pos is in bounds, but does not - otherwise return a value to the caller. -*/ -int cson_array_value_fetch( cson_array const * ar, unsigned int pos, cson_value ** v ); - -/** - Simplified form of cson_array_value_fetch() which returns NULL if - ar is NULL, pos is out of bounds or if ar has no element at that - position. -*/ -cson_value * cson_array_get( cson_array const * ar, unsigned int pos ); - -/** - Ensures that ar has allocated space for at least the given - number of entries. This never shrinks the array and never - changes its logical size, but may pre-allocate space in the - array for storing new (as-yet-unassigned) values. - - Returns 0 on success, or non-zero on error: - - - If ar is NULL: cson_rc.ArgError - - - If allocation fails: cson_rc.AllocError -*/ -int cson_array_reserve( cson_array * ar, unsigned int size ); - -/** - If ar is not NULL, sets *v (if v is not NULL) to the length of the array - and returns 0. Returns cson_rc.ArgError if ar is NULL. -*/ -int cson_array_length_fetch( cson_array const * ar, unsigned int * v ); - -/** - Simplified form of cson_array_length_fetch() which returns 0 if ar - is NULL. -*/ -unsigned int cson_array_length_get( cson_array const * ar ); - -/** - Sets the given index of the given array to the given value. - - If ar already has an item at that index then it is cleaned up and - freed before inserting the new item. - - ar is expanded, if needed, to be able to hold at least (ndx+1) - items, and any new entries created by that expansion are empty - (NULL values). - - On success, 0 is returned and ownership of v is transfered to ar. - - On error ownership of v is NOT modified, and the caller may still - need to clean it up. For example, the following code will introduce - a leak if this function fails: - - @code - cson_array_append( myArray, cson_value_new_integer(42) ); - @endcode - - Because the value created by cson_value_new_integer() has no owner - and is not cleaned up. The "more correct" way to do this is: - - @code - cson_value * v = cson_value_new_integer(42); - int rc = cson_array_append( myArray, v ); - if( 0 != rc ) { - cson_value_free( v ); - ... handle error ... - } - @endcode - -*/ -int cson_array_set( cson_array * ar, unsigned int ndx, cson_value * v ); - -/** - Appends the given value to the given array, transfering ownership of - v to ar. On error, ownership of v is not modified. Ownership of ar - is never changed by this function. - - This is functionally equivalent to - cson_array_set(ar,cson_array_length_get(ar),v), but this - implementation has slightly different array-preallocation policy - (it grows more eagerly). - - Returns 0 on success, non-zero on error. Error cases include: - - - ar or v are NULL: cson_rc.ArgError - - - Array cannot be expanded to hold enough elements: cson_rc.AllocError. - - - Appending would cause a numeric overlow in the array's size: - cson_rc.RangeError. (However, you'll get an AllocError long before - that happens!) - - On error ownership of v is NOT modified, and the caller may still - need to clean it up. See cson_array_set() for the details. - -*/ -int cson_array_append( cson_array * ar, cson_value * v ); - - -/** - Creates a new cson_value from the given boolean value. - - Ownership of the new value is passed to the caller, who must - eventually either free the value using cson_value_free() or - inserting it into a container (array or object), which transfers - ownership to the container. See the cson_value class documentation - for more details. - - Returns NULL on allocation error. -*/ -cson_value * cson_value_new_bool( char v ); - - -/** - Returns the special JSON "null" value. When outputing JSON, - its string representation is "null" (without the quotes). - - See cson_value_new_bool() for notes regarding the returned - value's memory. -*/ -cson_value * cson_value_null(); - -/** - Equivalent to cson_value_new_bool(1). -*/ -cson_value * cson_value_true(); - -/** - Equivalent to cson_value_new_bool(0). -*/ -cson_value * cson_value_false(); - -/** - Semantically the same as cson_value_new_bool(), but for integers. -*/ -cson_value * cson_value_new_integer( cson_int_t v ); - -/** - Semantically the same as cson_value_new_bool(), but for doubles. -*/ -cson_value * cson_value_new_double( cson_double_t v ); - -/** - Semantically the same as cson_value_new_bool(), but for strings. - This creates a JSON value which copies the first n bytes of str. - The string will automatically be NUL-terminated. - - Note that if str is NULL or n is 0, this function still - returns non-NULL value representing that empty string. - - Returns NULL on allocation error. - - See cson_value_new_bool() for important information about the - returned memory. -*/ -cson_value * cson_value_new_string( char const * str, unsigned int n ); - -/** - Allocates a new "object" value and transfers ownership of it to the - caller. It must eventually be destroyed, by the caller or its - owning container, by passing it to cson_value_free(). - - Returns NULL on allocation error. - - Post-conditions: cson_value_is_object(value) will return true. - - @see cson_value_new_array() - @see cson_value_free() -*/ -cson_value * cson_value_new_object(); - -/** - Allocates a new "array" value and transfers ownership of it to the - caller. It must eventually be destroyed, by the caller or its - owning container, by passing it to cson_value_free(). - - Returns NULL on allocation error. - - Post-conditions: cson_value_is_array(value) will return true. - - @see cson_value_new_object() - @see cson_value_free() -*/ -cson_value * cson_value_new_array(); - -/** - Frees any resources owned by v, then frees v. If v is a container - type (object or array) its children are also freed (recursively). - - If v is NULL, this is a no-op. - - This function decrements a reference count and only destroys the - value if its reference count drops to 0. Reference counts are - increased by either inserting the value into a container or via - cson_value_add_reference(). Even if this function does not - immediately destroy the value, the value must be considered, from - the perspective of that client code, to have been - destroyed/invalidated by this call. - - - @see cson_value_new_object() - @see cson_value_new_array() - @see cson_value_add_reference() -*/ -void cson_value_free(cson_value * v); - -/** - Functionally similar to cson_array_set(), but uses a string key - as an index. Like arrays, if a value already exists for the given key, - it is destroyed by this function before inserting the new value. - - If v is NULL then this call is equivalent to - cson_object_unset(obj,key). Note that (v==NULL) is treated - differently from v having the special null value. In the latter - case, the key is set to the special null value. - - The key may be encoded as ASCII or UTF8. Results are undefined - with other encodings, and the errors won't show up here, but may - show up later, e.g. during output. - - Returns 0 on success, non-0 on error. It has the following error - cases: - - - cson_rc.ArgError: obj or key are NULL or strlen(key) is 0. - - - cson_rc.AllocError: an out-of-memory error - - On error ownership of v is NOT modified, and the caller may still - need to clean it up. For example, the following code will introduce - a leak if this function fails: - - @code - cson_object_set( myObj, "foo", cson_value_new_integer(42) ); - @endcode - - Because the value created by cson_value_new_integer() has no owner - and is not cleaned up. The "more correct" way to do this is: - - @code - cson_value * v = cson_value_new_integer(42); - int rc = cson_object_set( myObj, "foo", v ); - if( 0 != rc ) { - cson_value_free( v ); - ... handle error ... - } - @endcode - - Potential TODOs: - - - Add an overload which takes a cson_value key instead. To get - any value out of that we first need to be able to convert arbitrary - value types to strings. We could simply to-JSON them and use those - as keys. -*/ -int cson_object_set( cson_object * obj, char const * key, cson_value * v ); - -/** - Removes a property from an object. - - If obj contains the given key, it is removed and 0 is returned. If - it is not found, cson_rc.NotFoundError is returned (which can - normally be ignored by client code). - - cson_rc.ArgError is returned if obj or key are NULL or key has - a length of 0. - - Returns 0 if the given key is found and removed. - - This is functionally equivalent calling - cson_object_set(obj,key,NULL). -*/ -int cson_object_unset( cson_object * obj, char const * key ); - -/** - Searches the given object for a property with the given key. If found, - it is returned. If no match is found, or any arguments are NULL, NULL is - returned. The returned object is owned by obj, and may be invalidated - by ANY operations which change obj's property list (i.e. add or remove - properties). - - FIXME: allocate the key/value pairs like we do for cson_array, - to get improve the lifetimes of fetched values. - - @see cson_object_fetch_sub() - @see cson_object_get_sub() -*/ -cson_value * cson_object_get( cson_object const * obj, char const * key ); - -/** - Similar to cson_object_get(), but removes the value from the parent - object's ownership. If no item is found then NULL is returned, else - the object (now owned by the caller or possibly shared with other - containers) is returned. - - Returns NULL if either obj or key are NULL or key has a length - of 0. - - This function reduces the returned value's reference count but has - the specific property that it does not treat refcounts 0 and 1 - identically, meaning that the returned object may have a refcount - of 0. This behaviour works around a corner-case where we want to - extract a child element from its parent and then destroy the parent - (which leaves us in an undesireable (normally) reference count - state). -*/ -cson_value * cson_object_take( cson_object * obj, char const * key ); - -/** - Fetches a property from a child (or [great-]*grand-child) object. - - obj is the object to search. - - path is a delimited string, where the delimiter is the given - separator character. - - This function searches for the given path, starting at the given object - and traversing its properties as the path specifies. If a given part of the - path is not found, then this function fails with cson_rc.NotFoundError. - - If it finds the given path, it returns the value by assiging *tgt - to it. If tgt is NULL then this function has no side-effects but - will return 0 if the given path is found within the object, so it can be used - to test for existence without fetching it. - - Returns 0 if it finds an entry, cson_rc.NotFoundError if it finds - no item, and any other non-zero error code on a "real" error. Errors include: - - - obj or path are NULL: cson_rc.ArgError - - - separator is 0, or path is an empty string or contains only - separator characters: cson_rc.RangeError - - - There is an upper limit on how long a single path component may - be (some "reasonable" internal size), and cson_rc.RangeError is - returned if that length is violated. - - - Limitations: - - - It has no way to fetch data from arrays this way. i could - imagine, e.g., a path of "subobj.subArray.0" for - subobj.subArray[0], or "0.3.1" for [0][3][1]. But i'm too - lazy/tired to add this. - - Example usage: - - - Assume we have a JSON structure which abstractly looks like: - - @code - {"subobj":{"subsubobj":{"myValue":[1,2,3]}}} - @endcode - - Out goal is to get the value of myValue. We can do that with: - - @code - cson_value * v = NULL; - int rc = cson_object_fetch_sub( object, &v, "subobj.subsubobj.myValue", '.' ); - @endcode - - Note that because keys in JSON may legally contain a '.', the - separator must be specified by the caller. e.g. the path - "subobj/subsubobj/myValue" with separator='/' is equivalent the - path "subobj.subsubobj.myValue" with separator='.'. The value of 0 - is not legal as a separator character because we cannot - distinguish that use from the real end-of-string without requiring - the caller to also pass in the length of the string. - - Multiple successive separators in the list are collapsed into a - single separator for parsing purposes. e.g. the path "a...b...c" - (separator='.') is equivalent to "a.b.c". - - @see cson_object_get_sub() -*/ -int cson_object_fetch_sub( cson_object const * obj, cson_value ** tgt, char const * path, char separator ); - -/** - Convenience form of cson_object_fetch_sub() which returns NULL if the given - item is not found. -*/ -cson_value * cson_object_get_sub( cson_object const * obj, char const * path, char sep ); - - -/** - An iterator type for traversing object properties. - - Its values must be considered private, not to be touched by client - code. - - @see cson_object_iter_init() - @see cson_object_iter_next() -*/ -struct cson_object_iterator -{ - - /** @internal - The underlying object. - */ - cson_object const * obj; - /** @internal - Current position in the property list. - */ - unsigned int pos; -}; -typedef struct cson_object_iterator cson_object_iterator; - -/** - Empty-initialized cson_object_iterator object. -*/ -#define cson_object_iterator_empty_m {NULL/*obj*/,0/*pos*/} - -/** - Empty-initialized cson_object_iterator object. -*/ -extern const cson_object_iterator cson_object_iterator_empty; - -/** - Initializes the given iterator to point at the start of obj's - properties. Returns 0 on success or cson_rc.ArgError if !obj - or !iter. - - obj must outlive iter, or results are undefined. Results are also - undefined if obj is modified while the iterator is active. - - @see cson_object_iter_next() -*/ -int cson_object_iter_init( cson_object const * obj, cson_object_iterator * iter ); - -/** @struct cson_kvp - -This class represents a key/value pair and is used for storing -object properties. It is opaque to client code, and the public -API only uses this type for purposes of iterating over cson_object -properties using the cson_object_iterator interfaces. -*/ - -typedef struct cson_kvp cson_kvp; - -/** - Returns the next property from the given iterator's object, or NULL - if the end of the property list as been reached. - - Note that the order of object properties is undefined by the API, - and may change from version to version. - - The returned memory belongs to the underlying object and may be - invalidated by any changes to that object. - - Example usage: - - @code - cson_object_iterator it; - cson_object_iter_init( myObject, &it ); // only fails if either arg is 0 - cson_kvp * kvp; - cson_string const * key; - cson_value const * val; - while( (kvp = cson_object_iter_next(&it) ) ) - { - key = cson_kvp_key(kvp); - val = cson_kvp_value(kvp); - ... - } - @endcode - - There is no need to clean up an iterator, as it holds no dynamic resources. - - @see cson_kvp_key() - @see cson_kvp_value() -*/ -cson_kvp * cson_object_iter_next( cson_object_iterator * iter ); - - -/** - Returns the key associated with the given key/value pair, - or NULL if !kvp. The memory is owned by the object which contains - the key/value pair, and may be invalidated by any modifications - to that object. -*/ -cson_string const * cson_kvp_key( cson_kvp const * kvp ); - -/** - Returns the value associated with the given key/value pair, - or NULL if !kvp. The memory is owned by the object which contains - the key/value pair, and may be invalidated by any modifications - to that object. -*/ -cson_value * cson_kvp_value( cson_kvp const * kvp ); - -/** @typedef some unsigned int type cson_size_t - -*/ -typedef unsigned int cson_size_t; - -/** - A generic buffer class. - - They can be used like this: - - @code - cson_buffer b = cson_buffer_empty; - int rc = cson_buffer_reserve( &buf, 100 ); - if( 0 != rc ) { ... allocation error ... } - ... use buf.mem ... - ... then free it up ... - cson_buffer_reserve( &buf, 0 ); - @endcode - - To take over ownership of a buffer's memory: - - @code - void * mem = b.mem; - // mem is b.capacity bytes long, but only b.used - // bytes of it has been "used" by the API. - b = cson_buffer_empty; - @endcode - - The memory now belongs to the caller and must eventually be - free()d. -*/ -struct cson_buffer -{ - /** - The number of bytes allocated for this object. - Use cson_buffer_reserve() to change its value. - */ - cson_size_t capacity; - /** - The number of bytes "used" by this object. It is not needed for - all use cases, and management of this value (if needed) is up - to the client. The cson_buffer public API does not use this - member. The intention is that this can be used to track the - length of strings which are allocated via cson_buffer, since - they need an explicit length and/or null terminator. - */ - cson_size_t used; - - /** - This is a debugging/metric-counting value - intended to help certain malloc()-conscious - clients tweak their memory reservation sizes. - Each time cson_buffer_reserve() expands the - buffer, it increments this value by 1. - */ - cson_size_t timesExpanded; - - /** - The memory allocated for and owned by this buffer. - Use cson_buffer_reserve() to change its size or - free it. To take over ownership, do: - - @code - void * myptr = buf.mem; - buf = cson_buffer_empty; - @endcode - - (You might also need to store buf.used and buf.capacity, - depending on what you want to do with the memory.) - - When doing so, the memory must eventually be passed to free() - to deallocate it. - */ - unsigned char * mem; -}; -/** Convenience typedef. */ -typedef struct cson_buffer cson_buffer; - -/** An empty-initialized cson_buffer object. */ -#define cson_buffer_empty_m {0/*capacity*/,0/*used*/,0/*timesExpanded*/,NULL/*mem*/} -/** An empty-initialized cson_buffer object. */ -extern const cson_buffer cson_buffer_empty; - -/** - Uses cson_output() to append all JSON output to the given buffer - object. The semantics for the (v, opt) parameters, and the return - value, are as documented for cson_output(). buf must be a non-NULL - pointer to a properly initialized buffer (see example below). - - Ownership of buf is not changed by calling this. - - On success 0 is returned and the contents of buf.mem are guaranteed - to be NULL-terminated. On error the buffer might contain partial - contents, and it should not be used except to free its contents. - - On error non-zero is returned. Errors include: - - - Invalid arguments: cson_rc.ArgError - - - Buffer cannot be expanded (runs out of memory): cson_rc.AllocError - - Example usage: - - @code - cson_buffer buf = cson_buffer_empty; - // optional: cson_buffer_reserve(&buf, 1024 * 10); - int rc = cson_output_buffer( myValue, &buf, NULL ); - if( 0 != rc ) { - ... error! ... - } - else { - ... use buffer ... - puts((char const*)buf.mem); - } - // In both cases, we eventually need to clean up the buffer: - cson_buffer_reserve( &buf, 0 ); - // Or take over ownership of its memory: - { - char * mem = (char *)buf.mem; - buf = cson_buffer_empty; - ... - free(mem); - } - @endcode - - @see cson_output() - -*/ -int cson_output_buffer( cson_value const * v, cson_buffer * buf, - cson_output_opt const * opt ); - -/** - This works identically to cson_parse_string(), but takes a - cson_buffer object as its input. buf->used bytes of buf->mem are - assumed to be valid JSON input, but it need not be NUL-terminated - (we only read up to buf->used bytes). The value of buf->used is - assumed to be the "string length" of buf->mem, i.e. not including - the NUL terminator. - - Returns 0 on success, non-0 on error. - - See cson_parse() for the semantics of the tgt, opt, and err - parameters. -*/ -int cson_parse_buffer( cson_value ** tgt, cson_buffer const * buf, - cson_parse_opt const * opt, cson_parse_info * err ); - - -/** - Reserves the given amount of memory for the given buffer object. - - If n is 0 then buf->mem is freed and its state is set to - NULL/0 values. - - If buf->capacity is less than or equal to n then 0 is returned and - buf is not modified. - - If n is larger than buf->capacity then buf->mem is (re)allocated - and buf->capacity contains the new length. Newly-allocated bytes - are filled with zeroes. - - On success 0 is returned. On error non-0 is returned and buf is not - modified. - - buf->mem is owned by buf and must eventually be freed by passing an - n value of 0 to this function. - - buf->used is never modified by this function. -*/ -int cson_buffer_reserve( cson_buffer * buf, cson_size_t n ); - -/** - Fills all bytes of the given buffer with the given character. - Returns the number of bytes set (buf->capacity), or 0 if - !buf or buf has no memory allocated to it. -*/ -cson_size_t cson_buffer_fill( cson_buffer * buf, char c ); - -/** - Uses a cson_data_source_f() function to buffer input into a - cson_buffer. - - dest must be a non-NULL, initialized (though possibly empty) - cson_buffer object. Its contents, if any, will be overwritten by - this function, and any memory it holds might be re-used. - - The src function is called, and passed the state parameter, to - fetch the input. If it returns non-0, this function returns that - error code. src() is called, possibly repeatedly, until it reports - that there is no more data. - - Whether or not this function succeeds, dest still owns any memory - pointed to by dest->mem, and the client must eventually free it by - calling cson_buffer_reserve(dest,0). - - dest->mem might (and possibly will) be (re)allocated by this - function, so any pointers to it held from before this call might be - invalidated by this call. - - On error non-0 is returned and dest has almost certainly been - modified but its state must be considered incomplete. - - Errors include: - - - dest or src are NULL (cson_rc.ArgError) - - - Allocation error (cson_rc.AllocError) - - - src() returns an error code - - Whether or not the state parameter may be NULL depends on - the src implementation requirements. - - On success dest will contain the contents read from the input - source. dest->used will be the length of the read-in data, and - dest->mem will point to the memory. dest->mem is automatically - NUL-terminated if this function succeeds, but dest->used does not - count that terminator. On error the state of dest->mem must be - considered incomplete, and is not guaranteed to be NUL-terminated. - - Example usage: - - @code - cson_buffer buf = cson_buffer_empty; - int rc = cson_buffer_fill_from( &buf, - cson_data_source_FILE, - stdin ); - if( rc ) - { - fprintf(stderr,"Error %d (%s) while filling buffer.\n", - rc, cson_rc_string(rc)); - cson_buffer_reserve( &buf, 0 ); - return ...; - } - ... use the buf->mem ... - ... clean up the buffer ... - cson_buffer_reserve( &buf, 0 ); - @endcode - - To take over ownership of the buffer's memory, do: - - @code - void * mem = buf.mem; - buf = cson_buffer_empty; - @endcode - - In which case the memory must eventually be passed to free() to - free it. -*/ -int cson_buffer_fill_from( cson_buffer * dest, cson_data_source_f src, void * state ); - - -/** - Increments the reference count for the given value. This is a - low-level operation and should not normally be used by client code - without understanding exactly what side-effects it introduces. - Mis-use can lead to premature destruction or cause a value instance - to never be properly destructed (i.e. a memory leak). - - This function is probably only useful for the following cases: - - - You want to hold a reference to a value which is itself contained - in one or more containers, and you need to be sure that your - reference outlives the container(s) and/or that you can free your - copy of the reference without invaliding any references to the same - value held in containers. - - - You want to implement "value sharing" behaviour without using an - object or array to contain the shared value. This can be used to - ensure the lifetime of the shared value instance. Each sharing - point adds a reference and simply passed the value to - cson_value_free() when they're done. The object will be kept alive - for other sharing points which added a reference. - - Normally any such value handles would be invalidated when the - parent container(s) is/are cleaned up, but this function can be - used to effectively delay the cleanup. - - This function, at its lowest level, increments the value's - reference count by 1. - - To decrement the reference count, pass the value to - cson_value_free(), after which the value must be considered, from - the perspective of that client code, to be destroyed (though it - will not be if there are still other live references to - it). cson_value_free() will not _actually_ destroy the value until - its reference count drops to 0. - - Returns 0 on success. The only error conditions are if v is NULL - (cson_rc.ArgError) or if the reference increment would overflow - (cson_rc.RangeError). In theory a client would get allocation - errors long before the reference count could overflow (assuming - those reference counts come from container insertions, as opposed - to via this function). - - Insider notes which clients really need to know: - - For shared/constant value instances, such as those returned by - cson_value_true() and cson_value_null(), this function has no side - effects - it does not actually modify the reference count because - (A) those instances are shared across all client code and (B) those - objects are static and never get cleaned up. However, that is an - implementation detail which client code should not rely on. In - other words, if you call cson_value_add_reference() 3 times using - the value returned by cson_value_true() (which is incidentally a - shared cson_value instance), you must eventually call - cson_value_free() 3 times to (semantically) remove those - references. However, internally the reference count for that - specific cson_value instance will not be modified and those - objects will never be freed (they're stack-allocated). - - It might be interesting to note that newly-created objects - have a reference count of 0 instead of 1. This is partly because - if the initial reference is counted then it makes ownership - problematic when inserting values into containers. e.g. consider the - following code: - - @code - // ACHTUNG: this code is hypothetical and does not reflect - // what actually happens! - cson_value * v = - cson_value_new_integer( 42 ); // v's refcount = 1 - cson_array_append( myArray, v ); // v's refcount = 2 - @endcode - - If that were the case, the client would be forced to free his own - reference after inserting it into the container (which is a bit - counter-intuitive as well as intrusive). It would look a bit like - the following and would have to be done after every create/insert - operation: - - @code - // ACHTUNG: this code is hypothetical and does not reflect - // what actually happens! - cson_array_append( myArray, v ); // v's refcount = 2 - cson_value_free( v ); // v's refcount = 1 - @endcode - - (As i said: it's counter-intuitive and intrusive.) - - Instead, values start with a refcount of 0 and it is only increased - when the value is added to an object/array container or when this - function is used to manually increment it. cson_value_free() treats - a refcount of 0 or 1 equivalently, destroying the value - instance. The only semantic difference between 0 and 1, for - purposes of cleaning up, is that a value with a non-0 refcount has - been had its refcount adjusted, whereas a 0 refcount indicates a - fresh, "unowned" reference. -*/ -int cson_value_add_reference( cson_value * v ); - -#if 0 -/** - DO NOT use this unless you know EXACTLY what you're doing. - It is only in the public API to work around a couple corner - cases involving extracting child elements and discarding - their parents. - - This function sets v's reference count to the given value. - It does not clean up the object if rc is 0. - - Returns 0 on success, non-0 on error. -*/ -int cson_value_refcount_set( cson_value * v, unsigned short rc ); -#endif - -/** - Deeply copies a JSON value, be it an object/array or a "plain" - value (e.g. number/string/boolean). If cv is not NULL then this - function makes a deep clone of it and returns that clone. Ownership - of the clone is transfered to the caller, who must eventually free - the value using cson_value_free() or add it to a container - object/array to transfer ownership to the container. The returned - object will be of the same logical type as orig. - - ACHTUNG: if orig contains any cyclic references at any depth level - this function will endlessly recurse. (Having _any_ cyclic - references violates this library's requirements.) - - Returns NULL if orig is NULL or if cloning fails. Assuming that - orig is in a valid state, the only "likely" error case is that an - allocation fails while constructing the clone. In other words, if - cloning fails due to something other than an allocation error then - either orig is in an invalid state or there is a bug. -*/ -cson_value * cson_value_clone( cson_value const * orig ); - -/* LICENSE - -This software's source code, including accompanying documentation and -demonstration applications, are licensed under the following -conditions... - -Certain files are imported from external projects and have their own -licensing terms. Namely, the JSON_parser.* files. See their files for -their official licenses, but the summary is "do what you want [with -them] but leave the license text and copyright in place." - -The author (Stephan G. Beal [http://wanderinghorse.net/home/stephan/]) -explicitly disclaims copyright in all jurisdictions which recognize -such a disclaimer. In such jurisdictions, this software is released -into the Public Domain. - -In jurisdictions which do not recognize Public Domain property -(e.g. Germany as of 2011), this software is Copyright (c) 2011 by -Stephan G. Beal, and is released under the terms of the MIT License -(see below). - -In jurisdictions which recognize Public Domain property, the user of -this software may choose to accept it either as 1) Public Domain, 2) -under the conditions of the MIT License (see below), or 3) under the -terms of dual Public Domain/MIT License conditions described here, as -they choose. - -The MIT License is about as close to Public Domain as a license can -get, and is described in clear, concise terms at: - - http://en.wikipedia.org/wiki/MIT_License - -The full text of the MIT License follows: - --- -Copyright (c) 2011 Stephan G. Beal (http://wanderinghorse.net/home/stephan/) - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - ---END OF MIT LICENSE-- - -For purposes of the above license, the term "Software" includes -documentation and demonstration source code which accompanies -this software. ("Accompanies" = is contained in the Software's -primary public source code repository.) - -*/ - -#if defined(__cplusplus) -} /*extern "C"*/ -#endif - -#endif /* WANDERINGHORSE_NET_CSON_H_INCLUDED */ -/* end file include/wh/cson/cson.h */ -/* begin file include/wh/cson/cson_sqlite3.h */ -/** @file cson_sqlite3.h - -This file contains cson's public sqlite3-to-JSON API declarations -and API documentation. If CSON_ENABLE_SQLITE3 is not defined, -or is defined to 0, then including this file will have no side-effects -other than defining CSON_ENABLE_SQLITE3 (if it was not defined) to 0 -and defining a few include guard macros. i.e. if CSON_ENABLE_SQLITE3 -is not set to a true value then the API is not visible. - -This API requires that
%h
%s
", z, zRebuildMsg); - cgi_reply(); - }else{ - fprintf(stderr, "%s: %s\n\n%s", fossil_nameofexe(), z, zRebuildMsg); - } - } - free(z); + if( g.xferPanic ){ + cgi_reset_content(); + @ error Database\serror:\s%F(z) + cgi_reply(); + } + if( g.cgiOutput ){ + g.cgiOutput = 0; + cgi_printf("%h
%s
", z, zRebuildMsg); + cgi_reply(); + }else{ + fprintf(stderr, "%s: %s\n\n%s", fossil_nameofexe(), z, zRebuildMsg); + } db_force_rollback(); - fossil_exit(rc); + fossil_exit(1); } static int nBegin = 0; /* Nesting depth of BEGIN */ static int doRollback = 0; /* True to force a rollback */ static int nCommitHook = 0; /* Number of commit hooks */ @@ -751,20 +742,20 @@ /* * * Returns TRUE if zTable exists in the local database. */ static int db_local_table_exists(const char *zTable){ return db_exists("SELECT 1 FROM %s.sqlite_master" - " WHERE name=='%s'", + " WHERE name=='%s' /*scan*/", db_name("localdb"), zTable); } /* ** Returns TRUE if zColumn exists in zTable in the local database. */ static int db_local_column_exists(const char *zTable, const char *zColumn){ return db_exists("SELECT 1 FROM %s.sqlite_master" - " WHERE name=='%s' AND sql GLOB '* %s *'", + " WHERE name=='%s' AND sql GLOB '* %s *' /*scan*/", db_name("localdb"), zTable, zColumn); } /* ** If zDbName is a valid local database file, open it and return @@ -953,11 +944,10 @@ ** Verify that the repository schema is correct. If it is not correct, ** issue a fatal error and die. */ void db_verify_schema(void){ if( db_schema_is_outofdate() ){ - g.json.resultCode = FSL_JSON_E_DB_NEEDS_REBUILD; fossil_warning("incorrect repository schema version"); fossil_warning("your repository has schema version \"%s\" " "but this binary expects version \"%s\"", db_get("aux-schema",0), AUX_SCHEMA); fossil_fatal("run \"fossil rebuild\" to fix this problem"); Index: src/diff.c ================================================================== --- src/diff.c +++ src/diff.c @@ -578,10 +578,206 @@ free(c.aFrom); free(c.aTo); return c.aEdit; } } + +/* +** Copy a line with a limit. Used for side-by-side diffs to enforce a maximum +** line length limit. +*/ +static char *copylimline(char *out, DLine *dl, int lim){ + int len; + len = dl->h & LENGTH_MASK; + if( lim && len > lim ){ + memcpy(out, dl->z, lim-3); + strcpy(&out[lim-3], "..."); + }else{ + memcpy(out, dl->z, len); + out[len] = '\0'; + } + return out; +} + +/* +** Output table body of a side-by-side diff. Prior to the call, the caller +** should have output: +**Old title | +** | New title |
---|
cannot compute + @ difference between binary files
+ return 0; + } + + collim = collim < 4 ? 0 : collim; + + /* Compute the difference */ + diff_all(&c); + + linebuf = fossil_malloc(LENGTH_MASK+1); + if( !linebuf ){ + free(c.aFrom); + free(c.aTo); + free(c.aEdit); + return 0; + } + + iFrom=iTo=0; + i=0; + while( iOld (%S(zFrom)) | + @ | New (%S(zTo)) |
---|
Deleted %h(zName)
@@ -329,13 +360,17 @@ }else{ @Added %h(zName) @ version [%S(zNew)] } if( showDiff ){ - @
+ if( sideBySide ){ + generate_sbsdiff(zOld, zNew); + }else{ + @- append_diff(zOld, zNew); - @
+ } }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){ @ @ [diff] } @ @@ -361,10 +396,11 @@ void ci_page(void){ Stmt q; int rid; int isLeaf; int showDiff; + int sideBySide; const char *zName; /* Name of the checkin to be displayed */ const char *zUuid; /* UUID of zName */ const char *zParent; /* UUID of the parent checkin (if any) */ login_check_credentials(); @@ -390,10 +426,11 @@ " FROM blob, event" " WHERE blob.rid=%d" " AND event.objid=%d", rid, rid ); + sideBySide = atoi(PD("sbs","1")); if( db_step(&q)==SQLITE_ROW ){ const char *zUuid = db_column_text(&q, 0); char *zTitle = mprintf("Check-in [%.10s]", zUuid); char *zEUser, *zEComment; const char *zUser; @@ -511,12 +548,24 @@ showDiff = g.zPath[0]!='c'; if( db_get_boolean("show-version-diffs", 0)==0 ){ showDiff = !showDiff; if( showDiff ){ @ [hide diffs] + @ + if( sideBySide ){ + @ + @ [show 1-pane diffs] + }else{ + @ + @ [show 2-pane diffs] + } }else{ - @ [show diffs] + @ + @ [show 1-pane diffs] + @ + @ + @ [show 2-pane diffs] } }else{ if( showDiff ){ @ [hide diffs] }else{ @@ -540,11 +589,12 @@ const char *zName = db_column_text(&q,0); int mperm = db_column_int(&q, 1); const char *zOld = db_column_text(&q,2); const char *zNew = db_column_text(&q,3); const char *zOldName = db_column_text(&q, 4); - append_file_change_line(zName, zOld, zNew, zOldName, showDiff, mperm); + append_file_change_line(zName, zOld, zNew, zOldName, showDiff, + sideBySide, mperm); } db_finalize(&q); } style_footer(); } @@ -690,17 +740,18 @@ } /* ** WEBPAGE: vdiff -** URL: /vdiff?from=UUID&to=UUID&detail=BOOLEAN +** URL: /vdiff?from=UUID&to=UUID&detail=BOOLEAN;sbs=BOOLEAN ** ** Show all differences between two checkins. */ void vdiff_page(void){ int ridFrom, ridTo; int showDetail = 0; + int sideBySide = 0; Manifest *pFrom, *pTo; ManifestFile *pFileFrom, *pFileTo; login_check_credentials(); if( !g.perm.Read ){ login_needed(); return; } @@ -709,10 +760,20 @@ pFrom = vdiff_parse_manifest("from", &ridFrom); if( pFrom==0 ) return; pTo = vdiff_parse_manifest("to", &ridTo); if( pTo==0 ) return; showDetail = atoi(PD("detail","0")); + sideBySide = atoi(PD("sbs","1")); + if( !sideBySide ){ + style_submenu_element("2-Pane Diff", "TPD", + "%s/vdiff?from=%T&to=%T&detail=%d&sbs=1", + g.zTop, P("from"), P("to"), showDetail); + }else{ + style_submenu_element("1-Pane Diff", "OPD", + "%s/vdiff?from=%T&to=%T&detail=%d&sbs=0", + g.zTop, P("from"), P("to"), showDetail); + } style_header("Check-in Differences"); @+ append_diff(zOld, zNew); + @
checkin_description(ridFrom); @
checkin_description(ridTo); @@ -731,25 +792,25 @@ }else{ cmp = fossil_strcmp(pFileFrom->zName, pFileTo->zName); } if( cmp<0 ){ append_file_change_line(pFileFrom->zName, - pFileFrom->zUuid, 0, 0, 0, 0); + pFileFrom->zUuid, 0, 0, 0, 0, 0); pFileFrom = manifest_file_next(pFrom, 0); }else if( cmp>0 ){ append_file_change_line(pFileTo->zName, - 0, pFileTo->zUuid, 0, 0, + 0, pFileTo->zUuid, 0, 0, 0, manifest_file_mperm(pFileTo)); pFileTo = manifest_file_next(pTo, 0); }else if( fossil_strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){ /* No changes */ pFileFrom = manifest_file_next(pFrom, 0); pFileTo = manifest_file_next(pTo, 0); }else{ append_file_change_line(pFileFrom->zName, pFileFrom->zUuid, - pFileTo->zUuid, 0, showDetail, + pFileTo->zUuid, 0, showDetail, sideBySide, manifest_file_mperm(pFileTo)); pFileFrom = manifest_file_next(pFrom, 0); pFileTo = manifest_file_next(pTo, 0); } } @@ -797,11 +858,11 @@ " WHERE filename.fnid=mlink.fnid" " AND event.objid=mlink.mid" " AND a.rid=mlink.fid" " AND b.rid=mlink.mid" " AND mlink.fid=%d" - " ORDER BY filename.name, event.mtime", + " ORDER BY filename.name, event.mtime /*sort*/", TAG_BRANCH, rid ); @while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); @@ -983,28 +1044,30 @@ } /* ** WEBPAGE: fdiff -** URL: fdiff?v1=UUID&v2=UUID&patch +** URL: fdiff?v1=UUID&v2=UUID&patch&sbs=BOOLEAN ** -** Two arguments, v1 and v2, identify the files to be diffed. Show the -** difference between the two artifacts. Generate plaintext if "patch" -** is present. +** Two arguments, v1 and v2, identify the files to be diffed. Show the +** difference between the two artifacts. Show diff side by side unless sbs +** is 0. Generate plaintext if "patch" is present. */ void diff_page(void){ int v1, v2; int isPatch; + int sideBySide; Blob c1, c2, diff, *pOut; char *zV1; char *zV2; login_check_credentials(); if( !g.perm.Read ){ login_needed(); return; } v1 = name_to_rid_www("v1"); v2 = name_to_rid_www("v2"); if( v1==0 || v2==0 ) fossil_redirect_home(); + sideBySide = atoi(PD("sbs","1")); zV1 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v1); zV2 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v2); isPatch = P("patch")!=0; if( isPatch ){ pOut = cgi_output_blob(); @@ -1011,28 +1074,42 @@ cgi_set_content_type("text/plain"); }else{ blob_zero(&diff); pOut = &diff; } - content_get(v1, &c1); - content_get(v2, &c2); - text_diff(&c1, &c2, pOut, 4, 1); - blob_reset(&c1); - blob_reset(&c2); + if( !sideBySide || isPatch ){ + content_get(v1, &c1); + content_get(v2, &c2); + text_diff(&c1, &c2, pOut, 4, 1); + blob_reset(&c1); + blob_reset(&c2); + } if( !isPatch ){ style_header("Diff"); style_submenu_element("Patch", "Patch", "%s/fdiff?v1=%T&v2=%T&patch", g.zTop, P("v1"), P("v2")); + if( !sideBySide ){ + style_submenu_element("2-Pane Diff", "TPD", "%s/fdiff?v1=%T&v2=%T&sbs=1", + g.zTop, P("v1"), P("v2")); + }else{ + style_submenu_element("1-Pane Diff", "OPD", "%s/fdiff?v1=%T&v2=%T&sbs=0", + g.zTop, P("v1"), P("v2")); + } + @
Differences From @ Artifact [%S(zV1)]:
object_description(v1, 0, 0); @To Artifact [%S(zV2)]:
object_description(v2, 0, 0); @
- @+ if( sideBySide ){ + generate_sbsdiff(zV1, zV2); + }else{ + @- @ %h(blob_str(&diff)) - @+ } blob_reset(&diff); style_footer(); } } DELETED src/json.c Index: src/json.c ================================================================== --- src/json.c +++ /dev/null @@ -1,1965 +0,0 @@ -/* -** Copyright (c) 2007 D. Richard Hipp -** -** This program is free software; you can redistribute it and/or -** modify it under the terms of the Simplified BSD License (also -** known as the "2-Clause License" or "FreeBSD License".) - -** This program is distributed in the hope that it will be useful, -** but without any warranty; without even the implied warranty of -** merchantability or fitness for a particular purpose. -** -** Author contact information: -** drh@hwaci.com -** http://www.hwaci.com/drh/ -** -******************************************************************************* -** -** Code for the JSON API. -** -** For notes regarding the public JSON interface, please see: -** -** https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit -** -** -** Notes for hackers... -** -** Here's how command/page dispatching works: json_page_top() (in HTTP mode) or -** json_cmd_top() (in CLI mode) catch the "json" path/command. Those functions then -** dispatch to a JSON-mode-specific command/page handler with the type fossil_json_f(). -** See the API docs for that typedef (below) for the semantics of the callbacks. -** -** -*/ -#include "config.h" -#include "VERSION.h" -#include "json.h" -#include+ @ %h(blob_str(&diff)) + @-#include - -#if INTERFACE -#include "json_detail.h" /* workaround for apparent enum limitation in makeheaders */ -#endif - -const FossilJsonKeys_ FossilJsonKeys = { - "anonymousSeed" /*anonymousSeed*/, - "authToken" /*authToken*/, - "COMMAND_PATH" /*commandPath*/, - "payload" /* payload */, - "requestId" /*requestId*/, - "resultCode" /*resultCode*/, - "resultText" /*resultText*/, - "timestamp" /*timestamp*/ -}; - -/* -** Internal helpers to manipulate a byte array as a bitset. The B -** argument must be-a array at least (BIT/8+1) bytes long. -** The BIT argument is the bit number to query/set/clear/toggle. -*/ -#define BITSET_BYTEFOR(B,BIT) ((B)[ BIT / 8 ]) -#define BITSET_SET(B,BIT) ((BITSET_BYTEFOR(B,BIT) |= (0x01 << (BIT%8))),0x01) -#define BITSET_UNSET(B,BIT) ((BITSET_BYTEFOR(B,BIT) &= ~(0x01 << (BIT%8))),0x00) -#define BITSET_GET(B,BIT) ((BITSET_BYTEFOR(B,BIT) & (0x01 << (BIT%8))) ? 0x01 : 0x00) -#define BITSET_TOGGLE(B,BIT) (BITSET_GET(B,BIT) ? (BITSET_UNSET(B,BIT)) : (BITSET_SET(B,BIT))) - - -/* Timer code taken from sqlite3's shell.c, modified slightly. - FIXME: move the timer into the fossil core API so that we can - start the timer early on in the app init phase. Right now we're - just timing the json ops themselves. -*/ -#if !defined(_WIN32) && !defined(WIN32) && !defined(__OS2__) && !defined(__RTP__) && !defined(_WRS_KERNEL) -#include -#include - -/* Saved resource information for the beginning of an operation */ -static struct rusage sBegin; - -/* -** Begin timing an operation -*/ -static void beginTimer(void){ - getrusage(RUSAGE_SELF, &sBegin); -} - -/* Return the difference of two time_structs in milliseconds */ -static double timeDiff(struct timeval *pStart, struct timeval *pEnd){ - return ((pEnd->tv_usec - pStart->tv_usec)*0.001 + - (double)((pEnd->tv_sec - pStart->tv_sec)*1000.0)); -} - -/* -** Print the timing results. -*/ -static double endTimer(void){ - struct rusage sEnd; - getrusage(RUSAGE_SELF, &sEnd); - return timeDiff(&sBegin.ru_utime, &sEnd.ru_utime) - + timeDiff(&sBegin.ru_stime, &sEnd.ru_stime); -#if 0 - printf("CPU Time: user %f sys %f\n", - timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), - timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); -#endif -} - -#define BEGIN_TIMER beginTimer() -#define END_TIMER endTimer() -#define HAS_TIMER 1 - -#elif (defined(_WIN32) || defined(WIN32)) - -#include - -/* Saved resource information for the beginning of an operation */ -static HANDLE hProcess; -static FILETIME ftKernelBegin; -static FILETIME ftUserBegin; -typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, LPFILETIME, LPFILETIME); -static GETPROCTIMES getProcessTimesAddr = NULL; - -/* -** Check to see if we have timer support. Return 1 if necessary -** support found (or found previously). -*/ -static int hasTimer(void){ - if( getProcessTimesAddr ){ - return 1; - } else { - /* GetProcessTimes() isn't supported in WIN95 and some other Windows versions. - ** See if the version we are running on has it, and if it does, save off - ** a pointer to it and the current process handle. - */ - hProcess = GetCurrentProcess(); - if( hProcess ){ - HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll")); - if( NULL != hinstLib ){ - getProcessTimesAddr = (GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes"); - if( NULL != getProcessTimesAddr ){ - return 1; - } - FreeLibrary(hinstLib); - } - } - } - return 0; -} - -/* -** Begin timing an operation -*/ -static void beginTimer(void){ - if( getProcessTimesAddr ){ - FILETIME ftCreation, ftExit; - getProcessTimesAddr(hProcess, &ftCreation, &ftExit, &ftKernelBegin, &ftUserBegin); - } -} - -/* Return the difference of two FILETIME structs in milliseconds */ -static double timeDiff(FILETIME *pStart, FILETIME *pEnd){ - sqlite_int64 i64Start = *((sqlite_int64 *) pStart); - sqlite_int64 i64End = *((sqlite_int64 *) pEnd); - return (double) ((i64End - i64Start) / 10000.0); -} - -/* -** Print the timing results. -*/ -static double endTimer(void){ - if(getProcessTimesAddr){ - FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; - getProcessTimesAddr(hProcess, &ftCreation, &ftExit, &ftKernelEnd, &ftUserEnd); - return timeDiff(&ftUserBegin, &ftUserEnd) + - timeDiff(&ftKernelBegin, &ftKernelEnd); - } -} - -#define BEGIN_TIMER beginTimer() -#define END_TIMER endTimer() -#define HAS_TIMER hasTimer() - -#else -#define BEGIN_TIMER -#define END_TIMER 0.0 -#define HAS_TIMER 0 -#endif - -/* -** Placeholder /json/XXX page impl for NYI (Not Yet Implemented) -** (but planned) pages/commands. -*/ -static cson_value * json_page_nyi(){ - g.json.resultCode = FSL_JSON_E_NYI; - return NULL; -} - -/* -** Given a FossilJsonCodes value, it returns a string suitable for use -** as a resultText string. Returns some unspecified string if errCode -** is not one of the FossilJsonCodes values. -*/ -char const * json_err_str( int errCode ){ - switch( errCode ){ - case 0: return "Success"; -#define C(X,V) case FSL_JSON_E_ ## X: return V - - C(GENERIC,"Generic error"); - C(INVALID_REQUEST,"Invalid request"); - C(UNKNOWN_COMMAND,"Unknown Command"); - C(UNKNOWN,"Unknown error"); - C(RESOURCE_NOT_FOUND,"Resource not found"); - C(TIMEOUT,"Timeout reached"); - C(ASSERT,"Assertion failed"); - C(ALLOC,"Resource allocation failed"); - C(NYI,"Not yet implemented"); - C(AUTH,"Authentication error"); - C(LOGIN_FAILED,"Login failed"); - C(LOGIN_FAILED_NOSEED,"Anonymous login attempt was missing password seed"); - C(LOGIN_FAILED_NONAME,"Login failed - name not supplied"); - C(LOGIN_FAILED_NOPW,"Login failed - password not supplied"); - C(LOGIN_FAILED_NOTFOUND,"Login failed - no match found"); - C(MISSING_AUTH,"Authentication info missing from request"); - C(DENIED,"Access denied"); - C(WRONG_MODE,"Request not allowed (wrong operation mode)"); - - C(USAGE,"Usage error"); - C(INVALID_ARGS,"Invalid arguments"); - C(MISSING_ARGS,"Missing arguments"); - - C(DB,"Database error"); - C(STMT_PREP,"Statement preparation failed"); - C(STMT_BIND,"Statement parameter binding failed"); - C(STMT_EXEC,"Statement execution/stepping failed"); - C(DB_LOCKED,"Database is locked"); - C(DB_NEEDS_REBUILD,"Fossil repository needs to be rebuilt"); -#undef C - default: - return "Unknown Error"; - } -} - -/* -** Implements the cson_data_dest_f() interface and outputs the data to -** a fossil Blob object. pState must be-a initialized (Blob*), to -** which n bytes of src will be appended. -**/ -int cson_data_dest_Blob(void * pState, void const * src, unsigned int n){ - Blob * b = (Blob*)pState; - blob_append( b, (char const *)src, (int)n ) /* will die on OOM */; - return 0; -} - -/* -** Implements the cson_data_source_f() interface and reads -** input from a fossil Blob object. pState must be-a (Blob*). -*/ -int cson_data_src_Blob(void * pState, void * dest, unsigned int * n){ - Blob * b = (Blob*)pState; - *n = blob_read( b, dest, *n ); - return 0; -} - -/* -** Convenience wrapper around cson_output() which appends the output -** to pDest. pOpt may be NULL, in which case g.json.outOpt will be used. -*/ -int cson_output_Blob( cson_value const * pVal, Blob * pDest, cson_output_opt const * pOpt ){ - return cson_output( pVal, cson_data_dest_Blob, - pDest, pOpt ? pOpt : &g.json.outOpt ); -} - -/* -** Convenience wrapper around cson_parse() which reads its input -** from pSrc. pSrc is rewound before parsing. -** -** pInfo may be NULL. If it is not NULL then it will contain details -** about the parse state when this function returns. -** -** On success a new JSON Object or Array is returned. On error NULL is -** returned. -*/ -cson_value * cson_parse_Blob( Blob * pSrc, cson_parse_info * pInfo ){ - cson_value * root = NULL; - blob_rewind( pSrc ); - cson_parse( &root, cson_data_src_Blob, pSrc, NULL, pInfo ); - return root; -} - -/* -** Implements the cson_data_dest_f() interface and outputs the data to -** cgi_append_content(). pState is ignored. -**/ -int cson_data_dest_cgi(void * pState, void const * src, unsigned int n){ - cgi_append_content( (char const *)src, (int)n ); - return 0; -} - -/* -** Returns a string in the form FOSSIL-XXXX, where XXXX is a -** left-zero-padded value of code. The returned buffer is static, and -** must be copied if needed for later. The returned value will always -** be 11 bytes long (not including the trailing NUL byte). -** -** In practice we will only ever call this one time per app execution -** when constructing the JSON response envelope, so the static buffer -** "shouldn't" be a problem. -** -*/ -char const * json_rc_cstr( int code ){ - enum { BufSize = 12 }; - static char buf[BufSize] = {'F','O','S','S','I','L','-',0}; - assert((code >= 1000) && (code <= 9999) && "Invalid Fossil/JSON code."); - sprintf(buf+7,"%04d", code); - return buf; -} - -/* -** Adds v to the API-internal cleanup mechanism. key must be a unique -** key for the given element. Adding another item with that key may -** free the previous one (depending on its reference count). If -** freeOnError is true then v is passed to cson_value_free() if the -** key cannot be inserted, otherweise ownership of v is not changed on -** error. Failure to insert a key may be caused by any of the -** following: -** -** - Allocation error. -** - g.json.gc.o is NULL -** - key is NULL or empty. -** -** Returns 0 on success. -** -** On success, ownership of v is transfered to (or shared with) -** g.json.gc, and v will be valid until that object is cleaned up or -** its key is replaced via another call to this function. -*/ -int json_gc_add( char const * key, cson_value * v, char freeOnError ){ - int const rc = cson_object_set( g.json.gc.o, key, v ); - assert( NULL != g.json.gc.o ); - if( (0 != rc) && freeOnError ){ - cson_value_free( v ); - } - return rc; -} - - -/* -** Returns the value of json_rc_cstr(code) as a new JSON -** string, which is owned by the caller and must eventually -** be cson_value_free()d or transfered to a JSON container. -*/ -cson_value * json_rc_string( int code ){ - return cson_value_new_string( json_rc_cstr(code), 11 ); -} - -cson_value * json_new_string( char const * str ){ - return str - ? cson_value_new_string(str,strlen(str)) - : NULL; -} - -/* -** Gets a POST/POST.payload/GET/COOKIE/ENV value. The returned memory -** is owned by the g.json object (one of its sub-objects). Returns -** NULL if no match is found. -** -** ENV means the system environment (getenv()). -** -** Precedence: POST.payload, GET/COOKIE/non-JSON POST, JSON POST, ENV. -** -** FIXME: the precedence SHOULD be: GET, POST.payload, POST, COOKIE, -** ENV, but the amalgamation of the GET/POST vars makes it difficult -** for me to do that. Since fossil only uses one cookie, cookie -** precedence isn't a real/high-priority problem. -*/ -cson_value * json_getenv( char const * zKey ){ - cson_value * rc; - rc = g.json.reqPayload.o - ? cson_object_get( g.json.reqPayload.o, zKey ) - : NULL; - if(rc){ - return rc; - } - rc = cson_object_get( g.json.param.o, zKey ); - if( rc ){ - return rc; - } - rc = cson_object_get( g.json.post.o, zKey ); - if(rc){ - return rc; - }else{ - char const * cv = PD(zKey,NULL); - if(!cv){ - /* reminder to self: in CLI mode i'd like to try - find_option(zKey,NULL,XYZ) here, but we don't have a sane - default for the XYZ param here. - */ - cv = getenv(zKey); - } - if(cv){/*transform it to JSON for later use.*/ - /* use sscanf() to figure out if it's an int, - and transform it to JSON int if it is. - */ - int intVal = -1; - char endOfIntCheck; - int const scanRc = sscanf(cv,"%d%c",&intVal, &endOfIntCheck) - /* The %c bit there is to make sure that we don't accept 123x - as a number. sscanf() returns the number of tokens - successfully parsed, so an RC of 1 will be correct for "123" - but "123x" will have RC==2. - */ - ; - if(1==scanRc){ - json_setenv( zKey, cson_value_new_integer(intVal) ); - }else{ - rc = cson_value_new_string(cv,strlen(cv)); - json_setenv( zKey, rc ); - } - return rc; - } - } - return NULL; -} - -/* -** Wrapper around json_getenv() which... -** -** If it finds a value and that value is-a JSON number or is a string -** which looks like an integer or is-a JSON bool/null then it is -** converted to an int. If none of those apply then dflt is returned. -*/ -int json_getenv_int(char const * pKey, int dflt ){ - cson_value const * v = json_getenv(pKey); - if(!v){ - return dflt; - }else if( cson_value_is_number(v) ){ - return (int)cson_value_get_integer(v); - }else if( cson_value_is_string(v) ){ - char const * sv = cson_string_cstr(cson_value_get_string(v)); - assert( (NULL!=sv) && "This is quite unexpected." ); - return sv ? atoi(sv) : dflt; - }else if( cson_value_is_bool(v) ){ - return cson_value_get_bool(v) ? 1 : 0; - }else if( cson_value_is_null(v) ){ - return 0; - }else{ - /* we should arguably treat JSON null as 0. */ - return dflt; - } -} - - -/* -** Wrapper around json_getenv() which tries to evaluate a payload/env -** value as a boolean. Uses mostly the same logic as -** json_getenv_int(), with the addition that string values which -** either start with a digit 1..9 or the letters [tT] are considered -** to be true. If this function cannot find a matching key/value then -** dflt is returned. e.g. if it finds the key but the value is-a -** Object then dftl is returned. -*/ -char json_getenv_bool(char const * pKey, char dflt ){ - cson_value const * v = json_getenv(pKey); - if(!v){ - return dflt; - }else if( cson_value_is_number(v) ){ - return cson_value_get_integer(v) ? 1 : 0; - }else if( cson_value_is_string(v) ){ - char const * sv = cson_string_cstr(cson_value_get_string(v)); - if(!*sv || ('0'==*sv)){ - return 0; - }else{ - return (('t'==*sv) || ('T'==*sv) - || (('1'<=*sv) && ('9'>=*sv))) - ? 1 : 0; - } - }else if( cson_value_is_bool(v) ){ - return cson_value_get_bool(v) ? 1 : 0; - }else if( cson_value_is_null(v) ){ - return 0; - }else{ - return dflt; - } -} - -/* -** Returns the string form of a json_getenv() value, but ONLY If that -** value is-a String. Non-strings are not converted to strings for -** this purpose. Returned memory is owned by g.json or fossil and is -** valid until end-of-app or the given key is replaced in fossil's -** internals via cgi_replace_parameter() and friends or json_setenv(). -*/ -char const * json_getenv_cstr( char const * zKey ){ - return cson_value_get_cstr( json_getenv(zKey) ); -} - - -/* -** Adds v to g.json.param.o using the given key. May cause any prior -** item with that key to be destroyed (depends on current reference -** count for that value). On success, transfers (or shares) ownership -** of v to (or with) g.json.param.o. On error ownership of v is not -** modified. -*/ -int json_setenv( char const * zKey, cson_value * v ){ - return cson_object_set( g.json.param.o, zKey, v ); -} - -/* -** Guesses a RESPONSE Content-Type value based (primarily) on the -** HTTP_ACCEPT header. -** -** It will try to figure out if the client can support -** application/json or application/javascript, and will fall back to -** text/plain if it cannot figure out anything more specific. -** -** Returned memory is static and immutable. -*/ -char const * json_guess_content_type(){ - char const * cset; - char doUtf8; - cset = PD("HTTP_ACCEPT_CHARSET",NULL); - doUtf8 = ((NULL == cset) || (NULL!=strstr("utf-8",cset))) - ? 1 : 0; - if( g.json.jsonp ){ - return doUtf8 - ? "application/javascript; charset=utf-8" - : "application/javascript"; - }else{ - /* - Content-type - - If the browser does not sent an ACCEPT for application/json - then we fall back to text/plain. - */ - char const * cstr; - cstr = PD("HTTP_ACCEPT",NULL); - if( NULL == cstr ){ - return doUtf8 - ? "application/json; charset=utf-8" - : "application/json"; - }else{ - if( strstr( cstr, "application/json" ) - || strstr( cstr, "*/*" ) ){ - return doUtf8 - ? "application/json; charset=utf-8" - : "application/json"; - }else{ - return "text/plain"; - } - } - } -} - -/* -** Sends pResponse to the output stream as the response object. This -** function does no validation of pResponse except to assert() that it -** is not NULL. The caller is responsible for ensuring that it meets -** API response envelope conventions. -** -** In CLI mode pResponse is sent to stdout immediately. In HTTP -** mode pResponse replaces any current CGI content but cgi_reply() -** is not called to flush the output. -** -** If g.json.jsonp is not NULL then the content type is set to -** application/javascript and the output is wrapped in a jsonp -** wrapper. -*/ -void json_send_response( cson_value const * pResponse ){ - assert( NULL != pResponse ); - if( g.isHTTP ){ - cgi_reset_content(); - if( g.json.jsonp ){ - cgi_printf("%s(",g.json.jsonp); - } - cson_output( pResponse, cson_data_dest_cgi, NULL, &g.json.outOpt ); - if( g.json.jsonp ){ - cgi_append_content(")",1); - } - }else{/*CLI mode*/ - if( g.json.jsonp ){ - fprintf(stdout,"%s(",g.json.jsonp); - } - cson_output_FILE( pResponse, stdout, &g.json.outOpt ); - if( g.json.jsonp ){ - fwrite(")\n", 2, 1, stdout); - } - } -} - -/* -** Returns the current request's JSON authentication token, or NULL if -** none is found. The token's memory is owned by (or shared with) -** g.json. -** -** If an auth token is found in the GET/POST request data then fossil -** is given that data for use in authentication for this -** session. i.e. the GET/POST data overrides fossil's authentication -** cookie value (if any) and also works with clients which do not -** support cookies. -** -** Must be called once before login_check_credentials() is called or -** we will not be able to replace fossil's internal idea of the auth -** info in time (and future changes to that state may cause unexpected -** results). -** -** The result of this call are cached for future calls. -*/ -cson_value * json_auth_token(){ - if( !g.json.authToken ){ - /* Try to get an authorization token from GET parameter, POSTed - JSON, or fossil cookie (in that order). */ - g.json.authToken = json_getenv(FossilJsonKeys.authToken); - if(g.json.authToken - && cson_value_is_string(g.json.authToken) - && !PD(login_cookie_name(),NULL)){ - /* tell fossil to use this login info. - - FIXME?: because the JSON bits don't carry around - login_cookie_name(), there is a potential login hijacking - window here. We may need to change the JSON auth token to be - in the form: login_cookie_name()=... - - Then again, the hardened cookie value helps ensure that - only a proper key/value match is valid. - */ - cgi_replace_parameter( login_cookie_name(), cson_value_get_cstr(g.json.authToken) ); - }else if( g.isHTTP ){ - /* try fossil's conventional cookie. */ - /* Reminder: chicken/egg scenario regarding db access in CLI - mode because login_cookie_name() needs the db. CLI - mode does not use any authentication, so we don't need - to support it here. - */ - char const * zCookie = P(login_cookie_name()); - if( zCookie && *zCookie ){ - /* Transfer fossil's cookie to JSON for downstream convenience... */ - cson_value * v = cson_value_new_string(zCookie, strlen(zCookie)); - if(0 == json_gc_add( FossilJsonKeys.authToken, v, 1 )){ - g.json.authToken = v; - } - } - } - } - return g.json.authToken; -} - -/* -** IFF json.reqPayload.o is not NULL then this returns -** cson_object_get(json.reqPayload.o,pKey), else it returns NULL. -** -** The returned value is owned by (or shared with) json.reqPayload.v. -*/ -cson_value * json_req_payload_get(char const *pKey){ - return g.json.reqPayload.o - ? cson_object_get(g.json.reqPayload.o,pKey) - : NULL; -} - -/* -** Initializes some JSON bits which need to be initialized relatively -** early on. It should only be called from cgi_init() or -** json_cmd_top() (early on in those functions). -** -** Initializes g.json.gc and g.json.param. This code does not (and -** must not) rely on any of the fossil environment having been set -** up. e.g. it must not use cgi_parameter() and friends because this -** must be called before those data are initialized. -*/ -void json_main_bootstrap(){ - cson_value * v; - assert( (NULL == g.json.gc.v) && "cgi_json_bootstrap() was called twice!" ); - - /* g.json.gc is our "garbage collector" - where we put JSON values - which need a long lifetime but don't have a logical parent to put - them in. - */ - v = cson_value_new_object(); - g.json.gc.v = v; - g.json.gc.o = cson_value_get_object(v); - - /* - g.json.param holds the JSONized counterpart of fossil's - cgi_parameter_xxx() family of data. We store them as JSON, as - opposed to using fossil's data directly, because we can retain - full type information for data this way (as opposed to it always - being of type string). - */ - v = cson_value_new_object(); - g.json.param.v = v; - g.json.param.o = cson_value_get_object(v); - json_gc_add("$PARAMS", v, 1); -} - -/* -** Appends a warning object to the (pending) JSON response. -** -** Code must be a FSL_JSON_W_xxx value from the FossilJsonCodes enum. -** -** A Warning object has this JSON structure: -** -** { "code":integer, "text":"string" } -** -** But the text part is optional. -** -** FIXME FIXME FIXME: i am EXPERIMENTALLY using integer codes instead -** of FOSSIL-XXXX codes here. i may end up switching FOSSIL-XXXX -** string-form codes to integers. Let's ask the mailing list for -** opinions... -** -** If msg is non-NULL and not empty then it is used as the "text" -** property's value. It is copied, and need not refer to static -** memory. -** -** CURRENTLY this code only allows a given warning code to be -** added one time, and elides subsequent warnings. The intention -** is to remove that burden from loops which produce warnings. -** -** FIXME: if msg is NULL then use a standard string for -** the given code. If !*msg then elide the "text" property, -** for consistency with how json_err() works. -*/ -void json_warn( int code, char const * msg ){ - cson_value * objV = NULL; - cson_object * obj = NULL; - assert( (code>FSL_JSON_W_START) - && (code 0) - ? (unsigned char)n - : 0; - } - } - g.json.outOpt.indentation = indent; - g.json.outOpt.addNewline = g.isHTTP - ? 0 - : (g.json.jsonp ? 0 : 1); - } - - if( g.isHTTP ){ - json_auth_token()/* will copy our auth token, if any, to fossil's - core, which we need before we call - login_check_credentials(). */; - login_check_credentials()/* populates g.perm */; - } - else{ - db_find_and_open_repository(OPEN_ANY_SCHEMA,0); - } -} - -/* -** Returns the ndx'th item in the "command path", where index 0 is the -** position of the "json" part of the path. Returns NULL if ndx is out -** of bounds or there is no "json" path element. -** -** In CLI mode the "path" is the list of arguments (skipping argv[0]). -** In server/CGI modes the path is taken from PATH_INFO. -** -** The returned bytes are owned by g.json.cmd.v and _may_ be -** invalidated if that object is modified (depending on how it is -** modified). -** -*/ -char const * json_command_arg(unsigned char ndx){ - cson_array * ar = g.json.cmd.a; - assert((NULL!=ar) && "Internal error. Was json_mode_bootstrap() called?"); - assert((g.argc>1) && "Internal error - we never should have gotten this far."); - if( g.json.cmd.offset < 0 ){ - /* first-time setup. */ - short i = 0; -#define NEXT cson_string_cstr( \ - cson_value_get_string( \ - cson_array_get(ar,i) \ - )) - char const * tok = NEXT; - while( tok ){ - if( !g.isHTTP/*workaround for "abbreviated name" in CLI mode*/ - ? (0==strcmp(g.argv[1],tok)) - : (0==strncmp("json",tok,4)) - ){ - g.json.cmd.offset = i; - break; - } - ++i; - tok = NEXT; - } - } -#undef NEXT - if(g.json.cmd.offset < 0){ - return NULL; - }else{ - ndx = g.json.cmd.offset + ndx; - return cson_string_cstr(cson_value_get_string(cson_array_get( ar, g.json.cmd.offset + ndx ))); - } -} - -/* -** If g.json.reqPayload.o is NULL then NULL is returned, else the -** given property is searched for in the request payload. If found it -** is returned. The returned value is owned by (or shares ownership -** with) g.json, and must NOT be cson_value_free()'d by the -** caller. -*/ -cson_value * json_payload_property( char const * key ){ - return g.json.reqPayload.o ? - cson_object_get( g.json.reqPayload.o, key ) - : NULL; -} - - -/* Returns the C-string form of json_auth_token(), or NULL -** if json_auth_token() returns NULL. -*/ -char const * json_auth_token_cstr(){ - return cson_value_get_cstr( json_auth_token() ); -} - -/* -** Returns the JsonPageDef with the given name, or NULL if no match is -** found. -** -** head must be a pointer to an array of JsonPageDefs in which the -** last entry has a NULL name. -*/ -JsonPageDef const * json_handler_for_name( char const * name, JsonPageDef const * head ){ - JsonPageDef const * pageDef = head; - assert( head != NULL ); - if(name && *name) for( ; pageDef->name; ++pageDef ){ - if( 0 == strcmp(name, pageDef->name) ){ - return pageDef; - } - } - return NULL; -} - -/* -** Given a Fossil/JSON result code, this function "dumbs it down" -** according to the current value of g.json.errorDetailParanoia. The -** dumbed-down value is returned. -** -** This function assert()s that code is in the inclusive range 0 to -** 9999. -** -** Note that WARNING codes (1..999) are never dumbed down. -** -*/ -static int json_dumbdown_rc( int code ){ - if(!code || ((code>FSL_JSON_W_START) && (code>FSL_JSON_W_END))){ - return code; - }else{ - int modulo = 0; - assert((code >= 1000) && (code <= 9999) && "Invalid Fossil/JSON code."); - switch( g.json.errorDetailParanoia ){ - case 1: modulo = 10; break; - case 2: modulo = 100; break; - case 3: modulo = 1000; break; - default: break; - } - if( modulo ) code = code - (code % modulo); - return code; - } -} - -/* -** Convenience routine which converts a Julian time value into a Unix -** Epoch timestamp. Requires the db, so this cannot be used before the -** repo is opened (will trigger a fatal error in db_xxx()). -*/ -cson_value * json_julian_to_timestamp(double j){ - return cson_value_new_integer((cson_int_t) - db_int64(0,"SELECT strftime('%%s',%lf)",j) - ); -} - -/* -** Returns a timestamp value. -*/ -cson_int_t json_timestamp(){ - return (cson_int_t)time(0); -} -/* -** Returns a new JSON value (owned by the caller) representing -** a timestamp. If timeVal is < 0 then time(0) is used to fetch -** the time, else timeVal is used as-is -*/ -cson_value * json_new_timestamp(cson_int_t timeVal){ - return cson_value_new_integer((timeVal<0) ? (cson_int_t)time(0) : timeVal); -} - -/* -** Creates a new Fossil/JSON response envelope skeleton. It is owned -** by the caller, who must eventually free it using cson_value_free(), -** or add it to a cson container to transfer ownership. Returns NULL -** on error. -** -** If payload is not NULL and resultCode is 0 then it is set as the -** "payload" property of the returned object. If resultCode is -** non-zero and payload is not NULL then this function calls -** cson_value_free(payload) and does not insert the payload into the -** response. In either case, onwership of payload is transfered to -** this function. -** -** pMsg is an optional message string property (resultText) of the -** response. If resultCode is non-0 and pMsg is NULL then -** json_err_str() is used to get the error string. The caller may -** provide his own or may use an empty string to suppress the -** resultText property. -** -*/ -cson_value * json_create_response( int resultCode, - char const * pMsg, - cson_value * payload){ - cson_value * v = NULL; - cson_value * tmp = NULL; - cson_object * o = NULL; - int rc; - resultCode = json_dumbdown_rc(resultCode); - v = cson_value_new_object(); - o = cson_value_get_object(v); - if( ! o ) return NULL; -#define SET(K) if(!tmp) goto cleanup; \ - rc = cson_object_set( o, K, tmp ); \ - if(rc) do{\ - cson_value_free(tmp); \ - tmp = NULL; \ - goto cleanup; \ - }while(0) - - tmp = cson_value_new_string(MANIFEST_UUID,strlen(MANIFEST_UUID)); - SET("fossil"); - - {/* timestamp */ - tmp = json_new_timestamp(-1); - SET(FossilJsonKeys.timestamp); - } - if( 0 != resultCode ){ - if( ! pMsg ) pMsg = json_err_str(resultCode); - tmp = json_rc_string(resultCode); - SET(FossilJsonKeys.resultCode); - } - if( pMsg && *pMsg ){ - tmp = cson_value_new_string(pMsg,strlen(pMsg)); - SET(FossilJsonKeys.resultText); - } - tmp = json_getenv(FossilJsonKeys.requestId); - if( tmp ) cson_object_set( o, FossilJsonKeys.requestId, tmp ); - - if(0){/* these are only intended for my own testing...*/ - if(g.json.cmd.v){ - tmp = g.json.cmd.v; - SET("$commandPath"); - } - if(g.json.param.v){ - tmp = g.json.param.v; - SET("$params"); - } - if(0){/*Only for debuggering, add some info to the response.*/ - tmp = cson_value_new_integer( g.json.cmd.offset ); - cson_object_set( o, "cmd.offset", tmp ); - cson_object_set( o, "isCGI", cson_value_new_bool( g.isHTTP ) ); - } - } - - if(HAS_TIMER){ - /* This is, philosophically speaking, not quite the right place - for ending the timer, but this is the one function which all of - the JSON exit paths use (and they call it after processing, - just before they end). - */ - double span; - span = END_TIMER; - /* i'm actually seeing sub-ms runtimes in some tests, but a time of - 0 is "just wrong", so we'll bump that up to 1ms. - */ - cson_object_set(o,"procTimeMs", cson_value_new_integer((cson_int_t)((span>0.0)?span:1))); - } - if(g.json.warnings.v){ - tmp = g.json.warnings.v; - SET("warnings"); - } - - /* Only add the payload to SUCCESS responses. Else delete it. */ - if( NULL != payload ){ - if( resultCode ){ - cson_value_free(payload); - payload = NULL; - }else{ - tmp = payload; - SET(FossilJsonKeys.payload); - } - } - -#undef SET - goto ok; - cleanup: - cson_value_free(v); - v = NULL; - ok: - return v; -} - -/* -** Outputs a JSON error response to either the cgi_xxx() family of -** buffers (in CGI/server mode) or stdout (in CLI mode). If rc is 0 -** then g.json.resultCode is used. If that is also 0 then the "Unknown -** Error" code is used. -** -** If g.isHTTP then the generated JSON error response object replaces -** any currently buffered page output. Because the output goes via -** the cgi_xxx() family of functions, this function inherits any -** compression which fossil does for its output. -** -** If alsoOutput is true AND g.isHTTP then cgi_reply() is called to -** flush the output (and headers). Generally only do this if you are -** about to call exit(). -** -** !g.isHTTP then alsoOutput is ignored and all output is sent to -** stdout immediately. -** -*/ -void json_err( int code, char const * msg, char alsoOutput ){ - int rc = code ? code : (g.json.resultCode - ? g.json.resultCode - : FSL_JSON_E_UNKNOWN); - cson_value * resp = NULL; - rc = json_dumbdown_rc(rc); - if( rc && !msg ){ - msg = json_err_str(rc); - } - resp = json_create_response(rc, msg, NULL); - if(!resp){ - /* about the only error case here is out-of-memory. DO NOT - call fossil_panic() here because that calls this function. - */ - fprintf(stderr, "%s: Fatal error: could not allocate " - "response object.\n", fossil_nameofexe()); - fossil_exit(1); - } - if( g.isHTTP ){ - if(alsoOutput){ - json_send_response(resp); - }else{ - /* almost a duplicate of json_send_response() :( */ - cgi_set_content_type("application/javascript"); - cgi_reset_content(); - if( g.json.jsonp ){ - cgi_printf("%s(",g.json.jsonp); - } - cson_output( resp, cson_data_dest_cgi, NULL, &g.json.outOpt ); - if( g.json.jsonp ){ - cgi_append_content(")",1); - } - } - }else{ - json_send_response(resp); - } - cson_value_free(resp); -} - - -/* -** Iterates through a prepared SELECT statement and converts each row -** to a JSON object. If pTgt is not NULL then it must be-a Array -** object and this function will return pTgt. If pTgt is NULL then a -** new Array object is created and returned (owned by the -** caller). Each row of pStmt is converted to an Object and appended -** to the array. -*/ -cson_value * json_stmt_to_array_of_obj(Stmt *pStmt, - cson_value * pTgt){ - cson_value * v = pTgt ? pTgt : cson_value_new_array(); - cson_array * a = cson_value_get_array(pTgt ? pTgt : v); - char const * warnMsg = NULL; - assert( NULL != a ); - while( (SQLITE_ROW==db_step(pStmt)) ){ - cson_value * row = cson_sqlite3_row_to_object(pStmt->pStmt); - if(!row && !warnMsg){ - warnMsg = "Could not convert at least one result row to JSON."; - continue; - } - cson_array_append(a, row); - } - if(warnMsg){ - json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED, warnMsg ); - } - return v; -} - -/* -** /json/version implementation. -** -** Returns the payload object (owned by the caller). -*/ -cson_value * json_page_version(){ - cson_value * jval = NULL; - cson_object * jobj = NULL; - jval = cson_value_new_object(); - jobj = cson_value_get_object(jval); -#define FSET(X,K) cson_object_set( jobj, K, cson_value_new_string(X,strlen(X))) - FSET(MANIFEST_UUID,"manifestUuid"); - FSET(MANIFEST_VERSION,"manifestVersion"); - FSET(MANIFEST_DATE,"manifestDate"); - FSET(MANIFEST_YEAR,"manifestYear"); - FSET(RELEASE_VERSION,"releaseVersion"); -#undef FSET - cson_object_set( jobj, "releaseVersionNumber", - cson_value_new_integer(RELEASE_VERSION_NUMBER) ); - cson_object_set( jobj, "resultCodeParanoiaLevel", - cson_value_new_integer(g.json.errorDetailParanoia) ); - return jval; -} - - -/* -** Implementation for /json/cap -** -** Returned object contains details about the "capabilities" of the -** current user (what he may/may not do). -** -** This is primarily intended for debuggering, but may have -** a use in client code. (?) -*/ -cson_value * json_page_cap(){ - cson_value * payload = cson_value_new_object(); - cson_value * sub = cson_value_new_object(); - Stmt q; - cson_object * obj = cson_value_get_object(payload); - db_prepare(&q, "SELECT login, cap FROM user WHERE uid=%d", g.userUid); - if( db_step(&q)==SQLITE_ROW ){ - /* reminder: we don't use g.zLogin because it's 0 for the guest - user and the HTML UI appears to currently allow the name to be - changed (but doing so would break other code). */ - char const * str = (char const *)sqlite3_column_text(q.pStmt,0); - if( str ){ - cson_object_set( obj, "userName", - cson_value_new_string(str,strlen(str)) ); - } - str = (char const *)sqlite3_column_text(q.pStmt,1); - if( str ){ - cson_object_set( obj, "capabilities", - cson_value_new_string(str,strlen(str)) ); - } - } - db_finalize(&q); - cson_object_set( obj, "permissionFlags", sub ); - obj = cson_value_get_object(sub); - -#define ADD(X,K) cson_object_set(obj, K, cson_value_new_bool(g.perm.X)) - ADD(Setup,"setup"); - ADD(Admin,"admin"); - ADD(Delete,"delete"); - ADD(Password,"password"); - ADD(Query,"query"); /* don't think this one is actually used */ - ADD(Write,"checkin"); - ADD(Read,"checkout"); - ADD(History,"history"); - ADD(Clone,"clone"); - ADD(RdWiki,"readWiki"); - ADD(NewWiki,"createWiki"); - ADD(ApndWiki,"appendWiki"); - ADD(WrWiki,"editWiki"); - ADD(RdTkt,"readTicket"); - ADD(NewTkt,"createTicket"); - ADD(ApndTkt,"appendTicket"); - ADD(WrTkt,"editTicket"); - ADD(Attach,"attachFile"); - ADD(TktFmt,"createTicketReport"); - ADD(RdAddr,"readPrivate"); - ADD(Zip,"zip"); - ADD(Private,"xferPrivate"); -#undef ADD - return payload; -} - -/* -** Implementation of the /json/stat page/command. -** -*/ -cson_value * json_page_stat(){ - i64 t, fsize; - int n, m; - int full; - const char *zDb; - enum { BufLen = 1000 }; - char zBuf[BufLen]; - cson_value * jv = NULL; - cson_object * jo = NULL; - cson_value * jv2 = NULL; - cson_object * jo2 = NULL; - if( !g.perm.Read ){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - if( g.isHTTP ){ - full = json_getenv_bool("full",0); - }else{ - full = (0!=find_option("full","f",0)); - } -#define SETBUF(O,K) cson_object_set(O, K, cson_value_new_string(zBuf, strlen(zBuf))); - - jv = cson_value_new_object(); - jo = cson_value_get_object(jv); - - sqlite3_snprintf(BufLen, zBuf, db_get("project-name","")); - SETBUF(jo, "projectName"); - /* FIXME: don't include project-description until we ensure that - zBuf will always be big enough. We "should" replace zBuf - with a blob for this purpose. - */ - fsize = file_size(g.zRepositoryName); - cson_object_set(jo, "repositorySize", cson_value_new_integer((cson_int_t)fsize)); - - if(full){ - n = db_int(0, "SELECT count(*) FROM blob"); - m = db_int(0, "SELECT count(*) FROM delta"); - cson_object_set(jo, "blobCount", cson_value_new_integer((cson_int_t)n)); - cson_object_set(jo, "deltaCount", cson_value_new_integer((cson_int_t)m)); - if( n>0 ){ - int a, b; - Stmt q; - db_prepare(&q, "SELECT total(size), avg(size), max(size)" - " FROM blob WHERE size>0"); - db_step(&q); - t = db_column_int64(&q, 0); - cson_object_set(jo, "uncompressedArtifactSize", - cson_value_new_integer((cson_int_t)t)); - cson_object_set(jo, "averageArtifactSize", - cson_value_new_integer((cson_int_t)db_column_int(&q, 1))); - cson_object_set(jo, "maxArtifactSize", - cson_value_new_integer((cson_int_t)db_column_int(&q, 2))); - db_finalize(&q); - if( t/fsize < 5 ){ - b = 10; - fsize /= 10; - }else{ - b = 1; - } - a = t/fsize; - sqlite3_snprintf(BufLen,zBuf, "%d:%d", a, b); - SETBUF(jo, "compressionRatio"); - } - n = db_int(0, "SELECT count(distinct mid) FROM mlink /*scan*/"); - cson_object_set(jo, "checkinCount", cson_value_new_integer((cson_int_t)n)); - n = db_int(0, "SELECT count(*) FROM filename /*scan*/"); - cson_object_set(jo, "fileCount", cson_value_new_integer((cson_int_t)n)); - n = db_int(0, "SELECT count(*) FROM tag /*scan*/" - " WHERE +tagname GLOB 'wiki-*'"); - cson_object_set(jo, "wikiPageCount", cson_value_new_integer((cson_int_t)n)); - n = db_int(0, "SELECT count(*) FROM tag /*scan*/" - " WHERE +tagname GLOB 'tkt-*'"); - cson_object_set(jo, "ticketCount", cson_value_new_integer((cson_int_t)n)); - }/*full*/ - n = db_int(0, "SELECT julianday('now') - (SELECT min(mtime) FROM event)" - " + 0.99"); - cson_object_set(jo, "ageDays", cson_value_new_integer((cson_int_t)n)); - cson_object_set(jo, "ageYears", cson_value_new_double(n/365.24)); - sqlite3_snprintf(BufLen, zBuf, db_get("project-code","")); - SETBUF(jo, "projectCode"); - sqlite3_snprintf(BufLen, zBuf, db_get("server-code","")); - SETBUF(jo, "serverCode"); - cson_object_set(jo, "compiler", cson_value_new_string(COMPILER_NAME, strlen(COMPILER_NAME))); - - jv2 = cson_value_new_object(); - jo2 = cson_value_get_object(jv2); - cson_object_set(jo, "sqlite", jv2); - sqlite3_snprintf(BufLen, zBuf, "%.19s [%.10s] (%s)", - SQLITE_SOURCE_ID, &SQLITE_SOURCE_ID[20], SQLITE_VERSION); - SETBUF(jo2, "version"); - zDb = db_name("repository"); - cson_object_set(jo2, "pageCount", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA %s.page_count", zDb))); - cson_object_set(jo2, "pageSize", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA %s.page_size", zDb))); - cson_object_set(jo2, "freeList", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA %s.freelist_count", zDb))); - sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0, "PRAGMA %s.encoding", zDb)); - SETBUF(jo2, "encoding"); - sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0, "PRAGMA %s.journal_mode", zDb)); - cson_object_set(jo2, "journalMode", *zBuf ? cson_value_new_string(zBuf, strlen(zBuf)) : cson_value_null()); - return jv; -#undef SETBUF -} - - - -static cson_value * json_user_list(); -static cson_value * json_user_get(); -#if 0 -static cson_value * json_user_create(); -static cson_value * json_user_edit(); -#endif - -/* -** Mapping of /json/user/XXX commands/paths to callbacks. -*/ -static const JsonPageDef JsonPageDefs_User[] = { -{"create", json_page_nyi, 1}, -{"edit", json_page_nyi, 1}, -{"get", json_user_get, 0}, -{"list", json_user_list, 0}, -/* Last entry MUST have a NULL name. */ -{NULL,NULL,0} -}; - - -cson_value * json_page_dispatch_helper(JsonPageDef const * pages){ - JsonPageDef const * def; - char const * cmd = json_command_arg(1+g.json.dispatchDepth); - assert( NULL != pages ); - if( ! cmd ){ - g.json.resultCode = FSL_JSON_E_MISSING_ARGS; - return NULL; - } - def = json_handler_for_name( cmd, pages ); - if(!def){ - g.json.resultCode = FSL_JSON_E_UNKNOWN_COMMAND; - return NULL; - } - else{ - ++g.json.dispatchDepth; - return (*def->func)(); - } -} - -/* -** Implements the /json/user family of pages/commands. -** -*/ -static cson_value * json_page_user(){ - return json_page_dispatch_helper(&JsonPageDefs_User[0]); -} - -static cson_value * json_branch_list(); -/* -** Mapping of /json/branch/XXX commands/paths to callbacks. -*/ -static const JsonPageDef JsonPageDefs_Branch[] = { -{"list", json_branch_list, 0}, -{"create", json_page_nyi, 1}, -/* Last entry MUST have a NULL name. */ -{NULL,NULL,0} -}; - -/* -** Implements the /json/branch family of pages/commands. Far from -** complete. -** -*/ -static cson_value * json_page_branch(){ - return json_page_dispatch_helper(&JsonPageDefs_Branch[0]); -} - -/* -** Impl for /json/branch/list -** -** -** CLI mode options: -** -** --range X | -r X, where X is one of (open,closed,all) -** (only the first letter is significant, default=open). -** -a (same as --range a) -** -c (same as --range c) -** -** HTTP mode options: -** -** "range" GET/POST.payload parameter. FIXME: currently we also use -** POST, but really want to restrict this to POST.payload. -*/ -static cson_value * json_branch_list(){ - cson_value * payV; - cson_object * pay; - cson_value * listV; - cson_array * list; - char const * range = NULL; - int which = 0; - char * sawConversionError = NULL; - Stmt q; - if( !g.perm.Read ){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - payV = cson_value_new_object(); - pay = cson_value_get_object(payV); - listV = cson_value_new_array(); - list = cson_value_get_array(listV); - if(!g.isHTTP){ - range = find_option("range","r",1); - if(!range||!*range){ - range = find_option("all","a",0); - if(range && *range){ - range = "a"; - }else{ - range = find_option("closed","c",0); - if(range&&*range){ - range = "c"; - } - } - } - }else{ - range = json_getenv_cstr("range"); - } - if(!range || !*range){ - range = "o"; - } - assert( (NULL != range) && *range ); - switch(*range){ - case 'c': - range = "closed"; - which = -1; - break; - case 'a': - range = "all"; - which = 1; - break; - default: - range = "open"; - which = 0; - break; - }; - cson_object_set(pay,"range",cson_value_new_string(range,strlen(range))); - - if( g.localOpen ){ /* add "current" property (branch name). */ - int vid = db_lget_int("checkout", 0); - char const * zCurrent = vid - ? db_text(0, "SELECT value FROM tagxref" - " WHERE rid=%d AND tagid=%d", - vid, TAG_BRANCH) - : 0; - if(zCurrent){ - cson_object_set(pay,"current",json_new_string(zCurrent)); - } - } - - - branch_prepare_list_query(&q, which); - cson_object_set(pay,"branches",listV); - while((SQLITE_ROW==db_step(&q))){ - cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0); - if(v){ - cson_array_append(list,v); - }else if(!sawConversionError){ - sawConversionError = mprintf("Column-to-json failed @ %s:%d", - __FILE__,__LINE__); - } - } - if( sawConversionError ){ - json_warn(FSL_JSON_W_COL_TO_JSON_FAILED,sawConversionError); - free(sawConversionError); -} - return payV; -} - -/* -** Impl of /json/rebuild. Requires admin previleges. -*/ -static cson_value * json_page_rebuild(){ - if( !g.perm.Admin ){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - }else{ - /* Reminder: the db_xxx() ops "should" fail via - the fossil core error handlers, which will cause - a JSON error and exit(). i.e. we don't handle - the errors here. TODO: confirm that all these - db routine fail gracefully in JSON mode. - */ - db_close(1); - db_open_repository(g.zRepositoryName); - db_begin_transaction(); - rebuild_db(0, 0, 0); - db_end_transaction(0); - return NULL; - } -} - -/* -** Impl of /json/user/list. Requires admin rights. -*/ -static cson_value * json_user_list(){ - cson_value * payV = NULL; - Stmt q; - if(!g.perm.Admin){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - db_prepare(&q,"SELECT uid AS uid," - " login AS name," - " cap AS capabilities," - " info AS info," - " mtime AS mtime" - " FROM user ORDER BY login"); - payV = json_stmt_to_array_of_obj(&q, NULL); - db_finalize(&q); - if(NULL == payV){ - g.json.resultCode = FSL_JSON_E_UNKNOWN; - } - return payV; -} - -/* -** Impl of /json/user/get. Requires admin rights. -*/ -static cson_value * json_user_get(){ - cson_value * payV = NULL; - char const * pUser = NULL; - Stmt q; - if(!g.perm.Admin){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - pUser = json_command_arg(g.json.dispatchDepth+1); - if( g.isHTTP && (!pUser || !*pUser) ){ - pUser = json_getenv_cstr("name") - /* ACHTUNG: fossil apparently internally sets name=user/get/XYZ - if we pass the name as part of the path, so we check the path - _before_ checking for name=XYZ. - */; - } - if(!pUser || !*pUser){ - g.json.resultCode = FSL_JSON_E_MISSING_ARGS; - return NULL; - } - db_prepare(&q,"SELECT uid AS uid," - " login AS name," - " cap AS capabilities," - " info AS info," - " mtime AS mtime" - " FROM user" - " WHERE login=%Q", - pUser); - if( (SQLITE_ROW == db_step(&q)) ){ - payV = cson_sqlite3_row_to_object(q.pStmt); - if(!payV){ - g.json.resultCode = FSL_JSON_E_UNKNOWN; - } - }else{ - g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; - } - db_finalize(&q); - return payV; -} - -/* Impl in json_login.c. */ -cson_value * json_page_anon_password(); -/* Impl in json_login.c. */ -cson_value * json_page_login(); -/* Impl in json_login.c. */ -cson_value * json_page_logout(); - -/* -** Mapping of names to JSON pages/commands. Each name is a subpath of -** /json (in CGI mode) or a subcommand of the json command in CLI mode -*/ -static const JsonPageDef JsonPageDefs[] = { -/* please keep alphabetically sorted (case-insensitive) for maintenance reasons. */ -{"anonymousPassword",json_page_anon_password, 1}, -{"branch", json_page_branch,0}, -{"cap", json_page_cap, 0}, -{"dir", json_page_nyi, 0}, -{"HAI",json_page_version,0}, -{"login",json_page_login,1}, -{"logout",json_page_logout,1}, -{"rebuild",json_page_rebuild,0}, -{"stat",json_page_stat,0}, -{"tag", json_page_nyi,0}, -{"ticket", json_page_nyi,0}, -{"timeline", json_page_timeline,0}, -{"user",json_page_user,0}, -{"version",json_page_version,0}, -{"whoami",json_page_whoami,1/*FIXME: work in CLI mode*/}, -{"wiki",json_page_wiki,0}, -/* Last entry MUST have a NULL name. */ -{NULL,NULL,0} -}; - - -/* -** Mapping of /json/ticket/XXX commands/paths to callbacks. -*/ -static const JsonPageDef JsonPageDefs_Ticket[] = { -{"get", json_page_nyi, 0}, -{"list", json_page_nyi, 0}, -{"save", json_page_nyi, 1}, -{"create", json_page_nyi, 1}, -/* Last entry MUST have a NULL name. */ -{NULL,NULL,0} -}; - -/* -** Mapping of /json/artifact/XXX commands/paths to callbacks. -*/ -static const JsonPageDef JsonPageDefs_Artifact[] = { -{"vinfo", json_page_nyi, 0}, -{"finfo", json_page_nyi, 0}, -/* Last entry MUST have a NULL name. */ -{NULL,NULL,0} -}; - -/* -** Mapping of /json/tag/XXX commands/paths to callbacks. -*/ -static const JsonPageDef JsonPageDefs_Tag[] = { -{"list", json_page_nyi, 0}, -{"create", json_page_nyi, 1}, -/* Last entry MUST have a NULL name. */ -{NULL,NULL,0} -}; - - -/* -** WEBPAGE: json -** -** Pages under /json/... must be entered into JsonPageDefs. -** This function dispatches them, and is the HTTP equivalent of -** json_cmd_top(). -*/ -void json_page_top(void){ - int rc = FSL_JSON_E_UNKNOWN_COMMAND; - char const * cmd; - cson_value * payload = NULL; - JsonPageDef const * pageDef = NULL; - BEGIN_TIMER; - json_mode_bootstrap(); - cmd = json_command_arg(1); - /*cgi_printf("{\"cmd\":\"%s\"}\n",cmd); return;*/ - pageDef = json_handler_for_name(cmd,&JsonPageDefs[0]); - if( ! pageDef ){ - json_err( FSL_JSON_E_UNKNOWN_COMMAND, NULL, 0 ); - return; - }else if( pageDef->runMode < 0 /*CLI only*/){ - rc = FSL_JSON_E_WRONG_MODE; - }else{ - rc = 0; - g.json.dispatchDepth = 1; - payload = (*pageDef->func)(); - } - if( g.json.resultCode ){ - json_err(g.json.resultCode, NULL, 0); - }else{ - cson_value * root = json_create_response(rc, NULL, payload); - json_send_response(root); - cson_value_free(root); - } -} - -/* -** This function dispatches json commands and is the CLI equivalent of -** json_page_top(). -** -** COMMAND: json -** -** Usage: %fossil json SUBCOMMAND -** -** The commands include: -** -** cap -** stat -** version (alias: HAI) -** -** -** TODOs: -** -** branch -** tag -** ticket -** timeline -** wiki -** ... -** -*/ -void json_cmd_top(void){ - char const * cmd = NULL; - int rc = FSL_JSON_E_UNKNOWN_COMMAND; - cson_value * payload = NULL; - JsonPageDef const * pageDef; - BEGIN_TIMER; - memset( &g.perm, 0xff, sizeof(g.perm) ) - /* In CLI mode fossil does not use permissions - and they all default to false. We enable them - here because (A) fossil doesn't use them in local - mode but (B) having them set gives us one less - difference in the CLI/CGI/Server-mode JSON - handling. - */ - ; - json_main_bootstrap(); - json_mode_bootstrap(); - if( g.argc<3 ){ - goto usage; - } - db_find_and_open_repository(0, 0); -#if 0 - json_warn(FSL_JSON_W_ROW_TO_JSON_FAILED, "Just testing."); - json_warn(FSL_JSON_W_ROW_TO_JSON_FAILED, "Just testing again."); -#endif - cmd = json_command_arg(1); - if( !cmd || !*cmd ){ - goto usage; - } - pageDef = json_handler_for_name(cmd,&JsonPageDefs[0]); - if( ! pageDef ){ - json_err( FSL_JSON_E_UNKNOWN_COMMAND, NULL, 1 ); - return; - }else if( pageDef->runMode > 0 /*HTTP only*/){ - rc = FSL_JSON_E_WRONG_MODE; - }else{ - rc = 0; - g.json.dispatchDepth = 1; - payload = (*pageDef->func)(); - } - if( g.json.resultCode ){ - json_err(g.json.resultCode, NULL, 1); - }else{ - payload = json_create_response(rc, NULL, payload); - json_send_response(payload); - cson_value_free( payload ); - if((0 != rc) && !g.isHTTP){ - /* FIXME: we need a way of passing this error back - up to the routine which called this callback. - e.g. add g.errCode. - */ - fossil_exit(1); - } - } - return; - usage: - usage("subcommand"); -} - -#undef BITSET_BYTEFOR -#undef BITSET_SET -#undef BITSET_UNSET -#undef BITSET_GET -#undef BITSET_TOGGLE DELETED src/json_detail.h Index: src/json_detail.h ================================================================== --- src/json_detail.h +++ /dev/null @@ -1,187 +0,0 @@ -#if !defined(FOSSIL_JSON_DETAIL_H_INCLUDED) -#define FOSSIL_JSON_DETAIL_H_INCLUDED - -#include "cson_amalgamation.h" -/* -** Impl details for the JSON API which need to be shared -** across multiple C files. -*/ - -/* -** The "official" list of Fossil/JSON error codes. Their values might -** very well change during initial development but after their first -** public release they must stay stable. -** -** Values must be in the range 1000..9999 for error codes and 1..999 -** for warning codes. -** -** Numbers evenly dividable by 100 are "categories", and error codes -** for a given category have their high bits set to the category -** value. -** -*/ -enum FossilJsonCodes { -FSL_JSON_W_START = 0, -FSL_JSON_W_UNKNOWN = FSL_JSON_W_START + 1, -FSL_JSON_W_ROW_TO_JSON_FAILED = FSL_JSON_W_START + 2, -FSL_JSON_W_COL_TO_JSON_FAILED = FSL_JSON_W_START + 3, -FSL_JSON_W_STRING_TO_ARRAY_FAILED = FSL_JSON_W_START + 4, - -FSL_JSON_W_END = 1000, -FSL_JSON_E_GENERIC = 1000, -FSL_JSON_E_GENERIC_SUB1 = FSL_JSON_E_GENERIC + 100, -FSL_JSON_E_INVALID_REQUEST = FSL_JSON_E_GENERIC_SUB1 + 1, -FSL_JSON_E_UNKNOWN_COMMAND = FSL_JSON_E_GENERIC_SUB1 + 2, -FSL_JSON_E_UNKNOWN = FSL_JSON_E_GENERIC_SUB1 + 3, -FSL_JSON_E_RESOURCE_NOT_FOUND = FSL_JSON_E_GENERIC_SUB1 + 4, -FSL_JSON_E_TIMEOUT = FSL_JSON_E_GENERIC_SUB1 + 5, -FSL_JSON_E_ASSERT = FSL_JSON_E_GENERIC_SUB1 + 6, -FSL_JSON_E_ALLOC = FSL_JSON_E_GENERIC_SUB1 + 7, -FSL_JSON_E_NYI = FSL_JSON_E_GENERIC_SUB1 + 8, -FSL_JSON_E_PANIC = FSL_JSON_E_GENERIC_SUB1 + 9, - -FSL_JSON_E_AUTH = 2000, -FSL_JSON_E_MISSING_AUTH = FSL_JSON_E_AUTH + 1, -FSL_JSON_E_DENIED = FSL_JSON_E_AUTH + 2, -FSL_JSON_E_WRONG_MODE = FSL_JSON_E_AUTH + 3, -FSL_JSON_E_RESOURCE_ALREADY_EXISTS = FSL_JSON_E_AUTH + 4, - -FSL_JSON_E_LOGIN_FAILED = FSL_JSON_E_AUTH + 100, -FSL_JSON_E_LOGIN_FAILED_NOSEED = FSL_JSON_E_LOGIN_FAILED + 1, -FSL_JSON_E_LOGIN_FAILED_NONAME = FSL_JSON_E_LOGIN_FAILED + 2, -FSL_JSON_E_LOGIN_FAILED_NOPW = FSL_JSON_E_LOGIN_FAILED + 3, -FSL_JSON_E_LOGIN_FAILED_NOTFOUND = FSL_JSON_E_LOGIN_FAILED + 4, - -FSL_JSON_E_USAGE = 3000, -FSL_JSON_E_INVALID_ARGS = FSL_JSON_E_USAGE + 1, -FSL_JSON_E_MISSING_ARGS = FSL_JSON_E_USAGE + 2, - -FSL_JSON_E_DB = 4000, -FSL_JSON_E_STMT_PREP = FSL_JSON_E_DB + 1, -FSL_JSON_E_STMT_BIND = FSL_JSON_E_DB + 2, -FSL_JSON_E_STMT_EXEC = FSL_JSON_E_DB + 3, -FSL_JSON_E_DB_LOCKED = FSL_JSON_E_DB + 4, - -FSL_JSON_E_DB_NEEDS_REBUILD = FSL_JSON_E_DB + 101 - -}; - - -/* -** Signature for JSON page/command callbacks. Each callback is -** responsible for handling one JSON request/command and/or -** dispatching to sub-commands. -** -** By the time the callback is called, json_page_top() (HTTP mode) or -** json_cmd_top() (CLI mode) will have set up the JSON-related -** environment. Implementations may generate a "result payload" of any -** JSON type by returning its value from this function (ownership is -** tranferred to the caller). On error they should set -** g.json.resultCode to one of the FossilJsonCodes values and return -** either their payload object or NULL. Note that NULL is a legal -** success value - it simply means the response will contain no -** payload. If g.json.resultCode is non-zero when this function -** returns then the top-level dispatcher will destroy any payload -** returned by this function and will output a JSON error response -** instead. -** -** All of the setup/response code is handled by the top dispatcher -** functions and the callbacks concern themselves only with generating -** the payload. -** -** It is imperitive that NO callback functions EVER output ANYTHING to -** stdout, as that will effectively corrupt any JSON output, and -** almost certainly will corrupt any HTTP response headers. Output -** sent to stderr ends up in my apache log, so that might be useful -** for debuggering in some cases, but so such code should be left -** enabled for non-debuggering builds. -*/ -typedef cson_value * (*fossil_json_f)(); - -/* -** Holds name-to-function mappings for JSON page/command dispatching. -** -*/ -typedef struct JsonPageDef{ - /* - ** The commmand/page's name (path, not including leading /json/). - ** - ** Reminder to self: we cannot use sub-paths with commands this way - ** without additional string-splitting downstream. e.g. foo/bar. - ** Alternately, we can create different JsonPageDef arrays for each - ** subset. - */ - char const * name; - /* - ** Returns a payload object for the response. If it returns a - ** non-NULL value, the caller owns it. To trigger an error this - ** function should set g.json.resultCode to a value from the - ** FossilJsonCodes enum. If it sets an error value and returns - ** a payload, the payload will be destroyed (not sent with the - ** response). - */ - fossil_json_f func; - /* - ** Which mode(s) of execution does func() support: - ** - ** <0 = CLI only, >0 = HTTP only, 0==both - */ - char runMode; -} JsonPageDef; - -/* -** Holds common keys used for various JSON API properties. -*/ -typedef struct FossilJsonKeys_{ - /** maintainers: please keep alpha sorted (case-insensitive) */ - char const * anonymousSeed; - char const * authToken; - char const * commandPath; - char const * payload; - char const * requestId; - char const * resultCode; - char const * resultText; - char const * timestamp; -} FossilJsonKeys_; -const FossilJsonKeys_ FossilJsonKeys; - -/* -** A page/command dispatch helper for fossil_json_f() implementations. -** pages must be an array of JsonPageDef commands which we can -** dispatch. The final item in the array MUST have a NULL name -** element. -** -** This function takes the command specified in -** json_comand_arg(1+g.json.dispatchDepth) and searches pages for a -** matching name. If found then that page's func() is called to fetch -** the payload, which is returned to the caller. -** -** On error, g.json.resultCode is set to one of the FossilJsonCodes -** values and NULL is returned. If non-NULL is returned, ownership is -** transfered to the caller. -*/ -cson_value * json_page_dispatch_helper(JsonPageDef const * pages); - -/* -** Implements the /json/wiki family of pages/commands. -** -*/ -cson_value * json_page_wiki(); - -/* -** Implements /json/timeline/wiki and /json/wiki/timeline. -*/ -cson_value * json_timeline_wiki(); - -/* -** Implements /json/timeline family of functions. -*/ -cson_value * json_page_timeline(); - -/* -** Convenience wrapper around cson_value_new_string(). -** Returns NULL if str is NULL or on allocation error. -*/ -cson_value * json_new_string( char const * str ); - -#endif/*FOSSIL_JSON_DETAIL_H_INCLUDED*/ DELETED src/json_login.c Index: src/json_login.c ================================================================== --- src/json_login.c +++ /dev/null @@ -1,240 +0,0 @@ -#include "config.h" -#include "json_login.h" - -#if INTERFACE -#include "json_detail.h" -#endif - - -/* -** Implementation of the /json/login page. -** -*/ -cson_value * json_page_login(){ - char preciseErrors = /* if true, "complete" JSON error codes are used, - else they are "dumbed down" to a generic login - error code. - */ -#if 1 - g.json.errorDetailParanoia ? 0 : 1 -#else - 0 -#endif - ; - /* - FIXME: we want to check the GET/POST args in this order: - - - GET: name, n, password, p - - POST: name, password - - but a bug in cgi_parameter() is breaking that, causing PD() to - return the last element of the PATH_INFO instead. - - Summary: If we check for P("name") first, then P("n"), - then ONLY a GET param of "name" will match ("n" - is not recognized). If we reverse the order of the - checks then both forms work. Strangely enough, the - "p"/"password" check is not affected by this. - */ - char const * name = cson_value_get_cstr(json_payload_property("name")); - char const * pw = NULL; - char const * anonSeed = NULL; - cson_value * payload = NULL; - int uid = 0; - /* reminder to self: - Fossil internally (for the sake of /wiki) interprets - paths in the form /foo/bar/baz such that - P("name") == "bar/baz". This collides with our - name/password checking, and thus we check for the - password first. - */ - pw = cson_value_get_cstr(json_payload_property("password")); - if( !pw ){ - pw = PD("p",NULL); - if( !pw ){ - pw = PD("password",NULL); - } - } - if(!pw){ - g.json.resultCode = preciseErrors - ? FSL_JSON_E_LOGIN_FAILED_NOPW - : FSL_JSON_E_LOGIN_FAILED; - return NULL; - } - - if( !name ){ - name = PD("n",NULL); - if( !name ){ - name = PD("name",NULL); - if( !name ){ - g.json.resultCode = preciseErrors - ? FSL_JSON_E_LOGIN_FAILED_NONAME - : FSL_JSON_E_LOGIN_FAILED; - return NULL; - } - } - } - - if(0 == strcmp("anonymous",name)){ - /* check captcha/seed values... */ - enum { SeedBufLen = 100 /* in some JSON tests i once actually got an - 80-digit number. - */ - }; - static char seedBuffer[SeedBufLen]; - cson_value const * jseed = json_getenv(FossilJsonKeys.anonymousSeed); - seedBuffer[0] = 0; - if( !jseed ){ - jseed = json_payload_property(FossilJsonKeys.anonymousSeed); - if( !jseed ){ - jseed = json_getenv("cs") /* name used by HTML interface */; - } - } - if(jseed){ - if( cson_value_is_number(jseed) ){ - sprintf(seedBuffer, "%"CSON_INT_T_PFMT, cson_value_get_integer(jseed)); - anonSeed = seedBuffer; - }else if( cson_value_is_string(jseed) ){ - anonSeed = cson_string_cstr(cson_value_get_string(jseed)); - } - } - if(!anonSeed){ - g.json.resultCode = preciseErrors - ? FSL_JSON_E_LOGIN_FAILED_NOSEED - : FSL_JSON_E_LOGIN_FAILED; - return NULL; - } - } - -#if 0 - { - /* only for debugging the PD()-incorrect-result problem */ - cson_object * o = NULL; - uid = login_search_uid( name, pw ); - payload = cson_value_new_object(); - o = cson_value_get_object(payload); - cson_object_set( o, "n", cson_value_new_string(name,strlen(name))); - cson_object_set( o, "p", cson_value_new_string(pw,strlen(pw))); - return payload; - } -#endif - uid = anonSeed - ? login_is_valid_anonymous(name, pw, anonSeed) - : login_search_uid(name, pw) - ; - if( !uid ){ - g.json.resultCode = preciseErrors - ? FSL_JSON_E_LOGIN_FAILED_NOTFOUND - : FSL_JSON_E_LOGIN_FAILED; - return NULL; - }else{ - char * cookie = NULL; - if(anonSeed){ - login_set_anon_cookie(NULL, &cookie); - }else{ - login_set_user_cookie(name, uid, &cookie); - } - /* FIXME: expand the payload to: - - { authToken:..., - name:..., - capabilities:... - } - */ - { - cson_object * po; - char * cap = NULL; - payload = cson_value_new_object(); - po = cson_value_get_object(payload); - cson_object_set(po, "authToken", json_new_string(cookie)); - cson_object_set(po, "name", json_new_string(name)); - cap = db_text(NULL,"SELECT cap FROM user WHERE login=%Q",name); - cson_object_set(po, "capabilities", json_new_string(cap)); - free(cap); - } - free(cookie); - return payload; - } - -} - -/* -** Impl of /json/logout. -** -*/ -cson_value * json_page_logout(){ - cson_value const *token = g.json.authToken; - /* Remember that json_mode_bootstrap() replaces the login cookie - with the JSON auth token if the request contains it. If the - reqest is missing the auth token then this will fetch fossil's - original cookie. Either way, it's what we want :). - - We require the auth token to avoid someone maliciously - trying to log someone else out (not 100% sure if that - would be possible, given fossil's hardened cookie, but - i'll assume it would be for the time being). - */ - ; - if(!token){ - g.json.resultCode = FSL_JSON_E_MISSING_AUTH; - }else{ - login_clear_login_data(); - g.json.authToken = NULL /* memory is owned elsewhere.*/; - } - return NULL; -} - -/* -** Implementation of the /json/anonymousPassword page. -*/ -cson_value * json_page_anon_password(){ - cson_value * v = cson_value_new_object(); - cson_object * o = cson_value_get_object(v); - unsigned const int seed = captcha_seed(); - char const * zCaptcha = captcha_decode(seed); - cson_object_set(o, "seed", - cson_value_new_integer( (cson_int_t)seed ) - ); - cson_object_set(o, "password", - cson_value_new_string( zCaptcha, strlen(zCaptcha) ) - ); - return v; -} - - - -/* -** Implements the /json/whoami page/command. -*/ -cson_value * json_page_whoami(){ - cson_value * payload = NULL; - cson_object * obj = NULL; - Stmt q; - db_prepare(&q, "SELECT login, cap FROM user WHERE uid=%d", g.userUid); - if( db_step(&q)==SQLITE_ROW ){ - - /* reminder: we don't use g.zLogin because it's 0 for the guest - user and the HTML UI appears to currently allow the name to be - changed (but doing so would break other code). */ - char const * str; - payload = cson_value_new_object(); - obj = cson_value_get_object(payload); - str = (char const *)sqlite3_column_text(q.pStmt,0); - if( str ){ - cson_object_set( obj, "name", - cson_value_new_string(str,strlen(str)) ); - } - str = (char const *)sqlite3_column_text(q.pStmt,1); - if( str ){ - cson_object_set( obj, "capabilities", - cson_value_new_string(str,strlen(str)) ); - } - if( g.json.authToken ){ - cson_object_set( obj, "authToken", g.json.authToken ); - } - }else{ - g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; - } - db_finalize(&q); - return payload; -} DELETED src/json_timeline.c Index: src/json_timeline.c ================================================================== --- src/json_timeline.c +++ /dev/null @@ -1,533 +0,0 @@ -#include "VERSION.h" -#include "config.h" -#include "json_timeline.h" - -#if INTERFACE -#include "json_detail.h" -#endif - -static cson_value * json_timeline_ci(); -static cson_value * json_timeline_ticket(); -/* -** Mapping of /json/timeline/XXX commands/paths to callbacks. -*/ -static const JsonPageDef JsonPageDefs_Timeline[] = { -{"c", json_timeline_ci, 0}, -{"ci", json_timeline_ci, 0}, -{"com", json_timeline_ci, 0}, -{"commit", json_timeline_ci, 0}, -{"t", json_timeline_ticket, 0}, -{"ticket", json_timeline_ticket, 0}, -{"w", json_timeline_wiki, 0}, -{"wi", json_timeline_wiki, 0}, -{"wik", json_timeline_wiki, 0}, -{"wiki", json_timeline_wiki, 0}, -/* Last entry MUST have a NULL name. */ -{NULL,NULL,0} -}; - - -/* -** Implements the /json/timeline family of pages/commands. Far from -** complete. -** -*/ -cson_value * json_page_timeline(){ - return json_page_dispatch_helper(&JsonPageDefs_Timeline[0]); -} - -/* -** Create a temporary table suitable for storing timeline data. -*/ -static void json_timeline_temp_table(void){ - /* Field order MUST match that from json_timeline_query()!!! */ - static const char zSql[] = - @ CREATE TEMP TABLE IF NOT EXISTS json_timeline( - @ sortId INTEGER PRIMARY KEY, - @ rid INTEGER, - @ uuid TEXT, - @ mtime INTEGER, - @ timestampString TEXT, - @ comment TEXT, - @ user TEXT, - @ isLeaf BOOLEAN, - @ bgColor TEXT, - @ eventType TEXT, - @ tags TEXT, - @ tagId INTEGER, - @ brief TEXT - @ ) - ; - db_multi_exec(zSql); -} - -/* -** Return a pointer to a constant string that forms the basis -** for a timeline query for the JSON interface. -*/ -const char const * json_timeline_query(void){ - /* Field order MUST match that from json_timeline_temp_table()!!! */ - static const char zBaseSql[] = - @ SELECT - @ NULL, - @ blob.rid, - @ uuid, - @ strftime('%%s',event.mtime), - @ datetime(event.mtime,'utc'), - @ coalesce(ecomment, comment), - @ coalesce(euser, user), - @ blob.rid IN leaf, - @ bgcolor, - @ event.type, - @ (SELECT group_concat(substr(tagname,5), ',') FROM tag, tagxref - @ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid - @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0), - @ tagid, - @ brief - @ FROM event JOIN blob - @ WHERE blob.rid=event.objid - ; - return zBaseSql; -} - -/* -** Helper for the timeline family of functions. Possibly appends 1 -** AND clause and an ORDER BY clause to pSql, depending on the state -** of the "after" ("a") or "before" ("b") environment parameters. -** This function gives "after" precedence over "before", and only -** applies one of them. -** -** Returns -1 if it adds a "before" clause, 1 if it adds -** an "after" clause, and 0 if adds only an order-by clause. -*/ -static char json_timeline_add_time_clause(Blob *pSql){ - char const * zAfter = NULL; - char const * zBefore = NULL; - if( g.isHTTP ){ - /** - FIXME: we are only honoring STRING values here, not int (for - passing Unix Epoch times). - */ - zAfter = json_getenv_cstr("after"); - if(!zAfter || !*zAfter){ - zAfter = json_getenv_cstr("a"); - } - if(!zAfter){ - zBefore = json_getenv_cstr("before"); - if(!zBefore||!*zBefore){ - zBefore = json_getenv_cstr("b"); - } - } - }else{ - zAfter = find_option("after","a",1); - zBefore = zAfter ? NULL : find_option("before","b",1); - } - if(zAfter&&*zAfter){ - while( fossil_isspace(*zAfter) ) ++zAfter; - blob_appendf(pSql, - " AND event.mtime>=(SELECT julianday(%Q,'utc')) " - " ORDER BY event.mtime ASC ", - zAfter); - return 1; - }else if(zBefore && *zBefore){ - while( fossil_isspace(*zBefore) ) ++zBefore; - blob_appendf(pSql, - " AND event.mtime<=(SELECT julianday(%Q,'utc')) " - " ORDER BY event.mtime DESC ", - zBefore); - return -1; - }else{ - blob_append(pSql," ORDER BY event.mtime DESC ", -1); - return 0; - } -} - -/* -** Tries to figure out a timeline query length limit base on -** environment parameters. If it can it returns that value, -** else it returns some statically defined default value. -** -** Never returns a negative value. 0 means no limit. -*/ -static int json_timeline_limit(){ - static const int defaultLimit = 20; - int limit = -1; - if( g.isHTTP ){ - limit = json_getenv_int("limit",-1); - if(limit<0){ - limit = json_getenv_int("n",-1); - } - }else{/* CLI mode */ - char const * arg = find_option("limit","n",1); - if(arg && *arg){ - limit = atoi(arg); - } - } - return (limit<0) ? defaultLimit : limit; -} - -/* -** Internal helper for the json_timeline_EVENTTYPE() family of -** functions. zEventType must be one of (ci, w, t). pSql must be a -** cleanly-initialized, empty Blob to store the sql in. If pPayload is -** not NULL it is assumed to be the pending response payload. If -** json_timeline_limit() returns non-0, this function adds a LIMIT -** clause to the generated SQL and (if pPayload is not NULL) adds the -** limit value as the "limit" property of pPayload. -*/ -static void json_timeline_setup_sql( char const * zEventType, - Blob * pSql, - cson_object * pPayload ){ - int limit; - assert( zEventType && *zEventType && pSql ); - json_timeline_temp_table(); - blob_append(pSql, "INSERT OR IGNORE INTO json_timeline ", -1); - blob_append(pSql, json_timeline_query(), -1 ); - blob_appendf(pSql, " AND event.type IN(%Q) ", zEventType); - json_timeline_add_time_clause(pSql); - limit = json_timeline_limit(); - if(limit){ - blob_appendf(pSql,"LIMIT %d ",limit); - } - if(pPayload){ - cson_object_set(pPayload, "limit",cson_value_new_integer(limit)); - } - -} - -static cson_value * json_timeline_get_changed_files(int rid){ - cson_value * rowsV = NULL; - cson_array * rows = NULL; - Stmt q; - db_prepare(&q, -#if 0 - "SELECT (mlink.pid==0) AS isNew," - " (mlink.fid==0) AS isDel," - " filename.name AS name" - " FROM mlink, filename" - " WHERE mid=%d" - " AND pid!=fid" - " AND filename.fnid=mlink.fnid" - " ORDER BY 3 /*sort*/", -#else - "SELECT (pid==0) AS isnew," - " (fid==0) AS isdel," - " (SELECT name FROM filename WHERE fnid=mlink.fnid) AS name," - " (SELECT uuid FROM blob WHERE rid=fid)," - " (SELECT uuid FROM blob WHERE rid=pid)" - " FROM mlink" - " WHERE mid=%d AND pid!=fid" - " ORDER BY 3 /*sort*/", -#endif - rid - ); - while( (SQLITE_ROW == db_step(&q)) ){ - cson_value * rowV = cson_value_new_object(); - cson_object * row = cson_value_get_object(rowV); - int const isNew = db_column_int(&q,0); - int const isDel = db_column_int(&q,1); - if(!rowsV){ - rowsV = cson_value_new_array(); - rows = cson_value_get_array(rowsV); - } - cson_object_set(row, "name", json_new_string(db_column_text(&q,2))); - cson_object_set(row, "uuid", json_new_string(db_column_text(&q,3))); - if(!isNew){ - cson_object_set(row, "prevUuid", json_new_string(db_column_text(&q,4))); - } - cson_object_set(row, "state", - json_new_string(isNew - ? "added" - : (isDel - ? "removed" - : "modified"))); - cson_array_append( rows, rowV ); - } - db_finalize(&q); - return rowsV; -} -/* -** Implementation of /json/timeline/ci. -** -** Still a few TODOs (like figuring out how to structure -** inheritance info). -*/ -static cson_value * json_timeline_ci(){ - cson_value * payV = NULL; - cson_object * pay = NULL; - cson_value * tmp = NULL; - cson_value * listV = NULL; - cson_array * list = NULL; - int check = 0; - int showFiles = 0; - Stmt q; - char warnRowToJsonFailed = 0; - char warnStringToArrayFailed = 0; - Blob sql = empty_blob; - if( !g.perm.Read/* && !g.perm.RdTkt && !g.perm.RdWiki*/ ){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - if( g.isHTTP ){ - showFiles = json_getenv_bool("showFiles",0); - }else{ - showFiles = 0!=find_option("show-files", "f",0); - } - payV = cson_value_new_object(); - pay = cson_value_get_object(payV); - json_timeline_setup_sql( "ci", &sql, pay ); -#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ - g.json.resultCode = (cson_rc.AllocError==check) \ - ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ - goto error;\ - } -#if 0 - /* only for testing! */ - tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); - SET("timelineSql"); -#endif - db_multi_exec(blob_buffer(&sql)); - blob_reset(&sql); - db_prepare(&q, "SELECT " - " rid AS rid," - " uuid AS uuid," - " mtime AS timestamp," -#if 0 - " timestampString AS timestampString," -#endif - " comment AS comment, " - " user AS user," - " isLeaf AS isLeaf," /*FIXME: convert to JSON bool */ - " bgColor AS bgColor," /* why always null? */ - " eventType AS eventType," - " tags AS tags" /*FIXME: split this into - a JSON array*/ -#if 0 - /*tagId is always null?*/ - " tagId AS tagId" -#endif - " FROM json_timeline" - " ORDER BY sortId"); - listV = cson_value_new_array(); - list = cson_value_get_array(listV); - tmp = listV; - SET("timeline"); - while( (SQLITE_ROW == db_step(&q) )){ - /* convert each row into a JSON object...*/ - int const rid = db_column_int(&q,0); - cson_value * rowV = cson_sqlite3_row_to_object(q.pStmt); - cson_object * row = cson_value_get_object(rowV); - cson_string const * tagsStr = NULL; - if(!row && !warnRowToJsonFailed){ - warnRowToJsonFailed = 1; - json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED, - "Could not convert at least one timeline result row to JSON." ); - continue; - } - /* Split tags string field into JSON Array... */ - cson_array_append(list, rowV); - tagsStr = cson_value_get_string(cson_object_get(row,"tags")); - if(tagsStr){ - cson_value * tags = json_string_split2( cson_string_cstr(tagsStr), - ',', 0); - if( tags ){ - if(0 != cson_object_set(row,"tags",tags)){ - cson_value_free(tags); - }else{ - /*replaced/deleted old tags value, invalidating tagsStr*/; - tagsStr = NULL; - } - }else if(!warnStringToArrayFailed){ - warnStringToArrayFailed = 1; - json_warn(FSL_JSON_W_STRING_TO_ARRAY_FAILED, - "Could not convert tags string to array."); - } - } - - /* replace isLeaf int w/ JSON bool */ - tmp = cson_object_get(row,"isLeaf"); - if(tmp && cson_value_is_integer(tmp)){ - cson_object_set(row,"isLeaf", - cson_value_get_integer(tmp) - ? cson_value_true() - : cson_value_false()); - tmp = NULL; - } - if( showFiles ){ - cson_value * flist = json_timeline_get_changed_files(rid); - if(flist){ - cson_object_set(row,"files",flist); - } - } - } -#undef SET - goto ok; - error: - assert( 0 != g.json.resultCode ); - cson_value_free(payV); - payV = NULL; - ok: - db_finalize(&q); - return payV; -} - -/* -** Implementation of /json/timeline/wiki. -** -*/ -cson_value * json_timeline_wiki(){ - /* This code is 95% the same as json_timeline_ci(), by the way. */ - cson_value * payV = NULL; - cson_object * pay = NULL; - cson_value * tmp = NULL; - cson_value * listV = NULL; - cson_array * list = NULL; - int check = 0; - Stmt q; - Blob sql = empty_blob; - if( !g.perm.Read || !g.perm.RdWiki ){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - payV = cson_value_new_object(); - pay = cson_value_get_object(payV); - json_timeline_setup_sql( "w", &sql, pay ); -#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ - g.json.resultCode = (cson_rc.AllocError==check) \ - ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ - goto error;\ - } -#if 0 - /* only for testing! */ - tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); - SET("timelineSql"); -#endif - db_multi_exec(blob_buffer(&sql)); - blob_reset(&sql); - db_prepare(&q, "SELECT rid AS rid," - " uuid AS uuid," - " mtime AS timestamp," -#if 0 - " timestampString AS timestampString," -#endif - " comment AS comment, " - " user AS user," - " eventType AS eventType" -#if 0 - /* can wiki pages have tags? */ - " tags AS tags," /*FIXME: split this into - a JSON array*/ - " tagId AS tagId," -#endif - " FROM json_timeline" - " ORDER BY sortId", - -1); - listV = cson_value_new_array(); - list = cson_value_get_array(listV); - tmp = listV; - SET("timeline"); - json_stmt_to_array_of_obj(&q, listV); -#undef SET - goto ok; - error: - assert( 0 != g.json.resultCode ); - cson_value_free(payV); - payV = NULL; - ok: - db_finalize(&q); - return payV; -} - -/* -** Implementation of /json/timeline/ticket. -** -*/ -static cson_value * json_timeline_ticket(){ - /* This code is 95% the same as json_timeline_ci(), by the way. */ - cson_value * payV = NULL; - cson_object * pay = NULL; - cson_value * tmp = NULL; - cson_value * listV = NULL; - cson_array * list = NULL; - int check = 0; - Stmt q; - Blob sql = empty_blob; - if( !g.perm.Read || !g.perm.RdTkt ){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - payV = cson_value_new_object(); - pay = cson_value_get_object(payV); - json_timeline_setup_sql( "t", &sql, pay ); - db_multi_exec(blob_buffer(&sql)); -#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ - g.json.resultCode = (cson_rc.AllocError==check) \ - ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ - goto error;\ - } - -#if 0 - /* only for testing! */ - tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); - SET("timelineSql"); -#endif - - blob_reset(&sql); - db_prepare(&q, "SELECT rid AS rid," - " uuid AS uuid," - " mtime AS timestamp," -#if 0 - " timestampString AS timestampString," -#endif - " user AS user," - " eventType AS eventType," - " comment AS comment," - " brief AS briefComment" - " FROM json_timeline" - " ORDER BY sortId", - -1); - listV = cson_value_new_array(); - list = cson_value_get_array(listV); - tmp = listV; - SET("timeline"); - while( (SQLITE_ROW == db_step(&q) )){ - /* convert each row into a JSON object...*/ - int rc; - int const rid = db_column_int(&q,0); - Manifest * pMan = NULL; - cson_value * rowV = cson_sqlite3_row_to_object(q.pStmt); - cson_object * row = cson_value_get_object(rowV); - if(!row){ - json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED, - "Could not convert at least one timeline result row to JSON." ); - continue; - } - pMan = manifest_get(rid, CFTYPE_TICKET); - assert( pMan && "Manifest is NULL!?!" ); - if( pMan ){ - /* FIXME: certainly there's a more efficient way for use to get - the ticket UUIDs? - */ - cson_object_set(row,"ticketUuid",json_new_string(pMan->zTicketUuid)); - manifest_destroy(pMan); - } - rc = cson_array_append( list, rowV ); - if( 0 != rc ){ - cson_value_free(rowV); - g.json.resultCode = (cson_rc.AllocError==rc) - ? FSL_JSON_E_ALLOC - : FSL_JSON_E_UNKNOWN; - goto error; - } - } -#undef SET - goto ok; - error: - assert( 0 != g.json.resultCode ); - cson_value_free(payV); - payV = NULL; - ok: - db_finalize(&q); - return payV; -} - DELETED src/json_wiki.c Index: src/json_wiki.c ================================================================== --- src/json_wiki.c +++ /dev/null @@ -1,251 +0,0 @@ -#include "VERSION.h" -#include "config.h" -#include "json_wiki.h" - -#if INTERFACE -#include "json_detail.h" -#endif - -static cson_value * json_wiki_create(); -static cson_value * json_wiki_get(); -static cson_value * json_wiki_list(); -static cson_value * json_wiki_save(); - -/* -** Mapping of /json/wiki/XXX commands/paths to callbacks. -*/ -static const JsonPageDef JsonPageDefs_Wiki[] = { -{"create", json_wiki_create, 1}, -{"get", json_wiki_get, 0}, -{"list", json_wiki_list, 0}, -{"save", json_wiki_save, 1}, -{"timeline", json_timeline_wiki,0}, -/* Last entry MUST have a NULL name. */ -{NULL,NULL,0} -}; - - -/* -** Implements the /json/wiki family of pages/commands. -** -*/ -cson_value * json_page_wiki(){ - return json_page_dispatch_helper(&JsonPageDefs_Wiki[0]); -} - - -/* -** Implementation of /json/wiki/get. -** -** TODO: add option to parse wiki output. It is currently -** unparsed. -*/ -static cson_value * json_wiki_get(){ - int rid; - Manifest *pWiki = 0; - char const * zBody = NULL; - char const * zPageName; - char doParse = 0/*not yet implemented*/; - - if( !g.perm.RdWiki ){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - zPageName = g.isHTTP - ? json_getenv_cstr("page") - : find_option("page","p",1); - if(!zPageName||!*zPageName){ - g.json.resultCode = FSL_JSON_E_MISSING_ARGS; - return NULL; - } - rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x" - " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'" - " ORDER BY x.mtime DESC LIMIT 1", - zPageName - ); - if( (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){ - zBody = pWiki->zWiki; - } - if( zBody==0 ){ - manifest_destroy(pWiki); - g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; - return NULL; - }else{ - unsigned int const len = strlen(zBody); - cson_value * payV = cson_value_new_object(); - cson_object * pay = cson_value_get_object(payV); - cson_object_set(pay,"name",json_new_string(zPageName)); - cson_object_set(pay,"version",json_new_string(pWiki->zBaseline)) - /*FIXME: pWiki->zBaseline is NULL. How to get the version number?*/ - ; - cson_object_set(pay,"rid",cson_value_new_integer((cson_int_t)rid)); - cson_object_set(pay,"lastSavedBy",json_new_string(pWiki->zUser)); - cson_object_set(pay,FossilJsonKeys.timestamp, json_julian_to_timestamp(pWiki->rDate)); - cson_object_set(pay,"contentLength",cson_value_new_integer((cson_int_t)len)); - cson_object_set(pay,"contentFormat",json_new_string(doParse?"html":"raw")); - cson_object_set(pay,"content",cson_value_new_string(zBody,len)); - /*TODO: add 'T' (tag) fields*/ - /*TODO: add the 'A' card (file attachment) entries?*/ - manifest_destroy(pWiki); - return payV; - } -} - -/* -** Internal impl of /wiki/save and /wiki/create. If createMode is 0 -** and the page already exists then a -** FSL_JSON_E_RESOURCE_ALREADY_EXISTS error is triggered. If -** createMode is false then the FSL_JSON_E_RESOURCE_NOT_FOUND is -** triggered if the page does not already exists. -** -** Note that the error triggered when createMode==0 and no such page -** exists is rather arbitrary - we could just as well create the entry -** here if it doesn't already exist. With that, save/create would -** become one operation. That said, i expect there are people who -** would categorize such behaviour as "being too clever" or "doing too -** much automatically" (and i would likely agree with them). -** -** If allowCreateIfExists is true then this function will allow a new -** page to be created even if createMode is false. -*/ -static cson_value * json_wiki_create_or_save(char createMode, - char allowCreateIfExists){ - Blob content = empty_blob; - cson_value * nameV; - cson_value * contentV; - cson_value * emptyContent = NULL; - cson_value * payV = NULL; - cson_object * pay = NULL; - cson_string const * jstr = NULL; - char const * zContent; - char const * zBody = NULL; - char const * zPageName; - unsigned int contentLen = 0; - int rid; - if( (createMode && !g.perm.NewWiki) - || (!createMode && !g.perm.WrWiki)){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - nameV = json_req_payload_get("name"); - if(!nameV){ - g.json.resultCode = FSL_JSON_E_MISSING_ARGS; - goto error; - } - zPageName = cson_string_cstr(cson_value_get_string(nameV)); - rid = db_int(0, - "SELECT x.rid FROM tag t, tagxref x" - " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'" - " ORDER BY x.mtime DESC LIMIT 1", - zPageName - ); - - if(rid){ - if(createMode){ - g.json.resultCode = FSL_JSON_E_RESOURCE_ALREADY_EXISTS; - goto error; - } - }else if(!allowCreateIfExists){ - g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; - goto error; - } - - contentV = json_req_payload_get("content"); - if( !contentV ){ - if( createMode || (!rid && allowCreateIfExists) ){ - contentV = emptyContent = cson_value_new_string("",0); - }else{ - g.json.resultCode = FSL_JSON_E_MISSING_ARGS; - goto error; - } - } - if( !cson_value_is_string(nameV) - || !cson_value_is_string(contentV)){ - g.json.resultCode = FSL_JSON_E_INVALID_ARGS; - goto error; - } - jstr = cson_value_get_string(contentV); - contentLen = (int)cson_string_length_bytes(jstr); - if(contentLen){ - blob_append(&content, cson_string_cstr(jstr),contentLen); - } - wiki_cmd_commit(zPageName, 0==rid, &content); - blob_reset(&content); - - payV = cson_value_new_object(); - pay = cson_value_get_object(payV); - cson_object_set( pay, "name", nameV ); - cson_object_set( pay, FossilJsonKeys.timestamp, - json_new_timestamp(-1) ); - - goto ok; - error: - assert( 0 != g.json.resultCode ); - cson_value_free(payV); - payV = NULL; - ok: - if( emptyContent ){ - /* We have some potentially tricky memory ownership - here, which is why we handle emptyContent separately. - - This is, in fact, overkill because cson_value_new_string("",0) - actually returns a shared singleton instance (i.e. doesn't - allocate), but that is a cson implementation detail which i - don't want leaking into this code... - */ - cson_value_free(emptyContent); - } - return payV; - -} - -/* -** Implementation of /json/wiki/create. -*/ -static cson_value * json_wiki_create(){ - return json_wiki_create_or_save(1,0); -} - -/* -** Implementation of /json/wiki/save. -*/ -static cson_value * json_wiki_save(){ - char const createIfNotExists = json_getenv_bool("createIfNotExists",0); - return json_wiki_create_or_save(0,createIfNotExists); -} - -/* -** Implementation of /json/wiki/list. -*/ -static cson_value * json_wiki_list(){ - cson_value * listV = NULL; - cson_array * list = NULL; - Stmt q; - if( !g.perm.RdWiki ){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - db_prepare(&q,"SELECT" - " substr(tagname,6) as name" - " FROM tag WHERE tagname GLOB 'wiki-*'" - " ORDER BY lower(name)"); - listV = cson_value_new_array(); - list = cson_value_get_array(listV); - while( SQLITE_ROW == db_step(&q) ){ - cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0); - if(!v){ - goto error; - }else if( 0 != cson_array_append( list, v ) ){ - cson_value_free(v); - goto error; - } - } - goto end; - error: - g.json.resultCode = FSL_JSON_E_UNKNOWN; - cson_value_free(listV); - listV = NULL; - end: - db_finalize(&q); - return listV; -} Index: src/login.c ================================================================== --- src/login.c +++ src/login.c @@ -84,18 +84,18 @@ ** ** The login cookie name is always of the form: fossil-XXXXXXXXXXXXXXXX ** where the Xs are the first 16 characters of the login-group-code or ** of the project-code if we are not a member of any login-group. */ -char *login_cookie_name(void){ +static char *login_cookie_name(void){ static char *zCookieName = 0; if( zCookieName==0 ){ zCookieName = db_text(0, "SELECT 'fossil-' || substr(value,1,16)" " FROM config" " WHERE name IN ('project-code','login-group-code')" - " ORDER BY name;" + " ORDER BY name /*sort*/" ); } return zCookieName; } @@ -141,26 +141,24 @@ /* ** Check to see if the anonymous login is valid. If it is valid, return ** the userid of the anonymous user. -** -** The zCS parameter is the "captcha seed" used for a specific -** anonymous login request. */ -int login_is_valid_anonymous( +static int isValidAnonymousLogin( const char *zUsername, /* The username. Must be "anonymous" */ - const char *zPassword, /* The supplied password */ - const char *zCS /* The captcha seed value */ + const char *zPassword /* The supplied password */ ){ + const char *zCS; /* The captcha seed value */ const char *zPw; /* The correct password shown in the captcha */ int uid; /* The user ID of anonymous */ if( zUsername==0 ) return 0; - else if( zPassword==0 ) return 0; - else if( zCS==0 ) return 0; - else if( fossil_strcmp(zUsername,"anonymous")!=0 ) return 0; + if( zPassword==0 ) return 0; + if( fossil_strcmp(zUsername,"anonymous")!=0 ) return 0; + zCS = P("cs"); /* The "cs" parameter is the "captcha seed" */ + if( zCS==0 ) return 0; zPw = captcha_decode((unsigned int)atoi(zCS)); if( fossil_stricmp(zPw, zPassword)!=0 ) return 0; uid = db_int(0, "SELECT uid FROM user WHERE login='anonymous'" " AND length(pw)>0 AND length(cap)>0"); return uid; @@ -195,161 +193,10 @@ "VALUES(%Q,%Q,%d,julianday('now'));", zUsername, zIpAddr, bSuccess ); } -/* -** Searches for the user ID matching the given name and password. -** On success it returns a positive value. On error it returns 0. -** On serious (DB-level) error it will probably exit. -** -** zPassword may be either the plain-text form or the encrypted -** form of the user's password. -*/ -int login_search_uid(char const *zUsername, char const *zPasswd){ - char * zSha1Pw = sha1_shared_secret(zPasswd, zUsername, 0); - int const uid = - db_int(0, - "SELECT uid FROM user" - " WHERE login=%Q" - " AND length(cap)>0 AND length(pw)>0" - " AND login NOT IN ('anonymous','nobody','developer','reader')" - " AND (pw=%Q OR pw=%Q)", - zUsername, zPasswd, zSha1Pw - ); - free(zSha1Pw); - return uid; -} - -/* -** Generates a login cookie value for a non-anonymous user. -** -** The zHash parameter must be a random value which must be -** subsequently stored in user.cookie for later validation. -** -** The returned memory should be free()d after use. -*/ -char * login_gen_user_cookie_value(char const *zUsername, char const * zHash){ - char *zCode = abbreviated_project_code(db_get("project-code","")); - assert((zUsername && *zUsername) && "Invalid user data."); - return mprintf("%s/%z/%s", zHash, zCode, zUsername); -} - -/* -** Generates a login cookie for NON-ANONYMOUS users. Note that this -** function "could" figure out the uid by itself but it currently -** doesn't because the code which calls this already has the uid. -** -** This function also updates the user.cookie, user.ipaddr, -** and user.cexpire fields for the given user. -** -** If zDest is not NULL then the generated cookie is copied to -** *zDdest and ownership is transfered to the caller (who should -** eventually pass it to free()). -*/ -void login_set_user_cookie( - char const * zUsername, /* User's name */ - int uid, /* User's ID */ - char ** zDest /* Optional: store generated cookie value. */ -){ - const char *zCookieName = login_cookie_name(); - const char *zExpire = db_get("cookie-expire","8766"); - int expires = atoi(zExpire)*3600; - char *zHash; - char *zCookie; - char const * zIpAddr = PD("REMOTE_ADDR","nil"); /* Complete IP address for logging */ - char * zRemoteAddr = ipPrefix(zIpAddr); /* Abbreviated IP address */ - assert((zUsername && *zUsername) && (uid > 0) && "Invalid user data."); - zHash = db_text(0, "SELECT hex(randomblob(25))"); - zCookie = login_gen_user_cookie_value(zUsername, zHash); - cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires); - record_login_attempt(zUsername, zIpAddr, 1); - db_multi_exec( - "UPDATE user SET cookie=%Q, ipaddr=%Q, " - " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d", - zHash, zRemoteAddr, expires, uid - ); - free(zRemoteAddr); - free(zHash); - if( zDest ){ - *zDest = zCookie; - }else{ - free(zCookie); - } -} - -/* Sets a cookie for an anonymous user login, which looks like this: -** -** HASH/TIME/anonymous -** -** Where HASH is the sha1sum of TIME/IPADDR/SECRET, in which IPADDR -** is the abbreviated IP address and SECRET is captcha-secret. -** -** If either zIpAddr or zRemoteAddr are NULL then REMOTE_ADDR -** is used. -** -** If zCookieDest is not NULL then the generated cookie is assigned to -** *zCookieDest and the caller must eventually free() it. -*/ -void login_set_anon_cookie(char const * zIpAddr, char ** zCookieDest ){ - char const *zNow; /* Current time (julian day number) */ - char *zCookie; /* The login cookie */ - char const *zCookieName; /* Name of the login cookie */ - Blob b; /* Blob used during cookie construction */ - char * zRemoteAddr; /* Abbreviated IP address */ - if(!zIpAddr){ - zIpAddr = PD("REMOTE_ADDR","nil"); - } - zRemoteAddr = ipPrefix(zIpAddr); - zCookieName = login_cookie_name(); - zNow = db_text("0", "SELECT julianday('now')"); - assert( zCookieName && zRemoteAddr && zIpAddr && zNow ); - blob_init(&b, zNow, -1); - blob_appendf(&b, "/%s/%s", zRemoteAddr, db_get("captcha-secret","")); - sha1sum_blob(&b, &b); - zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow); - blob_reset(&b); - cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600); - if( zCookieDest ){ - *zCookieDest = zCookie; - }else{ - free(zCookie); - } - -} - -/* -** "Unsets" the login cookie (insofar as cookies can be unset) and -** clears the current user's (g.userUid) login information from the -** user table. Sets: user.cookie, user.ipaddr, user.cexpire. -** -** We could/should arguably clear out g.userUid and g.perm here, but -** we don't currently do not. -** -** This is a no-op if g.userUid is 0. -*/ -void login_clear_login_data(){ - if(!g.userUid){ - return; - }else{ - char const * cookie = login_cookie_name(); - /* To logout, change the cookie value to an empty string */ - cgi_set_cookie(cookie, "", - login_cookie_path(), -86400); - db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, " - " cexpire=0 WHERE uid=%d" - " AND login NOT IN ('anonymous','guest'," - " 'developer','reader')", g.userUid); - cgi_replace_parameter(cookie, NULL) - /* At the time of this writing, cgi_replace_parameter() was - ** "NULL-value-safe", and i'm hoping the NULL doesn't cause any - ** downstream problems here. We could alternately use "" here. - */ - ; - } -} - /* ** WEBPAGE: login ** WEBPAGE: logout ** WEBPAGE: my ** @@ -367,25 +214,30 @@ int anonFlag; char *zErrMsg = ""; int uid; /* User id loged in user */ char *zSha1Pw; const char *zIpAddr; /* IP address of requestor */ + char *zRemoteAddr; /* Abbreviated IP address of requestor */ login_check_credentials(); zUsername = P("u"); zPasswd = P("p"); anonFlag = P("anon")!=0; if( P("out")!=0 ){ - login_clear_login_data(); + /* To logout, change the cookie value to an empty string */ + const char *zCookieName = login_cookie_name(); + cgi_set_cookie(zCookieName, "", login_cookie_path(), -86400); redirect_to_g(); } if( g.perm.Password && zPasswd && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 ){ /* The user requests a password change */ zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0); if( db_int(1, "SELECT 0 FROM user" - " WHERE uid=%d AND (pw=%Q OR pw=%Q)", - g.userUid, zPasswd, zSha1Pw) ){ + " WHERE uid=%d" + " AND (constant_time_cmp(pw,%Q)=0" + " OR constant_time_cmp(pw,%Q)=0)", + g.userUid, zSha1Pw, zPasswd) ){ sleep(1); zErrMsg = @ @ You entered an incorrect old password while attempting to change @ your password. Your password is unchanged. @@ -421,20 +273,50 @@ return; } } } zIpAddr = PD("REMOTE_ADDR","nil"); /* Complete IP address for logging */ - uid = login_is_valid_anonymous(zUsername, zPasswd, P("cs")); + zRemoteAddr = ipPrefix(zIpAddr); /* Abbreviated IP address */ + uid = isValidAnonymousLogin(zUsername, zPasswd); if( uid>0 ){ - login_set_anon_cookie(zIpAddr, NULL); + /* Successful login as anonymous. Set a cookie that looks like + ** this: + ** + ** HASH/TIME/anonymous + ** + ** Where HASH is the sha1sum of TIME/IPADDR/SECRET, in which IPADDR + ** is the abbreviated IP address and SECRET is captcha-secret. + */ + char *zNow; /* Current time (julian day number) */ + char *zCookie; /* The login cookie */ + const char *zCookieName; /* Name of the login cookie */ + Blob b; /* Blob used during cookie construction */ + + zCookieName = login_cookie_name(); + zNow = db_text("0", "SELECT julianday('now')"); + blob_init(&b, zNow, -1); + blob_appendf(&b, "/%s/%s", zRemoteAddr, db_get("captcha-secret","")); + sha1sum_blob(&b, &b); + zCookie = sqlite3_mprintf("%s/%s/anonymous", blob_buffer(&b), zNow); + blob_reset(&b); + free(zNow); + cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600); record_login_attempt("anonymous", zIpAddr, 1); redirect_to_g(); } if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){ /* Attempting to log in as a user other than anonymous. */ - uid = login_search_uid(zUsername, zPasswd); + zSha1Pw = sha1_shared_secret(zPasswd, zUsername, 0); + uid = db_int(0, + "SELECT uid FROM user" + " WHERE login=%Q" + " AND length(cap)>0 AND length(pw)>0" + " AND login NOT IN ('anonymous','nobody','developer','reader')" + " AND (constant_time_cmp(pw,%Q)=0 OR constant_time_cmp(pw,%Q)=0)", + zUsername, zSha1Pw, zPasswd + ); if( uid<=0 ){ sleep(1); zErrMsg = @
@ You entered an unknown user or an incorrect password. @@ -447,11 +329,26 @@ ** HASH/PROJECT/LOGIN ** ** where HASH is a random hex number, PROJECT is either project ** code prefix, and LOGIN is the user name. */ - login_set_user_cookie(zUsername, uid, NULL); + char *zCookie; + const char *zCookieName = login_cookie_name(); + const char *zExpire = db_get("cookie-expire","8766"); + int expires = atoi(zExpire)*3600; + char *zCode = abbreviated_project_code(db_get("project-code","")); + char *zHash; + + zHash = db_text(0, "SELECT hex(randomblob(25))"); + zCookie = mprintf("%s/%s/%s", zHash, zCode, zUsername); + cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires); + record_login_attempt(zUsername, zIpAddr, 1); + db_multi_exec( + "UPDATE user SET cookie=%Q, ipaddr=%Q, " + " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d", + zHash, zRemoteAddr, expires, uid + ); redirect_to_g(); } } style_header("Login/Logout"); @ %s(zErrMsg) @@ -556,10 +453,37 @@ @ @ } style_footer(); } + +/* +** SQL function for constant time comparison of two values. +** Sets result to 0 if two values are equal. +*/ +static void constant_time_cmp_function( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned char *buf1, *buf2; + int len, i; + unsigned char rc = 0; + + assert( argc==2 ); + len = sqlite3_value_bytes(argv[0]); + if( len==0 || len!=sqlite3_value_bytes(argv[1]) ){ + rc = 1; + }else{ + buf1 = sqlite3_value_text(argv[0]); + buf2 = sqlite3_value_text(argv[1]); + for( i=0; i
", z); cgi_reply(); }else{ char *zOut = mprintf("%s: %s\n", fossil_nameofexe(), z); fossil_puts(zOut, 1); } - free(z); db_force_rollback(); - fossil_exit(rc); + fossil_exit(1); } - -void fossil_fatal(const char *zFormat, ...){ +NORETURN void fossil_fatal(const char *zFormat, ...){ char *z; - int rc = 1; va_list ap; mainInFatalError = 1; va_start(ap, zFormat); z = vmprintf(zFormat, ap); va_end(ap); - if( g.json.isJsonMode ){ - json_err( g.json.resultCode, z, 1 ); - if( g.isHTTP ){ - rc = 0 /* avoid HTTP 500 */; - } - } - else if( g.cgiOutput ){ + if( g.cgiOutput ){ g.cgiOutput = 0; cgi_printf("0" " AND length(pw)>0" - " AND cexpire>julianday('now')", - zHash, zRemoteAddr, zLogin + " AND cexpire>julianday('now')" + " AND constant_time_cmp(cookie,%Q)=0", + zLogin, zRemoteAddr, zHash ); pStmt = 0; rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0); if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ db_multi_exec( @@ -616,16 +542,12 @@ fossil_free(zOtherRepo); return nXfer; } /* -** Lookup the uid for a non-built-in user with zLogin and zCookie and -** zRemoteAddr. Return 0 if not found. -** -** Note that this only searches for logged-in entries with matching -** zCookie (db: user.cookie) and zRemoteAddr (db: user.ipaddr) -** entries. +** Lookup the uid for a user with zLogin and zCookie and zRemoteAddr. +** Return 0 if not found. */ static int login_find_user( const char *zLogin, /* User name */ const char *zCookie, /* Login cookie value */ const char *zRemoteAddr /* Abbreviated IP address for valid login */ @@ -636,25 +558,27 @@ if( fossil_strcmp(zLogin, "developer")==0 ) return 0; if( fossil_strcmp(zLogin, "reader")==0 ) return 0; uid = db_int(0, "SELECT uid FROM user" " WHERE login=%Q" - " AND cookie=%Q" " AND ipaddr=%Q" " AND cexpire>julianday('now')" " AND length(cap)>0" - " AND length(pw)>0", - zLogin, zCookie, zRemoteAddr + " AND length(pw)>0" + " AND constant_time_cmp(cookie,%Q)=0", + zLogin, zRemoteAddr, zCookie ); return uid; } /* -** This routine examines the login cookie to see if it exists and and -** is valid. If the login cookie checks out, it then sets global -** variables appropriately. Global variables set include g.userUid -** and g.zLogin and the g.perm family of permission booleans. +** This routine examines the login cookie to see if it exists and +** and is valid. If the login cookie checks out, it then sets +** global variables appropriately. Global variables set include +** g.userUid and g.zLogin and of the g.perm.Read family of permission +** booleans. +** */ void login_check_credentials(void){ int uid = 0; /* User id */ const char *zCookie; /* Text of the login cookie */ const char *zIpAddr; /* Raw IP address of the requestor */ @@ -661,10 +585,13 @@ char *zRemoteAddr; /* Abbreviated IP address of the requestor */ const char *zCap = 0; /* Capability string */ /* Only run this check once. */ if( g.userUid!=0 ) return; + + sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0, + constant_time_cmp_function, 0, 0); /* If the HTTP connection is coming over 127.0.0.1 and if ** local login is disabled and if we are using HTTP and not HTTPS, ** then there is no need to check user credentials. ** @@ -739,11 +666,11 @@ } sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zHash); } /* If no user found and the REMOTE_USER environment variable is set, - ** then accept the value of REMOTE_USER as the user. + ** the accept the value of REMOTE_USER as the user. */ if( uid==0 ){ const char *zRemoteUser = P("REMOTE_USER"); if( zRemoteUser && db_get_boolean("remote_user_ok",0) ){ uid = db_int(0, "SELECT uid FROM user WHERE login=%Q" @@ -789,11 +716,11 @@ if( fossil_strcmp(g.zLogin,"nobody")==0 ){ g.zLogin = 0; } /* Set the capabilities */ - login_replace_capabilities(zCap, 0); + login_set_capabilities(zCap, 0); login_set_anon_nobody_capabilities(); } /* ** Memory of settings @@ -818,25 +745,22 @@ login_anon_once = 0; } } /* -** Flags passed into the 2nd argument of login_set/replace_capabilities(). +** Flags passed into the 2nd argument of login_set_capabilities(). */ #if INTERFACE #define LOGIN_IGNORE_U 0x01 /* Ignore "u" */ #define LOGIN_IGNORE_V 0x01 /* Ignore "v" */ #endif /* -** Adds all capability flags in zCap to g.perm. +** Set the global capability flags based on a capability string. */ void login_set_capabilities(const char *zCap, unsigned flags){ int i; - if(NULL==zCap){ - return; - } for(i=0; zCap[i]; i++){ switch( zCap[i] ){ case 's': g.perm.Setup = 1; /* Fall thru into Admin */ case 'a': g.perm.Admin = g.perm.RdTkt = g.perm.WrTkt = g.perm.Zip = g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki = @@ -891,18 +815,10 @@ } } } } -/* -** Zeroes out g.perm and calls login_set_capabilities(zCap,flags). -*/ -void login_replace_capabilities(const char *zCap, unsigned flags){ - memset(&g.perm, 0, sizeof(g.perm)); - return login_set_capabilities(zCap, flags); -} - /* ** If the current login lacks any of the capabilities listed in ** the input, then return 0. If all capabilities are present, then ** return 1. */ @@ -976,19 +892,14 @@ /* ** Call this routine when the credential check fails. It causes ** a redirect to the "login" page. */ void login_needed(void){ - if(g.json.isJsonMode){ - json_err( FSL_JSON_E_DENIED, NULL, 1 ); - fossil_exit(0); - }else{ - const char *zUrl = PD("REQUEST_URI", "index"); - cgi_redirect(mprintf("login?g=%T", zUrl)); - /* NOTREACHED */ - assert(0); - } + const char *zUrl = PD("REQUEST_URI", "index"); + cgi_redirect(mprintf("login?g=%T", zUrl)); + /* NOTREACHED */ + assert(0); } /* ** Call this routine if the user lacks okHistory permission. If ** the anonymous user has okHistory permission, then paint a mesage Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -23,15 +23,13 @@ #include #include #include #include #include -#include /* atexit() */ + #if INTERFACE -#include "cson_amalgamation.h" /* JSON API. Needed inside the INTERFACE block! */ -#include "json_detail.h" /* ** Number of elements in an array */ #define count(X) (sizeof(X)/sizeof(X[0])) @@ -117,11 +115,10 @@ int xlinkClusterOnly; /* Set when cloning. Only process clusters */ int fTimeFormat; /* 1 for UTC. 2 for localtime. 0 not yet selected */ int *aCommitFile; /* Array of files to be committed */ int markPrivate; /* All new artifacts are private if true */ int clockSkewSeen; /* True if clocks on client and server out of sync */ - int isHTTP; /* True if running in server/CGI modes, else assume CLI. */ int urlIsFile; /* True if a "file:" url */ int urlIsHttps; /* True if a "https:" url */ int urlIsSsh; /* True if an "ssh:" url */ char *urlName; /* Hostname for http: or filename for file: */ @@ -134,11 +131,11 @@ char *urlPasswd; /* Password for http: */ char *urlCanonical; /* Canonical representation of the URL */ char *urlProxyAuth; /* Proxy-Authorizer: string */ char *urlFossil; /* The path of the ?fossil=path suffix on ssh: */ int dontKeepUrl; /* Do not persist the URL */ - + const char *zLogin; /* Login name. "" if not logged in. */ const char *zSSLIdentity; /* Value of --ssl-identity option, filename of SSL client identity */ int useLocalauth; /* No login required if from 127.0.0.1 */ int noPswd; /* Logged in without password (on 127.0.0.1) */ int userUid; /* Integer user id */ @@ -169,60 +166,10 @@ const char *azAuxVal[MX_AUX]; /* Value of each aux() or option() value */ const char **azAuxOpt[MX_AUX]; /* Options of each option() value */ int anAuxCols[MX_AUX]; /* Number of columns for option() values */ int allowSymlinks; /* Cached "allow-symlinks" option */ - - struct FossilJsonBits { - int isJsonMode; /* True if running in JSON mode, else - false. This changes how errors are - reported. In JSON mode we try to - always output JSON-form error - responses and always exit() with - code 0 to avoid an HTTP 500 error. - */ - int resultCode; /* used for passing back specific codes from /json callbacks. */ - int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */ - cson_output_opt outOpt; /* formatting options for JSON mode. */ - cson_value * authToken; /* authentication token */ - char const * jsonp; /* Name of JSONP function wrapper. */ - unsigned char dispatchDepth /* Tells JSON command dispatching - which argument we are currently - working on. For this purpose, arg#0 - is the "json" path/CLI arg. - */; - struct { /* "garbage collector" */ - cson_value * v; - cson_object * o; - } gc; - struct { /* JSON POST data. */ - cson_value * v; - cson_array * a; - int offset; /* Tells us which PATH_INFO/CLI args - part holds the "json" command, so - that we can account for sub-repos - and path prefixes. This is handled - differently for CLI and CGI modes. - */ - } cmd; - struct { /* JSON POST data. */ - cson_value * v; - cson_object * o; - } post; - struct { /* GET/COOKIE params in JSON mode. */ - cson_value * v; - cson_object * o; - } param; - struct { - cson_value * v; - cson_object * o; - } reqPayload; /* request payload object (if any) */ - struct { /* response warnings */ - cson_value * v; - cson_array * a; - } warnings; - } json; }; /* ** Macro for debugging: */ @@ -270,11 +217,11 @@ upr = mid - 1; }else{ lwr = mid + 1; } } - for(m=cnt=0, i=upr-2; i<=upr+3 && i 1); } /* -** atexit() handler which frees up "some" of the resources -** used by fossil. +** Search g.argv for arguments "--args FILENAME". If found, then +** (1) remove the two arguments from g.argv +** (2) Read the file FILENAME +** (3) Use the contents of FILE to replace the two removed arguments: +** (a) Ignore blank lines in the file +** (b) Each non-empty line of the file is an argument, except +** (c) If the line begins with "-" and contains a space, it is broken +** into two arguments at the space. */ -void fossil_atexit() { +static void expand_args_option(void){ + Blob file = empty_blob; /* Content of the file */ + Blob line = empty_blob; /* One line of the file */ + unsigned int nLine; /* Number of lines in the file*/ + unsigned int i, j, k; /* Loop counters */ + int n; /* Number of bytes in one line */ + char *z; /* General use string pointer */ + char **newArgv; /* New expanded g.argv under construction */ + char const * zFileName; /* input file name */ + FILE * zInFile; /* input FILE */ + for(i=1; i =g.argc-1 ) return; - cson_value_free(g.json.gc.v); - memset(&g.json, 0, sizeof(g.json)); - if(g.db){ - db_close(0); + zFileName = g.argv[i+1]; + zInFile = (0==strcmp("-",zFileName)) + ? stdin + : fopen(zFileName,"rb"); + if(!zInFile){ + fossil_panic("Cannot open -args file [%s]", zFileName); + }else{ + blob_read_from_channel(&file, zInFile, -1); + if(stdin != zInFile){ + fclose(zInFile); + } + zInFile = NULL; + } + z = blob_str(&file); + for(k=0, nLine=1; z[k]; k++) if( z[k]=='\n' ) nLine++; + newArgv = fossil_malloc( sizeof(char*)*(g.argc + nLine*2) ); + for(j=0; j0 ){ + if( n<=1 ) continue; + z = blob_buffer(&line); + z[n-1] = 0; + if((n>1) && ('\r'==z[n-2])){ + if(n==2) continue /*empty line*/; + z[n-2] = 0; + } + newArgv[j++] = z; + if( z[0]=='-' ){ + for(k=1; z[k] && !fossil_isspace(z[k]); k++){} + if( z[k] ){ + z[k] = 0; + k++; + if( z[k] ) newArgv[j++] = &z[k]; + } + } } + i += 2; + while( i %h %h
", z); cgi_reply(); }else{ char *zOut = mprintf("\r%s: %s\n", fossil_nameofexe(), z); fossil_puts(zOut, 1); } db_force_rollback(); - fossil_exit(rc); + fossil_exit(1); } /* This routine works like fossil_fatal() except that if called ** recursively, the recursive call is a no-op. ** @@ -483,32 +459,25 @@ ** be prepared for this routine to return. */ void fossil_fatal_recursive(const char *zFormat, ...){ char *z; va_list ap; - int rc = 1; if( mainInFatalError ) return; mainInFatalError = 1; va_start(ap, zFormat); z = vmprintf(zFormat, ap); va_end(ap); - if( g.json.isJsonMode ){ - json_err( g.json.resultCode, z, 1 ); - if( g.isHTTP ){ - rc = 0 /* avoid HTTP 500 */; - } - }else if( g.cgiOutput ){ + if( g.cgiOutput ){ g.cgiOutput = 0; cgi_printf("%h
", z); cgi_reply(); }else{ char *zOut = mprintf("\r%s: %s\n", fossil_nameofexe(), z); fossil_puts(zOut, 1); - free(zOut); } db_force_rollback(); - fossil_exit(rc); + fossil_exit(1); } /* Print a warning message */ void fossil_warning(const char *zFormat, ...){ @@ -515,20 +484,17 @@ char *z; va_list ap; va_start(ap, zFormat); z = vmprintf(zFormat, ap); va_end(ap); - if(g.json.isJsonMode){ - json_warn( FSL_JSON_W_UNKNOWN, z ); - }else if( g.cgiOutput ){ + if( g.cgiOutput ){ cgi_printf("%h
", z); }else{ char *zOut = mprintf("\r%s: %s\n", fossil_nameofexe(), z); fossil_puts(zOut, 1); free(zOut); } - free(z); } /* ** Malloc and free routines that cannot fail */ @@ -950,11 +916,11 @@ } /* ** Send an HTTP redirect back to the designated Index Page. */ -void fossil_redirect_home(void){ +NORETURN void fossil_redirect_home(void){ cgi_redirectf("%s%s", g.zTop, db_get("index-page", "/index")); } /* ** If running as root, chroot to the directory containing the @@ -1064,17 +1030,13 @@ if( szFile<1024 ){ if( zNotFound ){ cgi_redirect(zNotFound); }else{ - if(g.json.isJsonMode){ - json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1); - }else{ - @Not Found
- cgi_set_status(404, "not found"); - cgi_reply(); - } + @Not Found
+ cgi_set_status(404, "not found"); + cgi_reply(); } return; } break; } @@ -1099,16 +1061,11 @@ zPathInfo = "/xfer"; } set_base_url(); if( zPathInfo==0 || zPathInfo[0]==0 || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){ - if(g.json.isJsonMode){ - json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1); - fossil_exit(0); - }else{ - fossil_redirect_home() /*does not return*/; - } + fossil_redirect_home(); }else{ zPath = mprintf("%s", zPathInfo); } /* Make g.zPath point to the first element of the path. Make @@ -1165,12 +1122,10 @@ break; } if( g.zExtra ){ /* CGI parameters get this treatment elsewhere, but places like getfile ** will use g.zExtra directly. - ** Reminder: the login mechanism uses 'name' differently, and may - ** eventually have a problem/collision with this. */ dehttpize(g.zExtra); cgi_set_parameter_nocopy("name", g.zExtra); } @@ -1177,25 +1132,17 @@ /* Locate the method specified by the path and execute the function ** that implements that method. */ if( name_search(g.zPath, aWebpage, count(aWebpage), &idx) && name_search("not_found", aWebpage, count(aWebpage), &idx) ){ - if(g.json.isJsonMode){ - json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,0); - }else{ - cgi_set_status(404,"Not Found"); - @Not Found
- @Page not found: %h(g.zPath)
- } + cgi_set_status(404,"Not Found"); + @Not Found
+ @Page not found: %h(g.zPath)
}else if( aWebpage[idx].xFunc!=page_xfer && db_schema_is_outofdate() ){ - if(g.json.isJsonMode){ - json_err(FSL_JSON_E_DB_NEEDS_REBUILD,NULL,0); - }else{ - @Server Configuration Error
- @The database schema on the server is out-of-date. Please ask - @ the administrator to run fossil rebuild.
- } + @Server Configuration Error
+ @The database schema on the server is out-of-date. Please ask + @ the administrator to run fossil rebuild.
}else{ aWebpage[idx].xFunc(); } /* Return the result. Index: src/main.mk ================================================================== --- src/main.mk +++ src/main.mk @@ -47,14 +47,10 @@ $(SRCDIR)/http_socket.c \ $(SRCDIR)/http_ssl.c \ $(SRCDIR)/http_transport.c \ $(SRCDIR)/import.c \ $(SRCDIR)/info.c \ - $(SRCDIR)/json.c \ - $(SRCDIR)/json_login.c \ - $(SRCDIR)/json_timeline.c \ - $(SRCDIR)/json_wiki.c \ $(SRCDIR)/leaf.c \ $(SRCDIR)/login.c \ $(SRCDIR)/main.c \ $(SRCDIR)/manifest.c \ $(SRCDIR)/md5.c \ @@ -135,14 +131,10 @@ $(OBJDIR)/http_socket_.c \ $(OBJDIR)/http_ssl_.c \ $(OBJDIR)/http_transport_.c \ $(OBJDIR)/import_.c \ $(OBJDIR)/info_.c \ - $(OBJDIR)/json_.c \ - $(OBJDIR)/json_login_.c \ - $(OBJDIR)/json_timeline_.c \ - $(OBJDIR)/json_wiki_.c \ $(OBJDIR)/leaf_.c \ $(OBJDIR)/login_.c \ $(OBJDIR)/main_.c \ $(OBJDIR)/manifest_.c \ $(OBJDIR)/md5_.c \ @@ -223,14 +215,10 @@ $(OBJDIR)/http_socket.o \ $(OBJDIR)/http_ssl.o \ $(OBJDIR)/http_transport.o \ $(OBJDIR)/import.o \ $(OBJDIR)/info.o \ - $(OBJDIR)/json.o \ - $(OBJDIR)/json_login.o \ - $(OBJDIR)/json_timeline.o \ - $(OBJDIR)/json_wiki.o \ $(OBJDIR)/leaf.o \ $(OBJDIR)/login.o \ $(OBJDIR)/main.o \ $(OBJDIR)/manifest.o \ $(OBJDIR)/md5.o \ @@ -313,11 +301,11 @@ # using -lsqlite3. SQLITE3_OBJ.1 = SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o SQLITE3_OBJ. = $(SQLITE3_OBJ.0) -EXTRAOBJ = $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) $(OBJDIR)/shell.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o $(OBJDIR)/cson_amalgamation.o +EXTRAOBJ = $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) $(OBJDIR)/shell.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o $(APPNAME): $(OBJDIR)/headers $(OBJ) $(EXTRAOBJ) $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) # This rule prevents make from using its default rules to try build @@ -331,14 +319,13 @@ $(OBJDIR)/page_index.h: $(TRANS_SRC) $(OBJDIR)/mkindex $(OBJDIR)/mkindex $(TRANS_SRC) >$@ $(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/makeheaders $(OBJDIR)/VERSION.h - $(OBJDIR)/makeheaders $(OBJDIR)/add_.c:$(OBJDIR)/add.h $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h $(OBJDIR)/configure_.c:$(OBJDIR)/configure.h $(OBJDIR)/content_.c:$(OBJDIR)/content.h $(OBJDIR)/db_.c:$(OBJDIR)/db.h $(OBJDIR)/delta_.c:$(OBJDIR)/delta.h $(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h $(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h $(OBJDIR)/event_.c:$(OBJDIR)/event.h $(OBJDIR)/export_.c:$(OBJDIR)/export.h $(OBJDIR)/file_.c:$(OBJDIR)/file.h $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h $(OBJDIR)/http_.c:$(OBJDIR)/http.h $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h $(OBJDIR)/import_.c:$(OBJDIR)/import.h $(OBJDIR)/info_.c:$(OBJDIR)/info.h $(OBJDIR)/json_.c:$(OBJDIR)/json.h $(OBJDIR)/json_login_.c:$(OBJDIR)/json_login.h $(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h $(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h $(OBJDIR)/login_.c:$(OBJDIR)/login.h $(OBJDIR)/main_.c:$(OBJDIR)/main.h $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h $(OBJDIR)/name_.c:$(OBJDIR)/name.h $(OBJDIR)/path_.c:$(OBJDIR)/path.h $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h $(OBJDIR)/report_.c:$(OBJDIR)/report.h $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h $(OBJDIR)/search_.c:$(OBJDIR)/search.h $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h $(OBJDIR)/style_.c:$(OBJDIR)/style.h $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h $(OBJDIR)/update_.c:$(OBJDIR)/update.h $(OBJDIR)/url_.c:$(OBJDIR)/url.h $(OBJDIR)/user_.c:$(OBJDIR)/user.h $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h $(OBJDIR)/VERSION.h + $(OBJDIR)/makeheaders $(OBJDIR)/add_.c:$(OBJDIR)/add.h $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h $(OBJDIR)/configure_.c:$(OBJDIR)/configure.h $(OBJDIR)/content_.c:$(OBJDIR)/content.h $(OBJDIR)/db_.c:$(OBJDIR)/db.h $(OBJDIR)/delta_.c:$(OBJDIR)/delta.h $(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h $(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h $(OBJDIR)/event_.c:$(OBJDIR)/event.h $(OBJDIR)/export_.c:$(OBJDIR)/export.h $(OBJDIR)/file_.c:$(OBJDIR)/file.h $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h $(OBJDIR)/http_.c:$(OBJDIR)/http.h $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h $(OBJDIR)/import_.c:$(OBJDIR)/import.h $(OBJDIR)/info_.c:$(OBJDIR)/info.h $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h $(OBJDIR)/login_.c:$(OBJDIR)/login.h $(OBJDIR)/main_.c:$(OBJDIR)/main.h $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h $(OBJDIR)/name_.c:$(OBJDIR)/name.h $(OBJDIR)/path_.c:$(OBJDIR)/path.h $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h $(OBJDIR)/report_.c:$(OBJDIR)/report.h $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h $(OBJDIR)/search_.c:$(OBJDIR)/search.h $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h $(OBJDIR)/style_.c:$(OBJDIR)/style.h $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h $(OBJDIR)/update_.c:$(OBJDIR)/update.h $(OBJDIR)/url_.c:$(OBJDIR)/url.h $(OBJDIR)/user_.c:$(OBJDIR)/user.h $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h $(OBJDIR)/VERSION.h touch $(OBJDIR)/headers $(OBJDIR)/headers: Makefile -$(OBJDIR)/json.o $(OBJDIR)/json_login.o $(OBJDIR)/json_wiki.o $(OBJDIR)/json_timeline.o : $(SRCDIR)/json_detail.h Makefile: $(OBJDIR)/add_.c: $(SRCDIR)/add.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/add.c >$(OBJDIR)/add_.c $(OBJDIR)/add.o: $(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h @@ -602,38 +589,10 @@ $(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c $(OBJDIR)/info.h: $(OBJDIR)/headers -$(OBJDIR)/json_.c: $(SRCDIR)/json.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json.c >$(OBJDIR)/json_.c - -$(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/json.o -c $(OBJDIR)/json_.c - -$(OBJDIR)/json.h: $(OBJDIR)/headers -$(OBJDIR)/json_login_.c: $(SRCDIR)/json_login.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_login.c >$(OBJDIR)/json_login_.c - -$(OBJDIR)/json_login.o: $(OBJDIR)/json_login_.c $(OBJDIR)/json_login.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/json_login.o -c $(OBJDIR)/json_login_.c - -$(OBJDIR)/json_login.h: $(OBJDIR)/headers -$(OBJDIR)/json_timeline_.c: $(SRCDIR)/json_timeline.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_timeline.c >$(OBJDIR)/json_timeline_.c - -$(OBJDIR)/json_timeline.o: $(OBJDIR)/json_timeline_.c $(OBJDIR)/json_timeline.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/json_timeline.o -c $(OBJDIR)/json_timeline_.c - -$(OBJDIR)/json_timeline.h: $(OBJDIR)/headers -$(OBJDIR)/json_wiki_.c: $(SRCDIR)/json_wiki.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_wiki.c >$(OBJDIR)/json_wiki_.c - -$(OBJDIR)/json_wiki.o: $(OBJDIR)/json_wiki_.c $(OBJDIR)/json_wiki.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/json_wiki.o -c $(OBJDIR)/json_wiki_.c - -$(OBJDIR)/json_wiki.h: $(OBJDIR)/headers $(OBJDIR)/leaf_.c: $(SRCDIR)/leaf.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/leaf.c >$(OBJDIR)/leaf_.c $(OBJDIR)/leaf.o: $(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/leaf.o -c $(OBJDIR)/leaf_.c @@ -950,8 +909,5 @@ $(XTCC) -I$(SRCDIR) -c $(SRCDIR)/th.c -o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o: $(SRCDIR)/th_lang.c $(XTCC) -I$(SRCDIR) -c $(SRCDIR)/th_lang.c -o $(OBJDIR)/th_lang.o -$(OBJDIR)/cson_amalgamation.o: $(SRCDIR)/cson_amalgamation.c - $(XTCC) -I$(SRCDIR) -c $(SRCDIR)/cson_amalgamation.c -o $(OBJDIR)/cson_amalgamation.o - Index: src/makemake.tcl ================================================================== --- src/makemake.tcl +++ src/makemake.tcl @@ -53,14 +53,10 @@ http http_socket http_transport import info - json - json_login - json_timeline - json_wiki leaf login main manifest md5 @@ -206,12 +202,11 @@ EXTRAOBJ = \ $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) \ $(OBJDIR)/shell.o \ $(OBJDIR)/th.o \ - $(OBJDIR)/th_lang.o \ - $(OBJDIR)/cson_amalgamation.o + $(OBJDIR)/th_lang.o $(APPNAME): $(OBJDIR)/headers $(OBJ) $(EXTRAOBJ) $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) # This rule prevents make from using its default rules to try build @@ -230,19 +225,17 @@ append mhargs " \$(OBJDIR)/${s}_.c:\$(OBJDIR)/$s.h" set extra_h($s) {} } append mhargs " \$(SRCDIR)/sqlite3.h" append mhargs " \$(SRCDIR)/th.h" -#append mhargs " \$(SRCDIR)/cson_amalgamation.h" append mhargs " \$(OBJDIR)/VERSION.h" writeln "\$(OBJDIR)/page_index.h: \$(TRANS_SRC) \$(OBJDIR)/mkindex" writeln "\t\$(OBJDIR)/mkindex \$(TRANS_SRC) >$@" writeln "\$(OBJDIR)/headers:\t\$(OBJDIR)/page_index.h \$(OBJDIR)/makeheaders \$(OBJDIR)/VERSION.h" writeln "\t\$(OBJDIR)/makeheaders $mhargs" writeln "\ttouch \$(OBJDIR)/headers" writeln "\$(OBJDIR)/headers: Makefile" -writeln "\$(OBJDIR)/json.o \$(OBJDIR)/json_login.o \$(OBJDIR)/json_wiki.o \$(OBJDIR)/json_timeline.o : \$(SRCDIR)/json_detail.h" writeln "Makefile:" set extra_h(main) \$(OBJDIR)/page_index.h foreach s [lsort $src] { writeln "\$(OBJDIR)/${s}_.c:\t\$(SRCDIR)/$s.c \$(OBJDIR)/translate" @@ -271,15 +264,10 @@ writeln "\$(OBJDIR)/th.o:\t\$(SRCDIR)/th.c" writeln "\t\$(XTCC) -I\$(SRCDIR) -c \$(SRCDIR)/th.c -o \$(OBJDIR)/th.o\n" writeln "\$(OBJDIR)/th_lang.o:\t\$(SRCDIR)/th_lang.c" writeln "\t\$(XTCC) -I\$(SRCDIR) -c \$(SRCDIR)/th_lang.c -o \$(OBJDIR)/th_lang.o\n" - -set opt {} -writeln "\$(OBJDIR)/cson_amalgamation.o:\t\$(SRCDIR)/cson_amalgamation.c" -writeln "\t\$(XTCC) -I\$(SRCDIR) -c \$(SRCDIR)/cson_amalgamation.c -o \$(OBJDIR)/cson_amalgamation.o\n" - close $output_file # # End of the main.mk output ############################################################################## @@ -424,12 +412,11 @@ EXTRAOBJ = \ $(OBJDIR)/sqlite3.o \ $(OBJDIR)/shell.o \ $(OBJDIR)/th.o \ - $(OBJDIR)/th_lang.o \ - $(OBJDIR)/cson_amalgamation.o + $(OBJDIR)/th_lang.o $(APPNAME): $(OBJDIR)/headers $(OBJ) $(EXTRAOBJ) $(OBJDIR)/icon.o $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) $(OBJDIR)/icon.o # This rule prevents make from using its default rules to try build @@ -479,15 +466,10 @@ writeln "\$(OBJDIR)/sqlite3.o:\t\$(SRCDIR)/sqlite3.c" set opt $SQLITE_OPTIONS writeln "\t\$(XTCC) $opt -c \$(SRCDIR)/sqlite3.c -o \$(OBJDIR)/sqlite3.o\n" -set opt {} -writeln "\$(OBJDIR)/cson_amalgamation.o:\t\$(SRCDIR)/cson_amalgamation.c" -writeln "\t\$(XTCC) $opt -c \$(SRCDIR)/cson_amalgamation.c -o \$(OBJDIR)/cson_amalgamation.o\n" -writeln "\$(OBJDIR)/json.o \$(OBJDIR)/json_login.o \$(OBJDIR)/json_wiki.o \$(OBJDIR)/json_timeline.o : \$(SRCDIR)/json_detail.h" - writeln "\$(OBJDIR)/shell.o:\t\$(SRCDIR)/shell.c \$(SRCDIR)/sqlite3.h" set opt {-Dmain=sqlite3_shell} append opt " -DSQLITE_OMIT_LOAD_EXTENSION=1" writeln "\t\$(XTCC) $opt -c \$(SRCDIR)/shell.c -o \$(OBJDIR)/shell.o\n" @@ -596,13 +578,10 @@ $(TCC) -o$@ -c $** $(OBJDIR)\th_lang$O : $(SRCDIR)\th_lang.c $(TCC) -o$@ -c $** -$(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h - cp $@ $@ - VERSION.h : version$E $B\manifest.uuid $B\manifest $B\VERSION +$** > $@ page_index.h: mkindex$E $(SRC) +$** > $@ @@ -611,16 +590,10 @@ -del $(OBJDIR)\*.obj -del *.obj *_.c *.h *.map realclean: -del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E - -$(OBJDIR)\json$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h - } foreach s [lsort $src] { writeln "\$(OBJDIR)\\$s\$O : ${s}_.c ${s}.h" writeln "\t\$(TCC) -o\$@ -c ${s}_.c\n" @@ -630,11 +603,11 @@ writeln -nonewline "headers: makeheaders\$E page_index.h VERSION.h\n\t +makeheaders\$E " foreach s [lsort $src] { writeln -nonewline "${s}_.c:$s.h " } -writeln "\$(SRCDIR)\\sqlite3.h \$(SRCDIR)\\th.h VERSION.h \$(SRCDIR)\\cson_amalgamation.h" +writeln "\$(SRCDIR)\\sqlite3.h \$(SRCDIR)\\th.h VERSION.h" writeln "\t@copy /Y nul: headers" close $output_file # # End of the win/Makefile.dmc output @@ -744,12 +717,10 @@ $(OX)\th_lang$O : $(SRCDIR)\th_lang.c $(TCC) /Fo$@ -c $** VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION $** > $@ -$(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h - cp $(SRCDIR)\cson_amalgamation.h $@ page_index.h: mkindex$E $(SRC) $** > $@ clean: @@ -758,15 +729,10 @@ -del headers linkopts realclean: -del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E -$(OBJDIR)\json$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h - } foreach s [lsort $src] { writeln "\$(OX)\\$s\$O : ${s}_.c ${s}.h" writeln "\t\$(TCC) /Fo\$@ -c ${s}_.c\n" writeln "${s}_.c : \$(SRCDIR)\\$s.c" @@ -775,11 +741,11 @@ writeln -nonewline "headers: makeheaders\$E page_index.h VERSION.h\n\tmakeheaders\$E " foreach s [lsort $src] { writeln -nonewline "${s}_.c:$s.h " } -writeln "\$(SRCDIR)\\sqlite3.h \$(SRCDIR)\\th.h VERSION.h \$(SRCDIR)\\cson_amalgamation.h" +writeln "\$(SRCDIR)\\sqlite3.h \$(SRCDIR)\\th.h VERSION.h" writeln "\t@copy /Y nul: headers" close $output_file # Index: src/name.c ================================================================== --- src/name.c +++ src/name.c @@ -154,11 +154,11 @@ " WHERE tag.tagname='sym-%q' " " AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 " " AND event.objid=tagxref.rid " " AND blob.rid=event.objid " " AND event.type GLOB '%q'" - " ORDER BY event.mtime DESC ", + " ORDER BY event.mtime DESC /*sort*/", zTag, zType ); if( zUuid==0 ){ int nTag = strlen(zTag); int i; @@ -180,11 +180,11 @@ " AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 " " AND event.objid=tagxref.rid " " AND blob.rid=event.objid " " AND event.mtime<=julianday(%Q %s)" " AND event.type GLOB '%q'" - " ORDER BY event.mtime DESC ", + " ORDER BY event.mtime DESC /*sort*/ ", zTagBase, zDate, (useUtc ? "" : ",'utc'"), zType ); break; } } Index: src/path.c ================================================================== --- src/path.c +++ src/path.c @@ -95,12 +95,13 @@ } /* ** Construct the path from path.pStart to path.pEnd in the u.pTo fields. */ -void path_reverse_path(void){ +static void path_reverse_path(void){ PathNode *p; + assert( path.pEnd!=0 ); for(p=path.pEnd; p && p->pFrom; p = p->pFrom){ p->pFrom->u.pTo = p; } path.pEnd->u.pTo = 0; assert( p==path.pStart ); Index: src/rebuild.c ================================================================== --- src/rebuild.c +++ src/rebuild.c @@ -786,10 +786,11 @@ ); if( bVerily ){ db_multi_exec( "DELETE FROM concealed;" "UPDATE rcvfrom SET ipaddr='unknown';" + "DROP TABLE IF EXISTS accesslog;" "UPDATE user SET photo=NULL, info='';" ); } } if( !bNeedRebuild ){ Index: src/report.c ================================================================== --- src/report.c +++ src/report.c @@ -19,11 +19,10 @@ */ #include "config.h" #include#include "report.h" #include -#include "cson_amalgamation.h" /* Forward references to static routines */ static void report_format_hints(void); /* @@ -1155,77 +1154,5 @@ report_unrestrict_sql(); if( zFilter ){ free(zSql); } } - - -void rptshowJson( - const char *zRep, - char const * zLimit, - const char *zFilter, - char indention -){ - Stmt q; - char *zSql; - char const *zTitle; - char const *zOwner; - char *zClrKey; - char *zErr1 = 0; - int count = 0; - int rn; - int rc; - cson_value * zJVal = NULL; - - if (!zRep || !strcmp(zRep,zFullTicketRptRn) || !strcmp(zRep,zFullTicketRptTitle) ){ - zTitle = zFullTicketRptTitle; - zSql = mprintf("SELECT * FROM ticket"); - zOwner = g.zLogin; - zClrKey = ""; - }else{ - rn = atoi(zRep); - if( rn ){ - db_prepare(&q, - "SELECT title, sqlcode, owner, cols FROM reportfmt WHERE rn=%d", rn); - }else{ - db_prepare(&q, - "SELECT title, sqlcode, owner, cols FROM reportfmt WHERE title=%Q", zRep); - } - if( db_step(&q)!=SQLITE_ROW ){ - db_finalize(&q); - rpt_list_reports(); - fossil_fatal("unkown report format(%s)!",zRep); - } - zTitle = db_column_malloc(&q, 0)/*leak!*/; - zSql = db_column_malloc(&q, 1); - zOwner = db_column_malloc(&q, 2)/*leak!*/; - zClrKey = db_column_malloc(&q, 3); - db_finalize(&q); - } - if( zFilter ){ - char * old = zSql; - zSql = mprintf("SELECT * FROM (%s) WHERE %s%s%s",old,zFilter, - (zLimit?" LIMIT ":""), (zLimit?zLimit:"")); - free(old); - }else if( zLimit ){ - char * old = zSql; - zSql = mprintf("%s LIMIT %s",old, zLimit); - free(old); - } - count = 0; - /*fprintf(stderr,"SQL=[%s]\n",zSql);*/ - sqlite3_set_authorizer(g.db, report_query_authorizer, (void*)&zErr1); - rc = cson_sqlite3_sql_to_json(g.db, &zJVal, zSql, 1); - if( 0 == rc ){ - cson_output_opt outOpt = cson_output_opt_empty; - outOpt.addNewline = 1; - outOpt.indentation = indention; - rc = cson_output_FILE( zJVal, stdout, &outOpt ); - } else{ - fossil_fatal("sql-to-json failed with code %d (%s)!", rc, - cson_rc_string(rc)); - } - sqlite3_set_authorizer(g.db, 0, 0); - cson_value_free(zJVal); - free(zSql); - free(zClrKey); -} Index: src/setup.c ================================================================== --- src/setup.c +++ src/setup.c @@ -848,18 +848,27 @@ @ fossil http commands @ without the "--localauth" option. @ - The server is started from CGI without the "localauth" keyword @ in the CGI script. @ + @ + @
** */ SQLITE_API int sqlite3_prepare( @@ -3567,10 +3573,17 @@ ** ^(In those routines that have a fourth argument, its value is the ** number of bytes in the parameter. To be clear: the value is the ** number of bytes in the value, not the number of characters.)^ ** ^If the fourth parameter is negative, the length of the string is ** the number of bytes up to the first zero terminator. +** If a non-negative fourth parameter is provided to sqlite3_bind_text() +** or sqlite3_bind_text16() then that parameter must be the byte offset +** where the NUL terminator would occur assuming the string were NUL +** terminated. If any NUL characters occur at byte offsets less than +** the value of the fourth parameter then the resulting string value will +** contain embedded NULs. The result of expressions involving strings +** with embedded NULs is undefined. ** ** ^The fifth argument to sqlite3_bind_blob(), sqlite3_bind_text(), and ** sqlite3_bind_text16() is a destructor used to dispose of the BLOB or ** string after SQLite has finished with it. ^The destructor is called ** to dispose of the BLOB or string even if the call to sqlite3_bind_blob(), @@ -3900,10 +3913,16 @@ ** current row of the result set of [prepared statement] P. ** ^If prepared statement P does not have results ready to return ** (via calls to the [sqlite3_column_int | sqlite3_column_*()] of ** interfaces) then sqlite3_data_count(P) returns 0. ** ^The sqlite3_data_count(P) routine also returns 0 if P is a NULL pointer. +** ^The sqlite3_data_count(P) routine returns 0 if the previous call to +** [sqlite3_step](P) returned [SQLITE_DONE]. ^The sqlite3_data_count(P) +** will return non-zero if previous call to [sqlite3_step](P) returned +** [SQLITE_ROW], except in the case of the [PRAGMA incremental_vacuum] +** where it always returns zero since each step of that multi-step +** pragma returns 0 columns of data. ** ** See also: [sqlite3_column_count()] */ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); @@ -4579,11 +4598,16 @@ ** is negative, then SQLite takes result text from the 2nd parameter ** through the first zero character. ** ^If the 3rd parameter to the sqlite3_result_text* interfaces ** is non-negative, then as many bytes (not characters) of the text ** pointed to by the 2nd parameter are taken as the application-defined -** function result. +** function result. If the 3rd parameter is non-negative, then it +** must be the byte offset into the string where the NUL terminator would +** appear if the string where NUL terminated. If any NUL characters occur +** in the string at a byte offset that is less than the value of the 3rd +** parameter, then the resulting string will contain embedded NULs and the +** result of expressions operating on strings with embedded NULs is undefined. ** ^If the 4th parameter to the sqlite3_result_text* interfaces ** or sqlite3_result_blob is a non-NULL pointer, then SQLite calls that ** function as the destructor on the text or BLOB result when it has ** finished using that result. ** ^If the 4th parameter to the sqlite3_result_text* interfaces or to @@ -6362,20 +6386,34 @@ **
+ onoff_attribute("Enable /test_env", + "test_env_enable", "test_env_enable", 0); + @When enabled, the %h(g.zBaseURL)/test_env URL is available to all + @ users. When disabled (the default) only users Admin and Setup can visit + @ the /test_env page. + @
+ @ @
onoff_attribute("Allow REMOTE_USER authentication", "remote_user_ok", "remote_user_ok", 0); @When enabled, if the REMOTE_USER environment variable is set to the @ login name of a valid user and no other login credentials are available, @ then the REMOTE_USER is accepted as an authenticated user. @
- + @ @
entry_attribute("Login expiration time", 6, "cookie-expire", "cex", "8766"); @The number of hours for which a login is valid. This must be a @ positive number. The default is 8760 hours which is approximately equal @ to a year.
Index: src/sha1.c ================================================================== --- src/sha1.c +++ src/sha1.c @@ -82,11 +82,11 @@ #define d qq[3] #define e qq[4] void SHA1Transform(unsigned int state[5], const unsigned char buffer[64]) { - unsigned int qq[5]; // a, b, c, d, e; + unsigned int qq[5]; /* a, b, c, d, e; */ static int one = 1; unsigned int block[16]; memcpy(block, buffer, 64); memcpy(qq,state,5*sizeof(unsigned int)); Index: src/shell.c ================================================================== --- src/shell.c +++ src/shell.c @@ -10,15 +10,26 @@ ** ************************************************************************* ** This file contains code to implement the "sqlite" command line ** utility for accessing SQLite databases. */ -#if defined(_WIN32) || defined(WIN32) +#if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS) /* This needs to come before any includes for MSVC compiler */ #define _CRT_SECURE_NO_WARNINGS #endif +/* +** Enable large-file support for fopen() and friends on unix. +*/ +#ifndef SQLITE_DISABLE_LFS +# define _LARGE_FILE 1 +# ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +# endif +# define _LARGEFILE_SOURCE 1 +#endif + #include#include #include #include #include "sqlite3.h" @@ -58,11 +69,11 @@ #define isatty(h) _isatty(h) #define access(f,m) _access((f),(m)) #else /* Make sure isatty() has a prototype. */ -extern int isatty(); +extern int isatty(int); #endif #if defined(_WIN32_WCE) /* Windows CE (arm-wince-mingw32ce-gcc) does not provide isatty() * thus we always assume that we have a console. That can be @@ -71,10 +82,15 @@ #define isatty(x) 1 #endif /* True if the timer is enabled */ static int enableTimer = 0; + +/* ctype macros that work with signed characters */ +#define IsSpace(X) isspace((unsigned char)X) +#define IsDigit(X) isdigit((unsigned char)X) +#define ToLower(X) (char)tolower((unsigned char)X) #if !defined(_WIN32) && !defined(WIN32) && !defined(__OS2__) && !defined(__RTP__) && !defined(_WRS_KERNEL) #include #include @@ -263,27 +279,27 @@ /* ** Determines if a string is a number of not. */ static int isNumber(const char *z, int *realnum){ if( *z=='-' || *z=='+' ) z++; - if( !isdigit(*z) ){ + if( !IsDigit(*z) ){ return 0; } z++; if( realnum ) *realnum = 0; - while( isdigit(*z) ){ z++; } + while( IsDigit(*z) ){ z++; } if( *z=='.' ){ z++; - if( !isdigit(*z) ) return 0; - while( isdigit(*z) ){ z++; } + if( !IsDigit(*z) ) return 0; + while( IsDigit(*z) ){ z++; } if( realnum ) *realnum = 1; } if( *z=='e' || *z=='E' ){ z++; if( *z=='+' || *z=='-' ) z++; - if( !isdigit(*z) ) return 0; - while( isdigit(*z) ){ z++; } + if( !IsDigit(*z) ) return 0; + while( IsDigit(*z) ){ z++; } if( realnum ) *realnum = 1; } return *z==0; } @@ -320,22 +336,20 @@ */ static char *local_getline(char *zPrompt, FILE *in){ char *zLine; int nLine; int n; - int eol; if( zPrompt && *zPrompt ){ printf("%s",zPrompt); fflush(stdout); } nLine = 100; zLine = malloc( nLine ); if( zLine==0 ) return 0; n = 0; - eol = 0; - while( !eol ){ + while( 1 ){ if( n+100>nLine ){ nLine = nLine*2 + 100; zLine = realloc(zLine, nLine); if( zLine==0 ) return 0; } @@ -343,19 +357,18 @@ if( n==0 ){ free(zLine); return 0; } zLine[n] = 0; - eol = 1; break; } while( zLine[n] ){ n++; } if( n>0 && zLine[n-1]=='\n' ){ n--; if( n>0 && zLine[n-1]=='\r' ) n--; zLine[n] = 0; - eol = 1; + break; } } zLine = realloc( zLine, n+1 ); return zLine; } @@ -400,10 +413,11 @@ sqlite3 *db; /* The database */ int echoOn; /* True to echo input commands */ int statsOn; /* True to display memory stats before each finalize */ int cnt; /* Number of records displayed so far */ FILE *out; /* Write results here */ + int nErr; /* Number of errors seen */ int mode; /* An output mode setting */ int writableSchema; /* True if PRAGMA writable_schema=ON */ int showHeader; /* True to show column names in List or Column mode */ char *zDestTable; /* Name of destination table when MODE_Insert */ char separator[20]; /* Separator character for MODE_List */ @@ -925,31 +939,37 @@ ** ** This is used, for example, to show the schema of the database by ** querying the SQLITE_MASTER table. */ static int run_table_dump_query( - FILE *out, /* Send output here */ - sqlite3 *db, /* Database to query */ - const char *zSelect, /* SELECT statement to extract content */ - const char *zFirstRow /* Print before first row, if not NULL */ + struct callback_data *p, /* Query context */ + const char *zSelect, /* SELECT statement to extract content */ + const char *zFirstRow /* Print before first row, if not NULL */ ){ sqlite3_stmt *pSelect; int rc; - rc = sqlite3_prepare(db, zSelect, -1, &pSelect, 0); + rc = sqlite3_prepare(p->db, zSelect, -1, &pSelect, 0); if( rc!=SQLITE_OK || !pSelect ){ + fprintf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(p->db)); + p->nErr++; return rc; } rc = sqlite3_step(pSelect); while( rc==SQLITE_ROW ){ if( zFirstRow ){ - fprintf(out, "%s", zFirstRow); + fprintf(p->out, "%s", zFirstRow); zFirstRow = 0; } - fprintf(out, "%s;\n", sqlite3_column_text(pSelect, 0)); + fprintf(p->out, "%s;\n", sqlite3_column_text(pSelect, 0)); rc = sqlite3_step(pSelect); } - return sqlite3_finalize(pSelect); + rc = sqlite3_finalize(pSelect); + if( rc!=SQLITE_OK ){ + fprintf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(p->db)); + p->nErr++; + } + return rc; } /* ** Allocate space and save off current error string. */ @@ -1027,11 +1047,16 @@ fprintf(pArg->out, "Lookaside failures due to size: %d\n", iHiwtr); sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Lookaside failures due to OOM: %d\n", iHiwtr); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset); - fprintf(pArg->out, "Pager Heap Usage: %d bytes\n", iCur); + fprintf(pArg->out, "Pager Heap Usage: %d bytes\n", iCur); iHiwtr = iCur = -1; + sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHiwtr, 1); + fprintf(pArg->out, "Page cache hits: %d\n", iCur); + iHiwtr = iCur = -1; + sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1); + fprintf(pArg->out, "Page cache misses: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Schema Heap Usage: %d bytes\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset); @@ -1067,10 +1092,11 @@ struct callback_data *pArg, /* Pointer to struct callback_data */ char **pzErrMsg /* Error msg written here */ ){ sqlite3_stmt *pStmt = NULL; /* Statement to execute. */ int rc = SQLITE_OK; /* Return Code */ + int rc2; const char *zLeftover; /* Tail of unprocessed SQL */ if( pzErrMsg ){ *pzErrMsg = NULL; } @@ -1083,11 +1109,11 @@ } }else{ if( !pStmt ){ /* this happens for a comment or white-space */ zSql = zLeftover; - while( isspace(zSql[0]) ) zSql++; + while( IsSpace(zSql[0]) ) zSql++; continue; } /* save off the prepared statment handle and reset row count */ if( pArg ){ @@ -1160,14 +1186,15 @@ } /* Finalize the statement just executed. If this fails, save a ** copy of the error message. Otherwise, set zSql to point to the ** next statement to execute. */ - rc = sqlite3_finalize(pStmt); + rc2 = sqlite3_finalize(pStmt); + if( rc!=SQLITE_NOMEM ) rc = rc2; if( rc==SQLITE_OK ){ zSql = zLeftover; - while( isspace(zSql[0]) ) zSql++; + while( IsSpace(zSql[0]) ) zSql++; }else if( pzErrMsg ){ *pzErrMsg = save_err_msg(db); } /* clear saved stmt handle */ @@ -1266,14 +1293,14 @@ return 1; } zSelect = appendText(zSelect, "|| ')' FROM ", 0); zSelect = appendText(zSelect, zTable, '"'); - rc = run_table_dump_query(p->out, p->db, zSelect, zPrepStmt); + rc = run_table_dump_query(p, zSelect, zPrepStmt); if( rc==SQLITE_CORRUPT ){ zSelect = appendText(zSelect, " ORDER BY rowid DESC", 0); - rc = run_table_dump_query(p->out, p->db, zSelect, 0); + run_table_dump_query(p, zSelect, 0); } if( zSelect ) free(zSelect); } return 0; } @@ -1285,23 +1312,34 @@ ** If we get a SQLITE_CORRUPT error, rerun the query after appending ** "ORDER BY rowid DESC" to the end. */ static int run_schema_dump_query( struct callback_data *p, - const char *zQuery, - char **pzErrMsg + const char *zQuery ){ int rc; - rc = sqlite3_exec(p->db, zQuery, dump_callback, p, pzErrMsg); + char *zErr = 0; + rc = sqlite3_exec(p->db, zQuery, dump_callback, p, &zErr); if( rc==SQLITE_CORRUPT ){ char *zQ2; int len = strlen30(zQuery); - if( pzErrMsg ) sqlite3_free(*pzErrMsg); + fprintf(p->out, "/****** CORRUPTION ERROR *******/\n"); + if( zErr ){ + fprintf(p->out, "/****** %s ******/\n", zErr); + sqlite3_free(zErr); + zErr = 0; + } zQ2 = malloc( len+100 ); if( zQ2==0 ) return rc; sqlite3_snprintf(sizeof(zQ2), zQ2, "%s ORDER BY rowid DESC", zQuery); - rc = sqlite3_exec(p->db, zQ2, dump_callback, p, pzErrMsg); + rc = sqlite3_exec(p->db, zQ2, dump_callback, p, &zErr); + if( rc ){ + fprintf(p->out, "/****** ERROR: %s ******/\n", zErr); + }else{ + rc = SQLITE_CORRUPT; + } + sqlite3_free(zErr); free(zQ2); } return rc; } @@ -1434,11 +1472,11 @@ */ static int booleanValue(char *zArg){ int val = atoi(zArg); int j; for(j=0; zArg[j]; j++){ - zArg[j] = (char)tolower(zArg[j]); + zArg[j] = ToLower(zArg[j]); } if( strcmp(zArg,"on")==0 ){ val = 1; }else if( strcmp(zArg,"yes")==0 ){ val = 1; @@ -1460,11 +1498,11 @@ char *azArg[50]; /* Parse the input line into tokens. */ while( zLine[i] && nArg out, "PRAGMA foreign_keys=OFF;\n"); fprintf(p->out, "BEGIN TRANSACTION;\n"); p->writableSchema = 0; - sqlite3_exec(p->db, "PRAGMA writable_schema=ON", 0, 0, 0); + sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0); + p->nErr = 0; if( nArg==1 ){ run_schema_dump_query(p, "SELECT name, type, sql FROM sqlite_master " - "WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'", 0 + "WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'" ); run_schema_dump_query(p, "SELECT name, type, sql FROM sqlite_master " - "WHERE name=='sqlite_sequence'", 0 + "WHERE name=='sqlite_sequence'" ); - run_table_dump_query(p->out, p->db, + run_table_dump_query(p, "SELECT sql FROM sqlite_master " "WHERE sql NOT NULL AND type IN ('index','trigger','view')", 0 ); }else{ int i; @@ -1572,12 +1610,12 @@ for(i=1; i out, p->db, + " AND sql NOT NULL"); + run_table_dump_query(p, "SELECT sql FROM sqlite_master " "WHERE sql NOT NULL" " AND type IN ('index','trigger','view')" " AND tbl_name LIKE shellstatic()", 0 ); @@ -1586,17 +1624,13 @@ } if( p->writableSchema ){ fprintf(p->out, "PRAGMA writable_schema=OFF;\n"); p->writableSchema = 0; } - sqlite3_exec(p->db, "PRAGMA writable_schema=OFF", 0, 0, 0); - if( zErrMsg ){ - fprintf(stderr,"Error: %s\n", zErrMsg); - sqlite3_free(zErrMsg); - }else{ - fprintf(p->out, "COMMIT;\n"); - } + sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); + sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0); + fprintf(p->out, p->nErr ? "ROLLBACK; -- due to errors\n" : "COMMIT;\n"); }else if( c=='e' && strncmp(azArg[0], "echo", n)==0 && nArg>1 && nArg<3 ){ p->echoOn = booleanValue(azArg[1]); }else @@ -1671,11 +1705,11 @@ nSep = strlen30(p->separator); if( nSep==0 ){ fprintf(stderr, "Error: non-null separator required for import\n"); return 1; } - zSql = sqlite3_mprintf("SELECT * FROM '%q'", zTable); + zSql = sqlite3_mprintf("SELECT * FROM %s", zTable); if( zSql==0 ){ fprintf(stderr, "Error: out of memory\n"); return 1; } nByte = strlen30(zSql); @@ -1693,11 +1727,11 @@ zSql = malloc( nByte + 20 + nCol*2 ); if( zSql==0 ){ fprintf(stderr, "Error: out of memory\n"); return 1; } - sqlite3_snprintf(nByte+20, zSql, "INSERT INTO '%q' VALUES(?", zTable); + sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zTable); j = strlen30(zSql); for(i=1; i db, "BEGIN", 0, 0, 0); zCommit = "COMMIT"; while( (zLine = local_getline(0, in))!=0 ){ char *z; - i = 0; lineno++; azCol[0] = zLine; for(i=0, z=zLine; *z && *z!='\n' && *z!='\r'; z++){ if( *z==p->separator[0] && strncmp(z, p->separator, nSep)==0 ){ *z = 0; @@ -2014,11 +2047,11 @@ memcpy(&data, p, sizeof(data)); data.showHeader = 0; data.mode = MODE_Semi; if( nArg>1 ){ int i; - for(i=0; azArg[1][i]; i++) azArg[1][i] = (char)tolower(azArg[1][i]); + for(i=0; azArg[1][i]; i++) azArg[1][i] = ToLower(azArg[1][i]); if( strcmp(azArg[1],"sqlite_master")==0 ){ char *new_argv[2], *new_colv[2]; new_argv[0] = "CREATE TABLE sqlite_master (\n" " type text,\n" " name text,\n" @@ -2200,11 +2233,11 @@ for(i=0; i<(int)(sizeof(aCtrl)/sizeof(aCtrl[0])); i++){ if( strncmp(azArg[1], aCtrl[i].zCtrlName, n)==0 ){ if( testctrl<0 ){ testctrl = aCtrl[i].ctrlCode; }else{ - fprintf(stderr, "ambiguous option name: \"%s\"\n", azArg[i]); + fprintf(stderr, "ambiguous option name: \"%s\"\n", azArg[1]); testctrl = -1; break; } } } @@ -2337,11 +2370,11 @@ /* ** Test to see if a line consists entirely of whitespace. */ static int _all_whitespace(const char *z){ for(; *z; z++){ - if( isspace(*(unsigned char*)z) ) continue; + if( IsSpace(z[0]) ) continue; if( *z=='/' && z[1]=='*' ){ z += 2; while( *z && (*z!='*' || z[1]!='/') ){ z++; } if( *z==0 ) return 0; z++; @@ -2362,15 +2395,15 @@ ** Return TRUE if the line typed in is an SQL command terminator other ** than a semi-colon. The SQL Server style "go" command is understood ** as is the Oracle "/". */ static int _is_command_terminator(const char *zLine){ - while( isspace(*(unsigned char*)zLine) ){ zLine++; }; + while( IsSpace(zLine[0]) ){ zLine++; }; if( zLine[0]=='/' && _all_whitespace(&zLine[1]) ){ return 1; /* Oracle */ } - if( tolower(zLine[0])=='g' && tolower(zLine[1])=='o' + if( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o' && _all_whitespace(&zLine[2]) ){ return 1; /* SQL Server */ } return 0; } @@ -2436,11 +2469,11 @@ memcpy(zLine,";",2); } nSqlPrior = nSql; if( zSql==0 ){ int i; - for(i=0; zLine[i] && isspace((unsigned char)zLine[i]); i++){} + for(i=0; zLine[i] && IsSpace(zLine[i]); i++){} if( zLine[i]!=0 ){ nSql = strlen30(zLine); zSql = malloc( nSql+3 ); if( zSql==0 ){ fprintf(stderr, "Error: out of memory\n"); @@ -2630,10 +2663,13 @@ " -version show SQLite version\n" " -vfs NAME use NAME as the default VFS\n" #ifdef SQLITE_ENABLE_VFSTRACE " -vfstrace enable tracing of all VFS calls\n" #endif +#ifdef SQLITE_ENABLE_MULTIPLEX + " -multiplex enable the multiplexor VFS\n" +#endif ; static void usage(int showDetail){ fprintf(stderr, "Usage: %s [OPTIONS] FILENAME [SQL]\n" "FILENAME is the name of an SQLite database. A new database is created\n" @@ -2705,10 +2741,11 @@ ** we do the actual processing of arguments later in a second pass. */ }else if( strcmp(argv[i],"-batch")==0 ){ stdin_is_interactive = 0; }else if( strcmp(argv[i],"-heap")==0 ){ +#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) int j, c; const char *zSize; sqlite3_int64 szHeap; zSize = argv[++i]; @@ -2717,11 +2754,10 @@ if( c=='M' ){ szHeap *= 1000000; break; } if( c=='K' ){ szHeap *= 1000; break; } if( c=='G' ){ szHeap *= 1000000000; break; } } if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000; -#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64); #endif #ifdef SQLITE_ENABLE_VFSTRACE }else if( strcmp(argv[i],"-vfstrace")==0 ){ extern int vfstrace_register( @@ -2731,10 +2767,15 @@ void *pOutArg, int makeDefault ); vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,stderr,1); #endif +#ifdef SQLITE_ENABLE_MULTIPLEX + }else if( strcmp(argv[i],"-multiplex")==0 ){ + extern int sqlite3_multiple_initialize(const char*,int); + sqlite3_multiplex_initialize(0, 1); +#endif }else if( strcmp(argv[i],"-vfs")==0 ){ sqlite3_vfs *pVfs = sqlite3_vfs_find(argv[++i]); if( pVfs ){ sqlite3_vfs_register(pVfs, 1); }else{ @@ -2753,11 +2794,11 @@ #ifndef SQLITE_OMIT_MEMORYDB data.zDbFilename = ":memory:"; #else data.zDbFilename = 0; #endif - /***** Begin Fossil Patch *****/ + /***** Begin Fossil Patch *****/ { extern void fossil_open(const char **); fossil_open(&data.zDbFilename); } /***** End Fossil Patch *****/ @@ -2855,12 +2896,18 @@ stdin_is_interactive = 0; }else if( strcmp(z,"-heap")==0 ){ i++; }else if( strcmp(z,"-vfs")==0 ){ i++; +#ifdef SQLITE_ENABLE_VFSTRACE }else if( strcmp(z,"-vfstrace")==0 ){ i++; +#endif +#ifdef SQLITE_ENABLE_MULTIPLEX + }else if( strcmp(z,"-multiplex")==0 ){ + i++; +#endif }else if( strcmp(z,"-help")==0 || strcmp(z, "--help")==0 ){ usage(1); }else{ fprintf(stderr,"%s: Error: unknown option: %s\n", Argv0, z); fprintf(stderr,"Use -help for a list of options.\n"); Index: src/skins.c ================================================================== --- src/skins.c +++ src/skins.c @@ -152,10 +152,54 @@ @ /* The label/value pairs on (for example) the vinfo page */ @ table.label-value th { @ vertical-align: top; @ text-align: right; @ padding: 0.2ex 2ex; +@ } +@ +@ /* Side-by-side diff */ +@ table.sbsdiff { +@ font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace; +@ font-size: 10pt; +@ border-collapse:collapse; +@ white-space: pre; +@ width: 98%; +@ border: 1px #000 dashed; +@ } +@ +@ table.sbsdiff th.diffhdr { +@ border-bottom: dotted; +@ border-width: 1px; +@ } +@ +@ table.sbsdiff tr td { +@ white-space: pre; +@ padding-left: 3px; +@ padding-right: 3px; +@ margin: 0px; +@ } +@ +@ table.sbsdiff tr td.lineno { +@ text-align: right; +@ } +@ +@ table.sbsdiff tr td.meta { +@ color: white; +@ background-color: rgb(20, 20, 20); +@ text-align: center; +@ } +@ +@ table.sbsdiff tr td.added { +@ background-color: rgb(230, 230, 230); +@ } +@ +@ table.sbsdiff tr td.removed { +@ background-color: rgb(200, 200, 200); +@ } +@ +@ table.sbsdiff tr td.changed { +@ background-color: rgb(220, 220, 220); @ }'); @ REPLACE INTO config(name,mtime,value) VALUES('header',now(),' @ @ $ : $ @ @ @ $ : $ @ @@ -590,10 +676,55 @@ @ /* The label/value pairs on (for example) the ci page */ @ table.label-value th { @ vertical-align: top; @ text-align: right; @ padding: 0.2ex 2ex; +@ } +@ +@ /* Side-by-side diff */ +@ table.sbsdiff { +@ font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace; +@ font-size: 10pt; +@ border-collapse:collapse; +@ white-space: pre; +@ width: 98%; +@ border: 1px #000 dashed; +@ } +@ +@ table.sbsdiff th.diffhdr { +@ border-bottom: dotted; +@ border-width: 1px; +@ } +@ +@ table.sbsdiff tr td { +@ white-space: pre; +@ padding-left: 3px; +@ padding-right: 3px; +@ margin: 0px; +@ } +@ +@ table.sbsdiff tr td.lineno { +@ text-align: right; +@ } +@ +@ table.sbsdiff tr td.meta { +@ color: white; +@ background-color: black; +@ text-align: center; +@ } +@ +@ table.sbsdiff tr td.added { +@ background-color: white; +@ } +@ +@ table.sbsdiff tr td.removed { +@ background-color: white; +@ text-decoration: line-through; +@ } +@ +@ table.sbsdiff tr td.changed { +@ background-color: white; @ }'); @ REPLACE INTO config(name,mtime,value) VALUES('header',now(),' @ @ $ : $ @ @ @ $ : $ @ 1 @@ -654,13 +654,13 @@ ** ** See also: [sqlite3_libversion()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.7.8" -#define SQLITE_VERSION_NUMBER 3007008 -#define SQLITE_SOURCE_ID "2011-09-19 14:49:19 3e0da808d2f5b4d12046e05980ca04578f581177" +#define SQLITE_VERSION "3.7.9" +#define SQLITE_VERSION_NUMBER 3007009 +#define SQLITE_SOURCE_ID "2011-10-15 00:16:30 39408702a989f907261c298bf0947f3e68bd10fe" /* ** CAPI3REF: Run-Time Library Version Numbers ** KEYWORDS: sqlite3_version, sqlite3_sourceid ** @@ -1318,11 +1318,15 @@ ** in order for the database to be readable. The fourth parameter to ** [sqlite3_file_control()] for this opcode should be a pointer to an integer. ** That integer is 0 to disable persistent WAL mode or 1 to enable persistent ** WAL mode. If the integer is -1, then it is overwritten with the current ** WAL persistence setting. -** +** +** ^The [SQLITE_FCNTL_OVERWRITE] opcode is invoked by SQLite after opening +** a write transaction to indicate that, unless it is rolled back for some +** reason, the entire database file will be overwritten by the current +** transaction. This is used by VACUUM operations. */ #define SQLITE_FCNTL_LOCKSTATE 1 #define SQLITE_GET_LOCKPROXYFILE 2 #define SQLITE_SET_LOCKPROXYFILE 3 #define SQLITE_LAST_ERRNO 4 @@ -1330,10 +1334,11 @@ #define SQLITE_FCNTL_CHUNK_SIZE 6 #define SQLITE_FCNTL_FILE_POINTER 7 #define SQLITE_FCNTL_SYNC_OMITTED 8 #define SQLITE_FCNTL_WIN32_AV_RETRY 9 #define SQLITE_FCNTL_PERSIST_WAL 10 +#define SQLITE_FCNTL_OVERWRITE 11 /* ** CAPI3REF: Mutex Handle ** ** The mutex module within SQLite defines [sqlite3_mutex] to be an @@ -3346,11 +3351,12 @@ ** zSql string ends at either the first '\000' or '\u0000' character or ** the nByte-th byte, whichever comes first. If the caller knows ** that the supplied string is nul-terminated, then there is a small ** performance advantage to be gained by passing an nByte parameter that ** is equal to the number of bytes in the input string including -** the nul-terminator bytes. +** the nul-terminator bytes as this saves SQLite from having to +** make a copy of the input string. ** ** ^If pzTail is not NULL then *pzTail is made to point to the first byte ** past the end of the first SQL statement in zSql. These routines only ** compile the first statement in zSql, so *pzTail is left pointing to ** what remains uncompiled. @@ -3397,11 +3403,11 @@ ** a schema change, on the first [sqlite3_step()] call following any change ** to the [sqlite3_bind_text | bindings] of that [parameter]. ** ^The specific value of WHERE-clause [parameter] might influence the ** choice of query plan if the parameter is the left-hand side of a [LIKE] ** or [GLOB] operator or if the parameter is compared to an indexed column -** and the [SQLITE_ENABLE_STAT2] compile-time option is enabled. +** and the [SQLITE_ENABLE_STAT3] compile-time option is enabled. ** the ** - This parameter returns the approximate number of of bytes of heap ** and lookaside memory used by all prepared statements associated with ** the database connection.)^ ** ^The highwater mark associated with SQLITE_DBSTATUS_STMT_USED is always 0. **
+** +** [[SQLITE_DBSTATUS_CACHE_HIT]] ^(- SQLITE_DBSTATUS_CACHE_HIT
+**- This parameter returns the number of pager cache hits that have +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_HIT +** is always 0. +**
+** +** [[SQLITE_DBSTATUS_CACHE_MISS]] ^(- SQLITE_DBSTATUS_CACHE_MISS
+**- This parameter returns the number of pager cache misses that have +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_MISS +** is always 0. +**
** */ #define SQLITE_DBSTATUS_LOOKASIDE_USED 0 #define SQLITE_DBSTATUS_CACHE_USED 1 #define SQLITE_DBSTATUS_SCHEMA_USED 2 #define SQLITE_DBSTATUS_STMT_USED 3 #define SQLITE_DBSTATUS_LOOKASIDE_HIT 4 #define SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5 #define SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6 -#define SQLITE_DBSTATUS_MAX 6 /* Largest defined DBSTATUS */ +#define SQLITE_DBSTATUS_CACHE_HIT 7 +#define SQLITE_DBSTATUS_CACHE_MISS 8 +#define SQLITE_DBSTATUS_MAX 8 /* Largest defined DBSTATUS */ /* ** CAPI3REF: Prepared Statement Status ** @@ -6425,11 +6463,10 @@ **- ^This is the number of rows inserted into transient indices that ** were created automatically in order to help joins run faster. ** A non-zero value in this counter may indicate an opportunity to ** improvement performance by adding permanent indices that do not ** need to be reinitialized each time the statement is run.
-** ** */ #define SQLITE_STMTSTATUS_FULLSCAN_STEP 1 #define SQLITE_STMTSTATUS_SORT 2 #define SQLITE_STMTSTATUS_AUTOINDEX 3 @@ -7711,10 +7748,22 @@ ** is 0x00000000ffffffff. But because of quirks of some compilers, we ** have to specify the value in the less intuitive manner shown: */ #define SQLITE_MAX_U32 ((((u64)1)<<32)-1) +/* +** The datatype used to store estimates of the number of rows in a +** table or index. This is an unsigned integer type. For 99.9% of +** the world, a 32-bit integer is sufficient. But a 64-bit integer +** can be used at compile-time if desired. +*/ +#ifdef SQLITE_64BIT_STATS + typedef u64 tRowcnt; /* 64-bit only if requested at compile-time */ +#else + typedef u32 tRowcnt; /* 32-bit is the default */ +#endif + /* ** Macros to determine whether the machine is big or little endian, ** evaluated at runtime. */ #ifdef SQLITE_AMALGAMATION @@ -8742,10 +8791,11 @@ SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager*); SQLITE_PRIVATE const char *sqlite3PagerJournalname(Pager*); SQLITE_PRIVATE int sqlite3PagerNosync(Pager*); SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager*); SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager*); +SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *, int, int, int *); /* Functions used to truncate the database file. */ SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager*,Pgno); #if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_WAL) @@ -9278,18 +9328,21 @@ /* ** If this is a no-op implementation, implement everything as macros. */ #define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8) #define sqlite3_mutex_free(X) -#define sqlite3_mutex_enter(X) +#define sqlite3_mutex_enter(X) #define sqlite3_mutex_try(X) SQLITE_OK -#define sqlite3_mutex_leave(X) +#define sqlite3_mutex_leave(X) #define sqlite3_mutex_held(X) ((void)(X),1) #define sqlite3_mutex_notheld(X) ((void)(X),1) #define sqlite3MutexAlloc(X) ((sqlite3_mutex*)8) #define sqlite3MutexInit() SQLITE_OK #define sqlite3MutexEnd() +#define MUTEX_LOGIC(X) +#else +#define MUTEX_LOGIC(X) X #endif /* defined(SQLITE_MUTEX_OMIT) */ /************** End of mutex.h ***********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ @@ -9918,11 +9971,11 @@ int iPKey; /* If not negative, use aCol[iPKey] as the primary key */ int nCol; /* Number of columns in this table */ Column *aCol; /* Information about each column */ Index *pIndex; /* List of SQL indexes on this table. */ int tnum; /* Root BTree node for this table (see note above) */ - unsigned nRowEst; /* Estimated rows in table - from sqlite_stat1 table */ + tRowcnt nRowEst; /* Estimated rows in table - from sqlite_stat1 table */ Select *pSelect; /* NULL for tables. Points to definition if a view. */ u16 nRef; /* Number of pointers to this Table */ u8 tabFlags; /* Mask of TF_* values */ u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */ FKey *pFKey; /* Linked list of all foreign keys in this table */ @@ -10117,11 +10170,11 @@ */ struct Index { char *zName; /* Name of this index */ int nColumn; /* Number of columns in the table used by this index */ int *aiColumn; /* Which columns are used by this index. 1st is 0 */ - unsigned *aiRowEst; /* Result of ANALYZE: Est. rows selected by each column */ + tRowcnt *aiRowEst; /* Result of ANALYZE: Est. rows selected by each column */ Table *pTable; /* The SQL table being indexed */ int tnum; /* Page containing root of this index in database file */ u8 onError; /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */ u8 autoIndex; /* True if is automatically created (ex: by UNIQUE) */ u8 bUnordered; /* Use this index for == or IN queries only */ @@ -10128,24 +10181,32 @@ char *zColAff; /* String defining the affinity of each column */ Index *pNext; /* The next index associated with the same table */ Schema *pSchema; /* Schema containing this index */ u8 *aSortOrder; /* Array of size Index.nColumn. True==DESC, False==ASC */ char **azColl; /* Array of collation sequence names for index */ - IndexSample *aSample; /* Array of SQLITE_INDEX_SAMPLES samples */ +#ifdef SQLITE_ENABLE_STAT3 + int nSample; /* Number of elements in aSample[] */ + tRowcnt avgEq; /* Average nEq value for key values not in aSample */ + IndexSample *aSample; /* Samples of the left-most key */ +#endif }; /* ** Each sample stored in the sqlite_stat2 table is represented in memory ** using a structure of this type. */ struct IndexSample { union { char *z; /* Value if eType is SQLITE_TEXT or SQLITE_BLOB */ - double r; /* Value if eType is SQLITE_FLOAT or SQLITE_INTEGER */ + double r; /* Value if eType is SQLITE_FLOAT */ + i64 i; /* Value if eType is SQLITE_INTEGER */ } u; u8 eType; /* SQLITE_NULL, SQLITE_INTEGER ... etc. */ - u8 nByte; /* Size in byte of text or blob. */ + int nByte; /* Size in byte of text or blob. */ + tRowcnt nEq; /* Est. number of rows where the key equals this sample */ + tRowcnt nLt; /* Est. number of rows where key is less than this sample */ + tRowcnt nDLt; /* Est. number of distinct keys less than this sample */ }; /* ** Each token coming out of the lexer is an instance of ** this structure. Tokens are also used as part of an expression. @@ -10593,14 +10654,14 @@ #define WHERE_ORDERBY_NORMAL 0x0000 /* No-op */ #define WHERE_ORDERBY_MIN 0x0001 /* ORDER BY processing for min() func */ #define WHERE_ORDERBY_MAX 0x0002 /* ORDER BY processing for max() func */ #define WHERE_ONEPASS_DESIRED 0x0004 /* Want to do one-pass UPDATE/DELETE */ #define WHERE_DUPLICATES_OK 0x0008 /* Ok to return a row more than once */ -#define WHERE_OMIT_OPEN 0x0010 /* Table cursors are already open */ -#define WHERE_OMIT_CLOSE 0x0020 /* Omit close of table & index cursors */ -#define WHERE_FORCE_TABLE 0x0040 /* Do not use an index-only search */ -#define WHERE_ONETABLE_ONLY 0x0080 /* Only code the 1st table in pTabList */ +#define WHERE_OMIT_OPEN_CLOSE 0x0010 /* Table cursors are already open */ +#define WHERE_FORCE_TABLE 0x0020 /* Do not use an index-only search */ +#define WHERE_ONETABLE_ONLY 0x0040 /* Only code the 1st table in pTabList */ +#define WHERE_AND_ONLY 0x0080 /* Don't use indices for OR terms */ /* ** The WHERE clause processing routine has two halves. The ** first part does the start of the WHERE loop and the second ** half does the tail of the WHERE loop. An instance of @@ -11350,10 +11411,11 @@ #else # define sqlite3ViewGetColumnNames(A,B) 0 #endif SQLITE_PRIVATE void sqlite3DropTable(Parse*, SrcList*, int, int); +SQLITE_PRIVATE void sqlite3CodeDropTable(Parse*, Table*, int, int); SQLITE_PRIVATE void sqlite3DeleteTable(sqlite3*, Table*); #ifndef SQLITE_OMIT_AUTOINCREMENT SQLITE_PRIVATE void sqlite3AutoincrementBegin(Parse *pParse); SQLITE_PRIVATE void sqlite3AutoincrementEnd(Parse *pParse); #else @@ -11606,11 +11668,11 @@ SQLITE_PRIVATE void sqlite3ValueSetStr(sqlite3_value*, int, const void *,u8, void(*)(void*)); SQLITE_PRIVATE void sqlite3ValueFree(sqlite3_value*); SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *); SQLITE_PRIVATE char *sqlite3Utf16to8(sqlite3 *, const void*, int, u8); -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 SQLITE_PRIVATE char *sqlite3Utf8to16(sqlite3 *, u8, char *, int, int *); #endif SQLITE_PRIVATE int sqlite3ValueFromExpr(sqlite3 *, Expr *, u8, u8, sqlite3_value **); SQLITE_PRIVATE void sqlite3ValueApplyAffinity(sqlite3_value *, u8, u8); #ifndef SQLITE_AMALGAMATION @@ -11708,19 +11770,21 @@ # define sqlite3VtabInSync(db) 0 # define sqlite3VtabLock(X) # define sqlite3VtabUnlock(X) # define sqlite3VtabUnlockList(X) # define sqlite3VtabSavepoint(X, Y, Z) SQLITE_OK +# define sqlite3GetVTable(X,Y) ((VTable*)0) #else SQLITE_PRIVATE void sqlite3VtabClear(sqlite3 *db, Table*); SQLITE_PRIVATE int sqlite3VtabSync(sqlite3 *db, char **); SQLITE_PRIVATE int sqlite3VtabRollback(sqlite3 *db); SQLITE_PRIVATE int sqlite3VtabCommit(sqlite3 *db); SQLITE_PRIVATE void sqlite3VtabLock(VTable *); SQLITE_PRIVATE void sqlite3VtabUnlock(VTable *); SQLITE_PRIVATE void sqlite3VtabUnlockList(sqlite3*); SQLITE_PRIVATE int sqlite3VtabSavepoint(sqlite3 *, int, int); +SQLITE_PRIVATE VTable *sqlite3GetVTable(sqlite3*, Table*); # define sqlite3VtabInSync(db) ((db)->nVTrans>0 && (db)->aVTrans==0) #endif SQLITE_PRIVATE void sqlite3VtabMakeWritable(Parse*,Table*); SQLITE_PRIVATE void sqlite3VtabBeginParse(Parse*, Token*, Token*, Token*); SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse*, Token*); @@ -11736,11 +11800,10 @@ SQLITE_PRIVATE int sqlite3TransferBindings(sqlite3_stmt *, sqlite3_stmt *); SQLITE_PRIVATE int sqlite3Reprepare(Vdbe*); SQLITE_PRIVATE void sqlite3ExprListCheckLength(Parse*, ExprList*, const char*); SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(Parse *, Expr *, Expr *); SQLITE_PRIVATE int sqlite3TempInMemory(const sqlite3*); -SQLITE_PRIVATE VTable *sqlite3GetVTable(sqlite3*, Table*); SQLITE_PRIVATE const char *sqlite3JournalModename(int); SQLITE_PRIVATE int sqlite3Checkpoint(sqlite3*, int, int, int*, int*); SQLITE_PRIVATE int sqlite3WalDefaultHook(void*,sqlite3*,const char*,int); /* Declarations for functions in fkey.c. All of these are replaced by @@ -12232,10 +12295,13 @@ #ifdef SQLITE_ENABLE_RTREE "ENABLE_RTREE", #endif #ifdef SQLITE_ENABLE_STAT2 "ENABLE_STAT2", +#endif +#ifdef SQLITE_ENABLE_STAT3 + "ENABLE_STAT3", #endif #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY "ENABLE_UNLOCK_NOTIFY", #endif #ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT @@ -12445,13 +12511,10 @@ "OMIT_WSD", #endif #ifdef SQLITE_OMIT_XFER_OPT "OMIT_XFER_OPT", #endif -#ifdef SQLITE_PAGECACHE_BLOCKALLOC - "PAGECACHE_BLOCKALLOC", -#endif #ifdef SQLITE_PERFORMANCE_TRACE "PERFORMANCE_TRACE", #endif #ifdef SQLITE_PROXY_DEBUG "PROXY_DEBUG", @@ -13189,10 +13252,32 @@ *pHighwater = 0; *pCurrent = nByte; break; } + + /* + ** Set *pCurrent to the total cache hits or misses encountered by all + ** pagers the database handle is connected to. *pHighwater is always set + ** to zero. + */ + case SQLITE_DBSTATUS_CACHE_HIT: + case SQLITE_DBSTATUS_CACHE_MISS: { + int i; + int nRet = 0; + assert( SQLITE_DBSTATUS_CACHE_MISS==SQLITE_DBSTATUS_CACHE_HIT+1 ); + + for(i=0; inDb; i++){ + if( db->aDb[i].pBt ){ + Pager *pPager = sqlite3BtreePager(db->aDb[i].pBt); + sqlite3PagerCacheStat(pPager, op, resetFlag, &nRet); + } + } + *pHighwater = 0; + *pCurrent = nRet; + break; + } default: { rc = SQLITE_ERROR; } } @@ -13490,16 +13575,22 @@ } return 0; } /* -** Set the time to the current time reported by the VFS +** Set the time to the current time reported by the VFS. +** +** Return the number of errors. */ -static void setDateTimeToCurrent(sqlite3_context *context, DateTime *p){ +static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){ sqlite3 *db = sqlite3_context_db_handle(context); - sqlite3OsCurrentTimeInt64(db->pVfs, &p->iJD); - p->validJD = 1; + if( sqlite3OsCurrentTimeInt64(db->pVfs, &p->iJD)==SQLITE_OK ){ + p->validJD = 1; + return 0; + }else{ + return 1; + } } /* ** Attempt to parse the given string into a Julian Day Number. Return ** the number of errors. @@ -13525,12 +13616,11 @@ if( parseYyyyMmDd(zDate,p)==0 ){ return 0; }else if( parseHhMmSs(zDate, p)==0 ){ return 0; }else if( sqlite3StrICmp(zDate,"now")==0){ - setDateTimeToCurrent(context, p); - return 0; + return setDateTimeToCurrent(context, p); }else if( sqlite3AtoF(zDate, &r, sqlite3Strlen30(zDate), SQLITE_UTF8) ){ p->iJD = (sqlite3_int64)(r*86400000.0 + 0.5); p->validJD = 1; return 0; } @@ -13953,12 +14043,13 @@ int i; const unsigned char *z; int eType; memset(p, 0, sizeof(*p)); if( argc==0 ){ - setDateTimeToCurrent(context, p); - }else if( (eType = sqlite3_value_type(argv[0]))==SQLITE_FLOAT + return setDateTimeToCurrent(context, p); + } + if( (eType = sqlite3_value_type(argv[0]))==SQLITE_FLOAT || eType==SQLITE_INTEGER ){ p->iJD = (sqlite3_int64)(sqlite3_value_double(argv[0])*86400000.0 + 0.5); p->validJD = 1; }else{ z = sqlite3_value_text(argv[0]); @@ -14266,35 +14357,32 @@ ){ time_t t; char *zFormat = (char *)sqlite3_user_data(context); sqlite3 *db; sqlite3_int64 iT; + struct tm *pTm; + struct tm sNow; char zBuf[20]; UNUSED_PARAMETER(argc); UNUSED_PARAMETER(argv); db = sqlite3_context_db_handle(context); - sqlite3OsCurrentTimeInt64(db->pVfs, &iT); + if( sqlite3OsCurrentTimeInt64(db->pVfs, &iT) ) return; t = iT/1000 - 10000*(sqlite3_int64)21086676; #ifdef HAVE_GMTIME_R - { - struct tm sNow; - gmtime_r(&t, &sNow); + pTm = gmtime_r(&t, &sNow); +#else + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); + pTm = gmtime(&t); + if( pTm ) memcpy(&sNow, pTm, sizeof(sNow)); + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); +#endif + if( pTm ){ strftime(zBuf, 20, zFormat, &sNow); - } -#else - { - struct tm *pTm; - sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); - pTm = gmtime(&t); - strftime(zBuf, 20, zFormat, pTm); - sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); - } -#endif - - sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); + sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); + } } #endif /* ** This function registered all of the above C functions as SQL @@ -14625,16 +14713,16 @@ ** Register a VFS with the system. It is harmless to register the same ** VFS multiple times. The new VFS becomes the default if makeDflt is ** true. */ SQLITE_API int sqlite3_vfs_register(sqlite3_vfs *pVfs, int makeDflt){ - sqlite3_mutex *mutex = 0; + MUTEX_LOGIC(sqlite3_mutex *mutex;) #ifndef SQLITE_OMIT_AUTOINIT int rc = sqlite3_initialize(); if( rc ) return rc; #endif - mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + MUTEX_LOGIC( mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) sqlite3_mutex_enter(mutex); vfsUnlink(pVfs); if( makeDflt || vfsList==0 ){ pVfs->pNext = vfsList; vfsList = pVfs; @@ -18878,52 +18966,14 @@ ** an historical reference. Most of the "enhancements" have been backed ** out so that the functionality is now the same as standard printf(). ** ************************************************************************** ** -** The following modules is an enhanced replacement for the "printf" subroutines -** found in the standard C library. The following enhancements are -** supported: -** -** + Additional functions. The standard set of "printf" functions -** includes printf, fprintf, sprintf, vprintf, vfprintf, and -** vsprintf. This module adds the following: -** -** * snprintf -- Works like sprintf, but has an extra argument -** which is the size of the buffer written to. -** -** * mprintf -- Similar to sprintf. Writes output to memory -** obtained from malloc. -** -** * xprintf -- Calls a function to dispose of output. -** -** * nprintf -- No output, but returns the number of characters -** that would have been output by printf. -** -** * A v- version (ex: vsnprintf) of every function is also -** supplied. -** -** + A few extensions to the formatting notation are supported: -** -** * The "=" flag (similar to "-") causes the output to be -** be centered in the appropriately sized field. -** -** * The %b field outputs an integer in binary notation. -** -** * The %c field now accepts a precision. The character output -** is repeated by the number of times the precision specifies. -** -** * The %' field works like %c, but takes as its character the -** next character of the format string, instead of the next -** argument. For example, printf("%.78'-") prints 78 minus -** signs, the same as printf("%.78c",'-'). -** -** + When compiled using GCC on a SPARC, this version of printf is -** faster than the library printf for SUN OS 4.1. -** -** + All functions are fully reentrant. -** +** This file contains code for a set of "printf"-like routines. These +** routines format strings much like the printf() from the standard C +** library, though the implementation here has enhancements to support +** SQLlite. */ /* ** Conversion types fall into various categories as defined by the ** following enumeration. @@ -19057,47 +19107,19 @@ } } /* ** On machines with a small stack size, you can redefine the -** SQLITE_PRINT_BUF_SIZE to be less than 350. +** SQLITE_PRINT_BUF_SIZE to be something smaller, if desired. */ #ifndef SQLITE_PRINT_BUF_SIZE -# if defined(SQLITE_SMALL_STACK) -# define SQLITE_PRINT_BUF_SIZE 50 -# else -# define SQLITE_PRINT_BUF_SIZE 350 -# endif +# define SQLITE_PRINT_BUF_SIZE 70 #endif #define etBUFSIZE SQLITE_PRINT_BUF_SIZE /* Size of the output buffer */ /* -** The root program. All variations call this core. -** -** INPUTS: -** func This is a pointer to a function taking three arguments -** 1. A pointer to anything. Same as the "arg" parameter. -** 2. A pointer to the list of characters to be output -** (Note, this list is NOT null terminated.) -** 3. An integer number of characters to be output. -** (Note: This number might be zero.) -** -** arg This is the pointer to anything which will be passed as the -** first argument to "func". Use it for whatever you like. -** -** fmt This is the format string, as in the usual print. -** -** ap This is a pointer to a list of arguments. Same as in -** vfprint. -** -** OUTPUTS: -** The return value is the total number of characters sent to -** the function "func". Returns -1 on a error. -** -** Note that the order in which automatic variables are declared below -** seems to make a big difference in determining how fast this beast -** will run. +** Render a string given by "fmt" into the StrAccum object. */ SQLITE_PRIVATE void sqlite3VXPrintf( StrAccum *pAccum, /* Accumulate results here */ int useExtended, /* Allow extended %-conversions */ const char *fmt, /* Format string */ @@ -19116,27 +19138,27 @@ etByte flag_altform2; /* True if "!" flag is present */ etByte flag_zeropad; /* True if field width constant starts with zero */ etByte flag_long; /* True if "l" flag is present */ etByte flag_longlong; /* True if the "ll" flag is present */ etByte done; /* Loop termination flag */ + etByte xtype = 0; /* Conversion paradigm */ + char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ sqlite_uint64 longvalue; /* Value for integer types */ LONGDOUBLE_TYPE realvalue; /* Value for real types */ const et_info *infop; /* Pointer to the appropriate info structure */ - char buf[etBUFSIZE]; /* Conversion buffer */ - char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ - etByte xtype = 0; /* Conversion paradigm */ - char *zExtra; /* Extra memory used for etTCLESCAPE conversions */ + char *zOut; /* Rendering buffer */ + int nOut; /* Size of the rendering buffer */ + char *zExtra; /* Malloced memory used by some conversion */ #ifndef SQLITE_OMIT_FLOATING_POINT int exp, e2; /* exponent of real numbers */ + int nsd; /* Number of significant digits returned */ double rounder; /* Used for rounding floating point values */ etByte flag_dp; /* True if decimal point should be shown */ etByte flag_rtz; /* True if trailing zeros should be removed */ - etByte flag_exp; /* True to force display of the exponent */ - int nsd; /* Number of significant digits returned */ #endif + char buf[etBUFSIZE]; /* Conversion buffer */ - length = 0; bufpt = 0; for(; (c=(*fmt))!=0; ++fmt){ if( c!='%' ){ int amt; bufpt = (char *)fmt; @@ -19177,13 +19199,10 @@ while( c>='0' && c<='9' ){ width = width*10 + c - '0'; c = *++fmt; } } - if( width > etBUFSIZE-10 ){ - width = etBUFSIZE-10; - } /* Get the precision */ if( c=='.' ){ precision = 0; c = *++fmt; if( c=='*' ){ @@ -19226,16 +19245,10 @@ break; } } zExtra = 0; - - /* Limit the precision to prevent overflowing buf[] during conversion */ - if( precision>etBUFSIZE-40 && (infop->flags & FLAG_STRING)==0 ){ - precision = etBUFSIZE-40; - } - /* ** At this point, variables are initialized as follows: ** ** flag_alternateform TRUE if a '#' is present. ** flag_altform2 TRUE if a '!' is present. @@ -19296,20 +19309,30 @@ } if( longvalue==0 ) flag_alternateform = 0; if( flag_zeropad && precision mallocFailed = 1; + return; + } + } + bufpt = &zOut[nOut-1]; if( xtype==etORDINAL ){ static const char zOrd[] = "thstndrd"; int x = (int)(longvalue % 10); if( x>=4 || (longvalue/10)%10==1 ){ x = 0; } - buf[etBUFSIZE-3] = zOrd[x*2]; - buf[etBUFSIZE-2] = zOrd[x*2+1]; - bufpt -= 2; + *(--bufpt) = zOrd[x*2+1]; + *(--bufpt) = zOrd[x*2]; } { register const char *cset; /* Use registers for speed */ register int base; cset = &aDigits[infop->charset]; @@ -19317,11 +19340,11 @@ do{ /* Convert to ascii */ *(--bufpt) = cset[longvalue%base]; longvalue = longvalue/base; }while( longvalue>0 ); } - length = (int)(&buf[etBUFSIZE-1]-bufpt); + length = (int)(&zOut[nOut-1]-bufpt); for(idx=precision-length; idx>0; idx--){ *(--bufpt) = '0'; /* Zero pad */ } if( prefix ) *(--bufpt) = prefix; /* Add sign */ if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */ @@ -19328,21 +19351,20 @@ const char *pre; char x; pre = &aPrefix[infop->prefix]; for(; (x=(*pre))!=0; pre++) *(--bufpt) = x; } - length = (int)(&buf[etBUFSIZE-1]-bufpt); + length = (int)(&zOut[nOut-1]-bufpt); break; case etFLOAT: case etEXP: case etGENERIC: realvalue = va_arg(ap,double); #ifdef SQLITE_OMIT_FLOATING_POINT length = 0; #else if( precision<0 ) precision = 6; /* Set default precision */ - if( precision>etBUFSIZE/2-10 ) precision = etBUFSIZE/2-10; if( realvalue<0.0 ){ realvalue = -realvalue; prefix = '-'; }else{ if( flag_plussign ) prefix = '+'; @@ -19386,11 +19408,10 @@ bufpt = buf; /* ** If the field type is etGENERIC, then convert to either etEXP ** or etFLOAT, as appropriate. */ - flag_exp = xtype==etEXP; if( xtype!=etFLOAT ){ realvalue += rounder; if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } } if( xtype==etGENERIC ){ @@ -19407,10 +19428,18 @@ if( xtype==etEXP ){ e2 = 0; }else{ e2 = exp; } + if( e2+precision+width > etBUFSIZE - 15 ){ + bufpt = zExtra = sqlite3Malloc( e2+precision+width+15 ); + if( bufpt==0 ){ + pAccum->mallocFailed = 1; + return; + } + } + zOut = bufpt; nsd = 0; flag_dp = (precision>0 ?1:0) | flag_alternateform | flag_altform2; /* The sign in front of the number */ if( prefix ){ *(bufpt++) = prefix; @@ -19438,21 +19467,21 @@ *(bufpt++) = et_getdigit(&realvalue,&nsd); } /* Remove trailing zeros and the "." if no digits follow the "." */ if( flag_rtz && flag_dp ){ while( bufpt[-1]=='0' ) *(--bufpt) = 0; - assert( bufpt>buf ); + assert( bufpt>zOut ); if( bufpt[-1]=='.' ){ if( flag_altform2 ){ *(bufpt++) = '0'; }else{ *(--bufpt) = 0; } } } /* Add the "eNNN" suffix */ - if( flag_exp || xtype==etEXP ){ + if( xtype==etEXP ){ *(bufpt++) = aDigits[infop->charset]; if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; }else{ *(bufpt++) = '+'; @@ -19467,12 +19496,12 @@ *bufpt = 0; /* The converted number is in buf[] and zero terminated. Output it. ** Note that the number is in the usual order, not reversed as with ** integer conversions. */ - length = (int)(bufpt-buf); - bufpt = buf; + length = (int)(bufpt-zOut); + bufpt = zOut; /* Special case: Add leading zeros if the flag_zeropad flag is ** set and we are not left justified */ if( flag_zeropad && !flag_leftjustify && length < width){ int i; @@ -19606,13 +19635,11 @@ nspace = width-length; if( nspace>0 ){ appendSpace(pAccum, nspace); } } - if( zExtra ){ - sqlite3_free(zExtra); - } + sqlite3_free(zExtra); }/* End for loop over the format string */ } /* End of function */ /* ** Append N bytes of text from z to the StrAccum object. @@ -19622,10 +19649,11 @@ if( p->tooBig | p->mallocFailed ){ testcase(p->tooBig); testcase(p->mallocFailed); return; } + assert( p->zText!=0 || p->nChar==0 ); if( N<0 ){ N = sqlite3Strlen30(z); } if( N==0 || NEVER(z==0) ){ return; @@ -19653,19 +19681,20 @@ zNew = sqlite3DbRealloc(p->db, zOld, p->nAlloc); }else{ zNew = sqlite3_realloc(zOld, p->nAlloc); } if( zNew ){ - if( zOld==0 ) memcpy(zNew, p->zText, p->nChar); + if( zOld==0 && p->nChar>0 ) memcpy(zNew, p->zText, p->nChar); p->zText = zNew; }else{ p->mallocFailed = 1; sqlite3StrAccumReset(p); return; } } } + assert( p->zText ); memcpy(&p->zText[p->nChar], z, N); p->nChar += N; } /* @@ -20511,11 +20540,11 @@ ** no longer required. ** ** If a malloc failure occurs, NULL is returned and the db.mallocFailed ** flag set. */ -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 SQLITE_PRIVATE char *sqlite3Utf8to16(sqlite3 *db, u8 enc, char *z, int n, int *pnOut){ Mem m; memset(&m, 0, sizeof(m)); m.db = db; sqlite3VdbeMemSetStr(&m, z, n, SQLITE_UTF8, SQLITE_STATIC); @@ -25102,11 +25131,11 @@ return sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); } #endif -#ifdef SQLITE_DEBUG +#if defined(SQLITE_TEST) && defined(SQLITE_DEBUG) /* ** Helper function for printing out trace information from debugging ** binaries. This returns the string represetation of the supplied ** integer lock-type. */ @@ -25937,18 +25966,18 @@ ** locking a random byte from a range, concurrent SHARED locks may exist ** even if the locking primitive used is always a write-lock. */ int rc = SQLITE_OK; unixFile *pFile = (unixFile*)id; - unixInodeInfo *pInode = pFile->pInode; + unixInodeInfo *pInode; struct flock lock; int tErrno = 0; assert( pFile ); OSTRACE(("LOCK %d %s was %s(%s,%d) pid=%d (unix)\n", pFile->h, azFileLock(eFileLock), azFileLock(pFile->eFileLock), - azFileLock(pInode->eFileLock), pInode->nShared , getpid())); + azFileLock(pFile->pInode->eFileLock), pFile->pInode->nShared , getpid())); /* If there is already a lock of this type or more restrictive on the ** unixFile, do nothing. Don't use the end_lock: exit path, as ** unixEnterMutex() hasn't been called yet. */ @@ -26148,11 +26177,10 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ unixFile *pFile = (unixFile*)id; unixInodeInfo *pInode; struct flock lock; int rc = SQLITE_OK; - int h; assert( pFile ); OSTRACE(("UNLOCK %d %d was %d(%d,%d) pid=%d (unix)\n", pFile->h, eFileLock, pFile->eFileLock, pFile->pInode->eFileLock, pFile->pInode->nShared, getpid())); @@ -26160,18 +26188,14 @@ assert( eFileLock<=SHARED_LOCK ); if( pFile->eFileLock<=eFileLock ){ return SQLITE_OK; } unixEnterMutex(); - h = pFile->h; pInode = pFile->pInode; assert( pInode->nShared!=0 ); if( pFile->eFileLock>SHARED_LOCK ){ assert( pInode->eFileLock==pFile->eFileLock ); - SimulateIOErrorBenign(1); - SimulateIOError( h=(-1) ) - SimulateIOErrorBenign(0); #ifndef NDEBUG /* When reducing a lock such that other processes can start ** reading the database file again, make sure that the ** transaction counter was updated if any part of the database @@ -26178,15 +26202,10 @@ ** file changed. If the transaction counter is not updated, ** other connections to the same file might not realize that ** the file has changed and hence might not know to flush their ** cache. The use of a stale cache can lead to database corruption. */ -#if 0 - assert( pFile->inNormalWrite==0 - || pFile->dbUpdate==0 - || pFile->transCntrChng==1 ); -#endif pFile->inNormalWrite = 0; #endif /* downgrading to a shared lock on NFS involves clearing the write lock ** before establishing the readlock - to avoid a race condition we downgrade @@ -26284,13 +26303,10 @@ pInode->nShared--; if( pInode->nShared==0 ){ lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = lock.l_len = 0L; - SimulateIOErrorBenign(1); - SimulateIOError( h=(-1) ) - SimulateIOErrorBenign(0); if( unixFileLock(pFile, &lock)==0 ){ pInode->eFileLock = NO_LOCK; }else{ rc = SQLITE_IOERR_UNLOCK; pFile->lastErrno = errno; @@ -28428,20 +28444,19 @@ rc = SQLITE_NOMEM; goto shm_open_err; } if( pInode->bProcessLock==0 ){ - pShmNode->h = robust_open(zShmFilename, O_RDWR|O_CREAT, - (sStat.st_mode & 0777)); + const char *zRO; + int openFlags = O_RDWR | O_CREAT; + zRO = sqlite3_uri_parameter(pDbFd->zPath, "readonly_shm"); + if( zRO && sqlite3GetBoolean(zRO) ){ + openFlags = O_RDONLY; + pShmNode->isReadonly = 1; + } + pShmNode->h = robust_open(zShmFilename, openFlags, (sStat.st_mode&0777)); if( pShmNode->h<0 ){ - const char *zRO; - zRO = sqlite3_uri_parameter(pDbFd->zPath, "readonly_shm"); - if( zRO && sqlite3GetBoolean(zRO) ){ - pShmNode->h = robust_open(zShmFilename, O_RDONLY, - (sStat.st_mode & 0777)); - pShmNode->isReadonly = 1; - } if( pShmNode->h<0 ){ rc = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zShmFilename); goto shm_open_err; } } @@ -29122,10 +29137,13 @@ assert( zFilename==0 || zFilename[0]=='/' || pVfs->pAppData==(void*)&autolockIoFinder ); #else assert( zFilename==0 || zFilename[0]=='/' ); #endif + + /* No locking occurs in temporary files */ + assert( zFilename!=0 || noLock ); OSTRACE(("OPEN %-3d %s\n", h, zFilename)); pNew->h = h; pNew->zPath = zFilename; if( memcmp(pVfs->zName,"unix-excl",10)==0 ){ @@ -29224,10 +29242,11 @@ /* Dotfile locking uses the file path so it needs to be included in ** the dotlockLockingContext */ char *zLockFile; int nFilename; + assert( zFilename!=0 ); nFilename = (int)strlen(zFilename) + 6; zLockFile = (char *)sqlite3_malloc(nFilename); if( zLockFile==0 ){ rc = SQLITE_NOMEM; }else{ @@ -29462,12 +29481,20 @@ ** ** where NN is a 4 digit decimal number. The NN naming schemes are ** used by the test_multiplex.c module. */ nDb = sqlite3Strlen30(zPath) - 1; - while( nDb>0 && zPath[nDb]!='-' ) nDb--; - if( nDb==0 ) return SQLITE_OK; +#ifdef SQLITE_ENABLE_8_3_NAMES + while( nDb>0 && zPath[nDb]!='-' && zPath[nDb]!='/' ) nDb--; + if( nDb==0 || zPath[nDb]=='/' ) return SQLITE_OK; +#else + while( zPath[nDb]!='-' ){ + assert( nDb>0 ); + assert( zPath[nDb]!='\n' ); + nDb--; + } +#endif memcpy(zDb, zPath, nDb); zDb[nDb] = '\0'; if( 0==osStat(zDb, &sStat) ){ *pMode = sStat.st_mode & 0777; @@ -29995,14 +30022,16 @@ ** the current time and date as a Julian Day number times 86_400_000. In ** other words, write into *piNow the number of milliseconds since the Julian ** epoch of noon in Greenwich on November 24, 4714 B.C according to the ** proleptic Gregorian calendar. ** -** On success, return 0. Return 1 if the time and date cannot be found. +** On success, return SQLITE_OK. Return SQLITE_ERROR if the time and date +** cannot be found. */ static int unixCurrentTimeInt64(sqlite3_vfs *NotUsed, sqlite3_int64 *piNow){ static const sqlite3_int64 unixEpoch = 24405875*(sqlite3_int64)8640000; + int rc = SQLITE_OK; #if defined(NO_GETTOD) time_t t; time(&t); *piNow = ((sqlite3_int64)t)*1000 + unixEpoch; #elif OS_VXWORKS @@ -30009,34 +30038,38 @@ struct timespec sNow; clock_gettime(CLOCK_REALTIME, &sNow); *piNow = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_nsec/1000000; #else struct timeval sNow; - gettimeofday(&sNow, 0); - *piNow = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_usec/1000; + if( gettimeofday(&sNow, 0)==0 ){ + *piNow = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_usec/1000; + }else{ + rc = SQLITE_ERROR; + } #endif #ifdef SQLITE_TEST if( sqlite3_current_time ){ *piNow = 1000*(sqlite3_int64)sqlite3_current_time + unixEpoch; } #endif UNUSED_PARAMETER(NotUsed); - return 0; + return rc; } /* ** Find the current time (in Universal Coordinated Time). Write the ** current time and date as a Julian Day number into *prNow and ** return 0. Return 1 if the time and date cannot be found. */ static int unixCurrentTime(sqlite3_vfs *NotUsed, double *prNow){ - sqlite3_int64 i; + sqlite3_int64 i = 0; + int rc; UNUSED_PARAMETER(NotUsed); - unixCurrentTimeInt64(0, &i); + rc = unixCurrentTimeInt64(0, &i); *prNow = i/86400000.0; - return 0; + return rc; } /* ** We added the xGetLastError() method with the intention of providing ** better low-level error messages when operating-system problems come up @@ -34169,11 +34202,11 @@ if( h==INVALID_HANDLE_VALUE ){ pFile->lastErrno = GetLastError(); winLogError(SQLITE_CANTOPEN, "winOpen", zUtf8Name); free(zConverted); - if( isReadWrite ){ + if( isReadWrite && !isExclusive ){ return winOpen(pVfs, zName, id, ((flags|SQLITE_OPEN_READONLY)&~(SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE)), pOutFlags); }else{ return SQLITE_CANTOPEN_BKPT; } @@ -34535,11 +34568,11 @@ } static void winDlError(sqlite3_vfs *pVfs, int nBuf, char *zBufOut){ UNUSED_PARAMETER(pVfs); getLastErrorMsg(nBuf, zBufOut); } -void (*winDlSym(sqlite3_vfs *pVfs, void *pHandle, const char *zSymbol))(void){ +static void (*winDlSym(sqlite3_vfs *pVfs, void *pHandle, const char *zSymbol))(void){ UNUSED_PARAMETER(pVfs); #if SQLITE_OS_WINCE /* The GetProcAddressA() routine is only available on wince. */ return (void(*)(void))GetProcAddressA((HANDLE)pHandle, zSymbol); #else @@ -34546,11 +34579,11 @@ /* All other windows platforms expect GetProcAddress() to take ** an Ansi string regardless of the _UNICODE setting */ return (void(*)(void))GetProcAddress((HANDLE)pHandle, zSymbol); #endif } -void winDlClose(sqlite3_vfs *pVfs, void *pHandle){ +static void winDlClose(sqlite3_vfs *pVfs, void *pHandle){ UNUSED_PARAMETER(pVfs); FreeLibrary((HANDLE)pHandle); } #else /* if SQLITE_OMIT_LOAD_EXTENSION is defined: */ #define winDlOpen 0 @@ -34620,11 +34653,12 @@ ** the current time and date as a Julian Day number times 86_400_000. In ** other words, write into *piNow the number of milliseconds since the Julian ** epoch of noon in Greenwich on November 24, 4714 B.C according to the ** proleptic Gregorian calendar. ** -** On success, return 0. Return 1 if the time and date cannot be found. +** On success, return SQLITE_OK. Return SQLITE_ERROR if the time and date +** cannot be found. */ static int winCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *piNow){ /* FILETIME structure is a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (= JD 2305813.5). */ @@ -34640,11 +34674,11 @@ #if SQLITE_OS_WINCE SYSTEMTIME time; GetSystemTime(&time); /* if SystemTimeToFileTime() fails, it returns zero. */ if (!SystemTimeToFileTime(&time,&ft)){ - return 1; + return SQLITE_ERROR; } #else GetSystemTimeAsFileTime( &ft ); #endif @@ -34656,19 +34690,19 @@ if( sqlite3_current_time ){ *piNow = 1000*(sqlite3_int64)sqlite3_current_time + unixEpoch; } #endif UNUSED_PARAMETER(pVfs); - return 0; + return SQLITE_OK; } /* ** Find the current time (in Universal Coordinated Time). Write the ** current time and date as a Julian Day number into *prNow and ** return 0. Return 1 if the time and date cannot be found. */ -int winCurrentTime(sqlite3_vfs *pVfs, double *prNow){ +static int winCurrentTime(sqlite3_vfs *pVfs, double *prNow){ int rc; sqlite3_int64 i; rc = winCurrentTimeInt64(pVfs, &i); if( !rc ){ *prNow = i/86400000.0; @@ -35789,12 +35823,10 @@ typedef struct PCache1 PCache1; typedef struct PgHdr1 PgHdr1; typedef struct PgFreeslot PgFreeslot; typedef struct PGroup PGroup; -typedef struct PGroupBlock PGroupBlock; -typedef struct PGroupBlockList PGroupBlockList; /* Each page cache (or PCache) belongs to a PGroup. A PGroup is a set ** of one or more PCaches that are able to recycle each others unpinned ** pages when they are under memory pressure. A PGroup is an instance of ** the following object. @@ -35821,69 +35853,11 @@ int nMaxPage; /* Sum of nMax for purgeable caches */ int nMinPage; /* Sum of nMin for purgeable caches */ int mxPinned; /* nMaxpage + 10 - nMinPage */ int nCurrentPage; /* Number of purgeable pages allocated */ PgHdr1 *pLruHead, *pLruTail; /* LRU list of unpinned pages */ -#ifdef SQLITE_PAGECACHE_BLOCKALLOC - int isBusy; /* Do not run ReleaseMemory() if true */ - PGroupBlockList *pBlockList; /* List of block-lists for this group */ -#endif -}; - -/* -** If SQLITE_PAGECACHE_BLOCKALLOC is defined when the library is built, -** each PGroup structure has a linked list of the the following starting -** at PGroup.pBlockList. There is one entry for each distinct page-size -** currently used by members of the PGroup (i.e. 1024 bytes, 4096 bytes -** etc.). Variable PGroupBlockList.nByte is set to the actual allocation -** size requested by each pcache, which is the database page-size plus -** the various header structures used by the pcache, pager and btree layers. -** Usually around (pgsz+200) bytes. -** -** This size (pgsz+200) bytes is not allocated efficiently by some -** implementations of malloc. In particular, some implementations are only -** able to allocate blocks of memory chunks of 2^N bytes, where N is some -** integer value. Since the page-size is a power of 2, this means we -** end up wasting (pgsz-200) bytes in each allocation. -** -** If SQLITE_PAGECACHE_BLOCKALLOC is defined, the (pgsz+200) byte blocks -** are not allocated directly. Instead, blocks of roughly M*(pgsz+200) bytes -** are requested from malloc allocator. After a block is returned, -** sqlite3MallocSize() is used to determine how many (pgsz+200) byte -** allocations can fit in the space returned by malloc(). This value may -** be more than M. -** -** The blocks are stored in a doubly-linked list. Variable PGroupBlock.nEntry -** contains the number of allocations that will fit in the aData[] space. -** nEntry is limited to the number of bits in bitmask mUsed. If a slot -** within aData is in use, the corresponding bit in mUsed is set. Thus -** when (mUsed+1==(1 << nEntry)) the block is completely full. -** -** Each time a slot within a block is freed, the block is moved to the start -** of the linked-list. And if a block becomes completely full, then it is -** moved to the end of the list. As a result, when searching for a free -** slot, only the first block in the list need be examined. If it is full, -** then it is guaranteed that all blocks are full. -*/ -struct PGroupBlockList { - int nByte; /* Size of each allocation in bytes */ - PGroupBlock *pFirst; /* First PGroupBlock in list */ - PGroupBlock *pLast; /* Last PGroupBlock in list */ - PGroupBlockList *pNext; /* Next block-list attached to group */ -}; - -struct PGroupBlock { - Bitmask mUsed; /* Mask of used slots */ - int nEntry; /* Maximum number of allocations in aData[] */ - u8 *aData; /* Pointer to data block */ - PGroupBlock *pNext; /* Next PGroupBlock in list */ - PGroupBlock *pPrev; /* Previous PGroupBlock in list */ - PGroupBlockList *pList; /* Owner list */ -}; - -/* Minimum value for PGroupBlock.nEntry */ -#define PAGECACHE_BLOCKALLOC_MINENTRY 15 +}; /* Each page cache is an instance of the following object. Every ** open database file (including each in-memory database and each ** temporary or transient database) has a single page cache which ** is an instance of this object. @@ -35983,21 +35957,10 @@ ** ** assert( PGHDR1_TO_PAGE(PAGE_TO_PGHDR1(pCache, X))==X ); */ #define PGHDR1_TO_PAGE(p) (void*)(((char*)p) - p->pCache->szPage) #define PAGE_TO_PGHDR1(c, p) (PgHdr1*)(((char*)p) + c->szPage) - -/* -** Blocks used by the SQLITE_PAGECACHE_BLOCKALLOC blocks to store/retrieve -** a PGroupBlock pointer based on a pointer to a page buffer. -*/ -#define PAGE_SET_BLOCKPTR(pCache, pPg, pBlock) \ - ( *(PGroupBlock **)&(((u8*)pPg)[sizeof(PgHdr1) + pCache->szPage]) = pBlock ) - -#define PAGE_GET_BLOCKPTR(pCache, pPg) \ - ( *(PGroupBlock **)&(((u8*)pPg)[sizeof(PgHdr1) + pCache->szPage]) ) - /* ** Macros to enter and leave the PCache LRU mutex. */ #define pcache1EnterMutex(X) sqlite3_mutex_enter((X)->mutex) @@ -36120,159 +36083,32 @@ return iSize; } } #endif /* SQLITE_ENABLE_MEMORY_MANAGEMENT */ -#ifdef SQLITE_PAGECACHE_BLOCKALLOC -/* -** The block pBlock belongs to list pList but is not currently linked in. -** Insert it into the start of the list. -*/ -static void addBlockToList(PGroupBlockList *pList, PGroupBlock *pBlock){ - pBlock->pPrev = 0; - pBlock->pNext = pList->pFirst; - pList->pFirst = pBlock; - if( pBlock->pNext ){ - pBlock->pNext->pPrev = pBlock; - }else{ - assert( pList->pLast==0 ); - pList->pLast = pBlock; - } -} - -/* -** If there are no blocks in the list headed by pList, remove pList -** from the pGroup->pBlockList list and free it with sqlite3_free(). -*/ -static void freeListIfEmpty(PGroup *pGroup, PGroupBlockList *pList){ - assert( sqlite3_mutex_held(pGroup->mutex) ); - if( pList->pFirst==0 ){ - PGroupBlockList **pp; - for(pp=&pGroup->pBlockList; *pp!=pList; pp=&(*pp)->pNext); - *pp = (*pp)->pNext; - sqlite3_free(pList); - } -} -#endif /* SQLITE_PAGECACHE_BLOCKALLOC */ - /* ** Allocate a new page object initially associated with cache pCache. */ static PgHdr1 *pcache1AllocPage(PCache1 *pCache){ int nByte = sizeof(PgHdr1) + pCache->szPage; - void *pPg = 0; - PgHdr1 *p; - -#ifdef SQLITE_PAGECACHE_BLOCKALLOC - PGroup *pGroup = pCache->pGroup; - PGroupBlockList *pList; - PGroupBlock *pBlock; - int i; - - nByte += sizeof(PGroupBlockList *); - nByte = ROUND8(nByte); - - for(pList=pGroup->pBlockList; pList; pList=pList->pNext){ - if( pList->nByte==nByte ) break; - } - if( pList==0 ){ - PGroupBlockList *pNew; - assert( pGroup->isBusy==0 ); - assert( sqlite3_mutex_held(pGroup->mutex) ); - pGroup->isBusy = 1; /* Disable sqlite3PcacheReleaseMemory() */ - pNew = (PGroupBlockList *)sqlite3MallocZero(sizeof(PGroupBlockList)); - pGroup->isBusy = 0; /* Reenable sqlite3PcacheReleaseMemory() */ - if( pNew==0 ){ - /* malloc() failure. Return early. */ - return 0; - } -#ifdef SQLITE_DEBUG - for(pList=pGroup->pBlockList; pList; pList=pList->pNext){ - assert( pList->nByte!=nByte ); - } -#endif - pNew->nByte = nByte; - pNew->pNext = pGroup->pBlockList; - pGroup->pBlockList = pNew; - pList = pNew; - } - - pBlock = pList->pFirst; - if( pBlock==0 || pBlock->mUsed==(((Bitmask)1< nEntry)-1) ){ - int sz; - - /* Allocate a new block. Try to allocate enough space for the PGroupBlock - ** structure and MINENTRY allocations of nByte bytes each. If the - ** allocator returns more memory than requested, then more than MINENTRY - ** allocations may fit in it. */ - assert( sqlite3_mutex_held(pGroup->mutex) ); - pcache1LeaveMutex(pCache->pGroup); - sz = sizeof(PGroupBlock) + PAGECACHE_BLOCKALLOC_MINENTRY * nByte; - pBlock = (PGroupBlock *)sqlite3Malloc(sz); - pcache1EnterMutex(pCache->pGroup); - - if( !pBlock ){ - freeListIfEmpty(pGroup, pList); - return 0; - } - pBlock->nEntry = (sqlite3MallocSize(pBlock) - sizeof(PGroupBlock)) / nByte; - if( pBlock->nEntry>=BMS ){ - pBlock->nEntry = BMS-1; - } - pBlock->pList = pList; - pBlock->mUsed = 0; - pBlock->aData = (u8 *)&pBlock[1]; - addBlockToList(pList, pBlock); - - sz = sqlite3MallocSize(pBlock); - sqlite3_mutex_enter(pcache1.mutex); - sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, sz); - sqlite3_mutex_leave(pcache1.mutex); - } - - for(i=0; pPg==0 && ALWAYS(i nEntry); i++){ - if( 0==(pBlock->mUsed & ((Bitmask)1<mUsed |= ((Bitmask)1<aData[pList->nByte * i]; - } - } - assert( pPg ); - PAGE_SET_BLOCKPTR(pCache, pPg, pBlock); - - /* If the block is now full, shift it to the end of the list */ - if( pBlock->mUsed==(((Bitmask)1< nEntry)-1) && pList->pLast!=pBlock ){ - assert( pList->pFirst==pBlock ); - assert( pBlock->pPrev==0 ); - assert( pList->pLast->pNext==0 ); - pList->pFirst = pBlock->pNext; - pList->pFirst->pPrev = 0; - pBlock->pPrev = pList->pLast; - pBlock->pNext = 0; - pList->pLast->pNext = pBlock; - pList->pLast = pBlock; - } - p = PAGE_TO_PGHDR1(pCache, pPg); - if( pCache->bPurgeable ){ - pCache->pGroup->nCurrentPage++; - } -#else + PgHdr1 *p = 0; + void *pPg; + /* The group mutex must be released before pcache1Alloc() is called. This ** is because it may call sqlite3_release_memory(), which assumes that ** this mutex is not held. */ assert( sqlite3_mutex_held(pCache->pGroup->mutex) ); pcache1LeaveMutex(pCache->pGroup); pPg = pcache1Alloc(nByte); pcache1EnterMutex(pCache->pGroup); + if( pPg ){ p = PAGE_TO_PGHDR1(pCache, pPg); if( pCache->bPurgeable ){ pCache->pGroup->nCurrentPage++; } - }else{ - p = 0; } -#endif return p; } /* ** Free a page object allocated by pcache1AllocPage(). @@ -36282,53 +36118,12 @@ ** with a NULL pointer, so we mark the NULL test with ALWAYS(). */ static void pcache1FreePage(PgHdr1 *p){ if( ALWAYS(p) ){ PCache1 *pCache = p->pCache; - void *pPg = PGHDR1_TO_PAGE(p); - -#ifdef SQLITE_PAGECACHE_BLOCKALLOC - PGroupBlock *pBlock = PAGE_GET_BLOCKPTR(pCache, pPg); - PGroupBlockList *pList = pBlock->pList; - int i = ((u8 *)pPg - pBlock->aData) / pList->nByte; - - assert( pPg==(void *)&pBlock->aData[i*pList->nByte] ); - assert( pBlock->mUsed & ((Bitmask)1<mUsed &= ~((Bitmask)1<pFirst==pBlock ){ - pList->pFirst = pBlock->pNext; - if( pList->pFirst ) pList->pFirst->pPrev = 0; - }else{ - pBlock->pPrev->pNext = pBlock->pNext; - } - if( pList->pLast==pBlock ){ - pList->pLast = pBlock->pPrev; - if( pList->pLast ) pList->pLast->pNext = 0; - }else{ - pBlock->pNext->pPrev = pBlock->pPrev; - } - - if( pBlock->mUsed==0 ){ - PGroup *pGroup = p->pCache->pGroup; - - int sz = sqlite3MallocSize(pBlock); - sqlite3_mutex_enter(pcache1.mutex); - sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, -sz); - sqlite3_mutex_leave(pcache1.mutex); - freeListIfEmpty(pGroup, pList); - sqlite3_free(pBlock); - }else{ - addBlockToList(pList, pBlock); - } -#else assert( sqlite3_mutex_held(p->pCache->pGroup->mutex) ); - pcache1Free(pPg); -#endif + pcache1Free(PGHDR1_TO_PAGE(p)); if( pCache->bPurgeable ){ pCache->pGroup->nCurrentPage--; } } } @@ -36935,13 +36730,10 @@ ** been released, the function returns. The return value is the total number ** of bytes of memory released. */ SQLITE_PRIVATE int sqlite3PcacheReleaseMemory(int nReq){ int nFree = 0; -#ifdef SQLITE_PAGECACHE_BLOCKALLOC - if( pcache1.grp.isBusy ) return 0; -#endif assert( sqlite3_mutex_notheld(pcache1.grp.mutex) ); assert( sqlite3_mutex_notheld(pcache1.mutex) ); if( pcache1.pStart==0 ){ PgHdr1 *p; pcache1EnterMutex(&pcache1.grp); @@ -38200,12 +37992,12 @@ i64 journalSizeLimit; /* Size limit for persistent journal files */ char *zFilename; /* Name of the database file */ char *zJournal; /* Name of the journal file */ int (*xBusyHandler)(void*); /* Function to call when busy */ void *pBusyHandlerArg; /* Context argument for xBusyHandler */ + int nHit, nMiss; /* Total cache hits and misses */ #ifdef SQLITE_TEST - int nHit, nMiss; /* Cache hits and missing */ int nRead, nWrite; /* Database pages read/written */ #endif void (*xReiniter)(DbPage*); /* Call this routine when reloading pages */ #ifdef SQLITE_HAS_CODEC void *(*xCodec)(void*,void*,Pgno,int); /* Routine for en/decoding data */ @@ -40233,11 +40025,10 @@ needPagerReset = 0; } rc = pager_playback_one_page(pPager,&pPager->journalOff,0,1,0); if( rc!=SQLITE_OK ){ if( rc==SQLITE_DONE ){ - rc = SQLITE_OK; pPager->journalOff = szJ; break; }else if( rc==SQLITE_IOERR_SHORT_READ ){ /* If the journal has been truncated, simply stop reading and ** processing the journal. This might happen if the journal was @@ -40495,10 +40286,11 @@ #if defined(SQLITE_DEBUG) || defined(SQLITE_CHECK_PAGES) PgHdr *p; /* For looping over pages */ #endif assert( pPager->pWal ); + assert( pList ); #ifdef SQLITE_DEBUG /* Verify that the page list is in accending order */ for(p=pList; p && p->pDirty; p=p->pDirty){ assert( p->pgno < p->pDirty->pgno ); } @@ -41699,11 +41491,11 @@ ** The doNotSpill flag inhibits all cache spilling regardless of whether ** or not a sync is required. This is set during a rollback. ** ** Spilling is also prohibited when in an error state since that could ** lead to database corruption. In the current implementaton it - ** is impossible for sqlite3PCacheFetch() to be called with createFlag==1 + ** is impossible for sqlite3PcacheFetch() to be called with createFlag==1 ** while in the error state, hence it is impossible for this routine to ** be called in the error state. Nevertheless, we include a NEVER() ** test for the error state as a safeguard against future changes. */ if( NEVER(pPager->errCode) ) return SQLITE_OK; @@ -42535,18 +42327,17 @@ if( (*ppPage)->pPager && !noContent ){ /* In this case the pcache already contains an initialized copy of ** the page. Return without further ado. */ assert( pgno<=PAGER_MAX_PGNO && pgno!=PAGER_MJ_PGNO(pPager) ); - PAGER_INCR(pPager->nHit); + pPager->nHit++; return SQLITE_OK; }else{ /* The pager cache has created a new page. Its content needs to ** be initialized. */ - PAGER_INCR(pPager->nMiss); pPg = *ppPage; pPg->pPager = pPager; /* The maximum page number is 2^31. Return SQLITE_CORRUPT if a page ** number greater than this, or the unused locking-page, is requested. */ @@ -42578,10 +42369,11 @@ } memset(pPg->pData, 0, pPager->pageSize); IOTRACE(("ZERO %p %d\n", pPager, pgno)); }else{ assert( pPg->pPager==pPager ); + pPager->nMiss++; rc = readDbPage(pPg); if( rc!=SQLITE_OK ){ goto pager_acquire_err; } } @@ -43611,10 +43403,35 @@ a[9] = pPager->nRead; a[10] = pPager->nWrite; return a; } #endif + +/* +** Parameter eStat must be either SQLITE_DBSTATUS_CACHE_HIT or +** SQLITE_DBSTATUS_CACHE_MISS. Before returning, *pnVal is incremented by the +** current cache hit or miss count, according to the value of eStat. If the +** reset parameter is non-zero, the cache hit or miss count is zeroed before +** returning. +*/ +SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *pPager, int eStat, int reset, int *pnVal){ + int *piStat; + + assert( eStat==SQLITE_DBSTATUS_CACHE_HIT + || eStat==SQLITE_DBSTATUS_CACHE_MISS + ); + if( eStat==SQLITE_DBSTATUS_CACHE_HIT ){ + piStat = &pPager->nHit; + }else{ + piStat = &pPager->nMiss; + } + + *pnVal += *piStat; + if( reset ){ + *piStat = 0; + } +} /* ** Return true if this is an in-memory pager. */ SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager *pPager){ @@ -46706,11 +46523,11 @@ */ if( iRead ){ int sz; i64 iOffset; sz = pWal->hdr.szPage; - sz = (pWal->hdr.szPage&0xfe00) + ((pWal->hdr.szPage&0x0001)<<16); + sz = (sz&0xfe00) + ((sz&0x0001)<<16); testcase( sz<=32768 ); testcase( sz>=65536 ); iOffset = walFrameOffset(iRead, sz) + WAL_FRAME_HDRSIZE; *pInWal = 1; /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL */ @@ -50019,21 +49836,23 @@ */ if( isMemdb==0 && isTempDb==0 ){ if( vfsFlags & SQLITE_OPEN_SHAREDCACHE ){ int nFullPathname = pVfs->mxPathname+1; char *zFullPathname = sqlite3Malloc(nFullPathname); - sqlite3_mutex *mutexShared; + MUTEX_LOGIC( sqlite3_mutex *mutexShared; ) p->sharable = 1; if( !zFullPathname ){ sqlite3_free(p); return SQLITE_NOMEM; } sqlite3OsFullPathname(pVfs, zFilename, nFullPathname, zFullPathname); +#if SQLITE_THREADSAFE mutexOpen = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_OPEN); sqlite3_mutex_enter(mutexOpen); mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); sqlite3_mutex_enter(mutexShared); +#endif for(pBt=GLOBAL(BtShared*,sqlite3SharedCacheList); pBt; pBt=pBt->pNext){ assert( pBt->nRef>0 ); if( 0==strcmp(zFullPathname, sqlite3PagerFilename(pBt->pPager)) && sqlite3PagerVfs(pBt->pPager)==pVfs ){ int iDb; @@ -50135,13 +49954,13 @@ #if !defined(SQLITE_OMIT_SHARED_CACHE) && !defined(SQLITE_OMIT_DISKIO) /* Add the new BtShared object to the linked list sharable BtShareds. */ if( p->sharable ){ - sqlite3_mutex *mutexShared; + MUTEX_LOGIC( sqlite3_mutex *mutexShared; ) pBt->nRef = 1; - mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + MUTEX_LOGIC( mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);) if( SQLITE_THREADSAFE && sqlite3GlobalConfig.bCoreMutex ){ pBt->mutex = sqlite3MutexAlloc(SQLITE_MUTEX_FAST); if( pBt->mutex==0 ){ rc = SQLITE_NOMEM; db->mallocFailed = 0; @@ -50219,16 +50038,16 @@ ** true if the BtShared.nRef counter reaches zero and return ** false if it is still positive. */ static int removeFromSharingList(BtShared *pBt){ #ifndef SQLITE_OMIT_SHARED_CACHE - sqlite3_mutex *pMaster; + MUTEX_LOGIC( sqlite3_mutex *pMaster; ) BtShared *pList; int removed = 0; assert( sqlite3_mutex_notheld(pBt->mutex) ); - pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + MUTEX_LOGIC( pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) sqlite3_mutex_enter(pMaster); pBt->nRef--; if( pBt->nRef<=0 ){ if( GLOBAL(BtShared*,sqlite3SharedCacheList)==pBt ){ GLOBAL(BtShared*,sqlite3SharedCacheList) = pBt->pNext; @@ -52191,25 +52010,59 @@ offset -= ovflSize; }else{ /* Need to read this page properly. It contains some of the ** range of data that is being read (eOp==0) or written (eOp!=0). */ - DbPage *pDbPage; +#ifdef SQLITE_DIRECT_OVERFLOW_READ + sqlite3_file *fd; +#endif int a = amt; - rc = sqlite3PagerGet(pBt->pPager, nextPage, &pDbPage); - if( rc==SQLITE_OK ){ - aPayload = sqlite3PagerGetData(pDbPage); - nextPage = get4byte(aPayload); - if( a + offset > ovflSize ){ - a = ovflSize - offset; - } - rc = copyPayload(&aPayload[offset+4], pBuf, a, eOp, pDbPage); - sqlite3PagerUnref(pDbPage); - offset = 0; - amt -= a; - pBuf += a; - } + if( a + offset > ovflSize ){ + a = ovflSize - offset; + } + +#ifdef SQLITE_DIRECT_OVERFLOW_READ + /* If all the following are true: + ** + ** 1) this is a read operation, and + ** 2) data is required from the start of this overflow page, and + ** 3) the database is file-backed, and + ** 4) there is no open write-transaction, and + ** 5) the database is not a WAL database, + ** + ** then data can be read directly from the database file into the + ** output buffer, bypassing the page-cache altogether. This speeds + ** up loading large records that span many overflow pages. + */ + if( eOp==0 /* (1) */ + && offset==0 /* (2) */ + && pBt->inTransaction==TRANS_READ /* (4) */ + && (fd = sqlite3PagerFile(pBt->pPager))->pMethods /* (3) */ + && pBt->pPage1->aData[19]==0x01 /* (5) */ + ){ + u8 aSave[4]; + u8 *aWrite = &pBuf[-4]; + memcpy(aSave, aWrite, 4); + rc = sqlite3OsRead(fd, aWrite, a+4, pBt->pageSize * (nextPage-1)); + nextPage = get4byte(aWrite); + memcpy(aWrite, aSave, 4); + }else +#endif + + { + DbPage *pDbPage; + rc = sqlite3PagerGet(pBt->pPager, nextPage, &pDbPage); + if( rc==SQLITE_OK ){ + aPayload = sqlite3PagerGetData(pDbPage); + nextPage = get4byte(aPayload); + rc = copyPayload(&aPayload[offset+4], pBuf, a, eOp, pDbPage); + sqlite3PagerUnref(pDbPage); + offset = 0; + } + } + amt -= a; + pBuf += a; } } } if( rc==SQLITE_OK && amt>0 ){ @@ -52804,11 +52657,10 @@ } } if( c==0 ){ if( pPage->intKey && !pPage->leaf ){ lwr = idx; - upr = lwr - 1; break; }else{ *pRes = 0; rc = SQLITE_OK; goto moveto_finish; @@ -52822,11 +52674,11 @@ if( lwr>upr ){ break; } pCur->aiIdx[pCur->iPage] = (u16)(idx = (lwr+upr)/2); } - assert( lwr==upr+1 ); + assert( lwr==upr+1 || (pPage->intKey && !pPage->leaf) ); assert( pPage->isInit ); if( pPage->leaf ){ chldPg = 0; }else if( lwr>=pPage->nCell ){ chldPg = get4byte(&pPage->aData[pPage->hdrOffset+8]); @@ -53087,10 +52939,12 @@ } if( rc ){ pTrunk = 0; goto end_allocate_page; } + assert( pTrunk!=0 ); + assert( pTrunk->aData!=0 ); k = get4byte(&pTrunk->aData[4]); /* # of leaves on this trunk page */ if( k==0 && !searchList ){ /* The trunk has no leaves and the list is not being searched. ** So extract the trunk page itself and use it as the newly @@ -54214,17 +54068,19 @@ ** This is safe because dropping a cell only overwrites the first ** four bytes of it, and this function does not need the first ** four bytes of the divider cell. So the pointer is safe to use ** later on. ** - ** Unless SQLite is compiled in secure-delete mode. In this case, + ** But not if we are in secure-delete mode. In secure-delete mode, ** the dropCell() routine will overwrite the entire cell with zeroes. ** In this case, temporarily copy the cell into the aOvflSpace[] ** buffer. It will be copied out again as soon as the aSpace[] buffer ** is allocated. */ if( pBt->secureDelete ){ - int iOff = SQLITE_PTR_TO_INT(apDiv[i]) - SQLITE_PTR_TO_INT(pParent->aData); + int iOff; + + iOff = SQLITE_PTR_TO_INT(apDiv[i]) - SQLITE_PTR_TO_INT(pParent->aData); if( (iOff+szNew[i])>(int)pBt->usableSize ){ rc = SQLITE_CORRUPT_BKPT; memset(apOld, 0, (i+1)*sizeof(MemPage*)); goto balance_cleanup; }else{ @@ -54640,10 +54496,11 @@ int isDivider = 0; while( i==iNextOld ){ /* Cell i is the cell immediately following the last cell on old ** sibling page j. If the siblings are not leaf pages of an ** intkey b-tree, then cell i was a divider cell. */ + assert( j+1 < ArraySize(apCopy) ); pOld = apCopy[++j]; iNextOld = i + !leafData + pOld->nCell + pOld->nOverflow; if( pOld->nOverflow ){ nOverflow = pOld->nOverflow; iOverflow = i + !leafData + pOld->aOvfl[0].idx; @@ -56982,18 +56839,18 @@ /* ** Release all resources associated with an sqlite3_backup* handle. */ SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p){ sqlite3_backup **pp; /* Ptr to head of pagers backup list */ - sqlite3_mutex *mutex; /* Mutex to protect source database */ + MUTEX_LOGIC( sqlite3_mutex *mutex; ) /* Mutex to protect source database */ int rc; /* Value to return */ /* Enter the mutexes */ if( p==0 ) return SQLITE_OK; sqlite3_mutex_enter(p->pSrcDb->mutex); sqlite3BtreeEnter(p->pSrc); - mutex = p->pSrcDb->mutex; + MUTEX_LOGIC( mutex = p->pSrcDb->mutex; ) if( p->pDestDb ){ sqlite3_mutex_enter(p->pDestDb->mutex); } /* Detach this backup from the source pager. */ @@ -57108,13 +56965,21 @@ ** goes wrong, the transaction on pTo is rolled back. If successful, the ** transaction is committed before returning. */ SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){ int rc; + sqlite3_file *pFd; /* File descriptor for database pTo */ sqlite3_backup b; sqlite3BtreeEnter(pTo); sqlite3BtreeEnter(pFrom); + + assert( sqlite3BtreeIsInTrans(pTo) ); + pFd = sqlite3PagerFile(sqlite3BtreePager(pTo)); + if( pFd->pMethods ){ + i64 nByte = sqlite3BtreeGetPageSize(pFrom)*(i64)sqlite3BtreeLastPage(pFrom); + sqlite3OsFileControl(pFd, SQLITE_FCNTL_OVERWRITE, &nByte); + } /* Set up an sqlite3_backup object. sqlite3_backup.pDestDb must be set ** to 0. This is used by the implementations of sqlite3_backup_step() ** and sqlite3_backup_finish() to detect that they are being called ** from this function, not directly by the user. @@ -57137,10 +57002,11 @@ rc = sqlite3_backup_finish(&b); if( rc==SQLITE_OK ){ pTo->pBt->pageSizeFixed = 0; } + assert( sqlite3BtreeIsInTrans(pTo)==0 ); sqlite3BtreeLeave(pFrom); sqlite3BtreeLeave(pTo); return rc; } #endif /* SQLITE_OMIT_VACUUM */ @@ -58171,15 +58037,15 @@ *ppVal = 0; return SQLITE_OK; } op = pExpr->op; - /* op can only be TK_REGISTER if we have compiled with SQLITE_ENABLE_STAT2. + /* op can only be TK_REGISTER if we have compiled with SQLITE_ENABLE_STAT3. ** The ifdef here is to enable us to achieve 100% branch test coverage even - ** when SQLITE_ENABLE_STAT2 is omitted. + ** when SQLITE_ENABLE_STAT3 is omitted. */ -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 if( op==TK_REGISTER ) op = pExpr->op2; #else if( NEVER(op==TK_REGISTER) ) op = pExpr->op2; #endif @@ -58874,12 +58740,12 @@ /* ** Change the P2 operand of instruction addr so that it points to ** the address of the next instruction to be coded. */ SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe *p, int addr){ - assert( addr>=0 ); - sqlite3VdbeChangeP2(p, addr, p->nOp); + assert( addr>=0 || p->db->mallocFailed ); + if( addr>=0 ) sqlite3VdbeChangeP2(p, addr, p->nOp); } /* ** If the input FuncDef structure is ephemeral, then free it. If @@ -59080,34 +58946,33 @@ ** Change the comment on the the most recently coded instruction. Or ** insert a No-op and add the comment to that new instruction. This ** makes the code easier to read during debugging. None of this happens ** in a production build. */ -SQLITE_PRIVATE void sqlite3VdbeComment(Vdbe *p, const char *zFormat, ...){ - va_list ap; - if( !p ) return; +static void vdbeVComment(Vdbe *p, const char *zFormat, va_list ap){ assert( p->nOp>0 || p->aOp==0 ); assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->db->mallocFailed ); if( p->nOp ){ - char **pz = &p->aOp[p->nOp-1].zComment; + assert( p->aOp ); + sqlite3DbFree(p->db, p->aOp[p->nOp-1].zComment); + p->aOp[p->nOp-1].zComment = sqlite3VMPrintf(p->db, zFormat, ap); + } +} +SQLITE_PRIVATE void sqlite3VdbeComment(Vdbe *p, const char *zFormat, ...){ + va_list ap; + if( p ){ va_start(ap, zFormat); - sqlite3DbFree(p->db, *pz); - *pz = sqlite3VMPrintf(p->db, zFormat, ap); + vdbeVComment(p, zFormat, ap); va_end(ap); } } SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe *p, const char *zFormat, ...){ va_list ap; - if( !p ) return; - sqlite3VdbeAddOp0(p, OP_Noop); - assert( p->nOp>0 || p->aOp==0 ); - assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->db->mallocFailed ); - if( p->nOp ){ - char **pz = &p->aOp[p->nOp-1].zComment; + if( p ){ + sqlite3VdbeAddOp0(p, OP_Noop); va_start(ap, zFormat); - sqlite3DbFree(p->db, *pz); - *pz = sqlite3VMPrintf(p->db, zFormat, ap); + vdbeVComment(p, zFormat, ap); va_end(ap); } } #endif /* NDEBUG */ @@ -59441,11 +59306,11 @@ SubProgram **apSub = 0; /* Array of sub-vdbes */ Mem *pSub = 0; /* Memory cell hold array of subprogs */ sqlite3 *db = p->db; /* The database connection */ int i; /* Loop counter */ int rc = SQLITE_OK; /* Return code */ - Mem *pMem = p->pResultSet = &p->aMem[1]; /* First Mem of result set */ + Mem *pMem = &p->aMem[1]; /* First Mem of result set */ assert( p->explain ); assert( p->magic==VDBE_MAGIC_RUN ); assert( p->rc==SQLITE_OK || p->rc==SQLITE_BUSY || p->rc==SQLITE_NOMEM ); @@ -59452,10 +59317,11 @@ /* Even though this opcode does not use dynamic strings for ** the result, result columns may become dynamic if the user calls ** sqlite3_column_text16(), causing a translation to UTF-16 encoding. */ releaseMemArray(pMem, 8); + p->pResultSet = 0; if( p->rc==SQLITE_NOMEM ){ /* This happens if a malloc() inside a call to sqlite3_column_text() or ** sqlite3_column_text16() failed. */ db->mallocFailed = 1; @@ -59606,10 +59472,11 @@ pMem->type = SQLITE_NULL; } } p->nResColumn = 8 - 4*(p->explain-1); + p->pResultSet = &p->aMem[1]; p->rc = SQLITE_OK; rc = SQLITE_ROW; } return rc; } @@ -61361,11 +61228,11 @@ ** than 2GiB are support - anything large must be database corruption. ** Any corruption is detected in sqlite3BtreeParseCellPtr(), though, so ** this code can safely assume that nCellKey is 32-bits */ assert( sqlite3BtreeCursorIsValid(pCur) ); - rc = sqlite3BtreeKeySize(pCur, &nCellKey); + VVA_ONLY(rc =) sqlite3BtreeKeySize(pCur, &nCellKey); assert( rc==SQLITE_OK ); /* pCur is always valid so KeySize cannot fail */ assert( (nCellKey & SQLITE_MAX_U32)==(u64)nCellKey ); /* Read in the complete content of the index entry */ memset(&m, 0, sizeof(m)); @@ -61436,11 +61303,11 @@ int rc; BtCursor *pCur = pC->pCursor; Mem m; assert( sqlite3BtreeCursorIsValid(pCur) ); - rc = sqlite3BtreeKeySize(pCur, &nCellKey); + VVA_ONLY(rc =) sqlite3BtreeKeySize(pCur, &nCellKey); assert( rc==SQLITE_OK ); /* pCur is always valid so KeySize cannot fail */ /* nCellKey will always be between 0 and 0xffffffff because of the say ** that btreeParseCellPtr() and sqlite3GetVarint32() are implemented */ if( nCellKey<=0 || nCellKey>0x7fffffff ){ *res = 0; @@ -65712,20 +65579,20 @@ }else if( u.am.pC->cacheStatus==p->cacheCtr ){ u.am.payloadSize = u.am.pC->payloadSize; u.am.zRec = (char*)u.am.pC->aRow; }else if( u.am.pC->isIndex ){ assert( sqlite3BtreeCursorIsValid(u.am.pCrsr) ); - rc = sqlite3BtreeKeySize(u.am.pCrsr, &u.am.payloadSize64); + VVA_ONLY(rc =) sqlite3BtreeKeySize(u.am.pCrsr, &u.am.payloadSize64); assert( rc==SQLITE_OK ); /* True because of CursorMoveto() call above */ /* sqlite3BtreeParseCellPtr() uses getVarint32() to extract the ** payload size, so it is impossible for u.am.payloadSize64 to be ** larger than 32 bits. */ assert( (u.am.payloadSize64 & SQLITE_MAX_U32)==(u64)u.am.payloadSize64 ); u.am.payloadSize = (u32)u.am.payloadSize64; }else{ assert( sqlite3BtreeCursorIsValid(u.am.pCrsr) ); - rc = sqlite3BtreeDataSize(u.am.pCrsr, &u.am.payloadSize); + VVA_ONLY(rc =) sqlite3BtreeDataSize(u.am.pCrsr, &u.am.payloadSize); assert( rc==SQLITE_OK ); /* DataSize() cannot fail */ } }else if( ALWAYS(u.am.pC->pseudoTableReg>0) ){ u.am.pReg = &aMem[u.am.pC->pseudoTableReg]; assert( u.am.pReg->flags & MEM_Blob ); @@ -67773,18 +67640,18 @@ rc = sqlite3VdbeCursorMoveto(u.bk.pC); if( NEVER(rc!=SQLITE_OK) ) goto abort_due_to_error; if( u.bk.pC->isIndex ){ assert( !u.bk.pC->isTable ); - rc = sqlite3BtreeKeySize(u.bk.pCrsr, &u.bk.n64); + VVA_ONLY(rc =) sqlite3BtreeKeySize(u.bk.pCrsr, &u.bk.n64); assert( rc==SQLITE_OK ); /* True because of CursorMoveto() call above */ if( u.bk.n64>db->aLimit[SQLITE_LIMIT_LENGTH] ){ goto too_big; } u.bk.n = (u32)u.bk.n64; }else{ - rc = sqlite3BtreeDataSize(u.bk.pCrsr, &u.bk.n); + VVA_ONLY(rc =) sqlite3BtreeDataSize(u.bk.pCrsr, &u.bk.n); assert( rc==SQLITE_OK ); /* DataSize() cannot fail */ if( u.bk.n>(u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){ goto too_big; } } @@ -73417,11 +73284,12 @@ pNew->flags |= EP_IntValue; pNew->u.iValue = iValue; }else{ int c; pNew->u.zToken = (char*)&pNew[1]; - memcpy(pNew->u.zToken, pToken->z, pToken->n); + assert( pToken->z!=0 || pToken->n==0 ); + if( pToken->n ) memcpy(pNew->u.zToken, pToken->z, pToken->n); pNew->u.zToken[pToken->n] = 0; if( dequote && nExtra>=3 && ((c = pToken->z[0])=='\'' || c=='"' || c=='[' || c=='`') ){ sqlite3Dequote(pNew->u.zToken); if( c=='"' ) pNew->flags |= EP_DblQuoted; @@ -74456,15 +74324,23 @@ ** ephemeral table. */ p = (ExprHasProperty(pX, EP_xIsSelect) ? pX->x.pSelect : 0); if( ALWAYS(pParse->nErr==0) && isCandidateForInOpt(p) ){ sqlite3 *db = pParse->db; /* Database connection */ - Expr *pExpr = p->pEList->a[0].pExpr; /* Expression */ - int iCol = pExpr->iColumn; /* Index of column */ Vdbe *v = sqlite3GetVdbe(pParse); /* Virtual machine being coded */ - Table *pTab = p->pSrc->a[0].pTab; /* Table . */ + Table *pTab; /* Table
. */ + Expr *pExpr; /* Expression
*/ + int iCol; /* Index of column */ int iDb; /* Database idx for pTab */ + + assert( p ); /* Because of isCandidateForInOpt(p) */ + assert( p->pEList!=0 ); /* Because of isCandidateForInOpt(p) */ + assert( p->pEList->a[0].pExpr!=0 ); /* Because of isCandidateForInOpt(p) */ + assert( p->pSrc!=0 ); /* Because of isCandidateForInOpt(p) */ + pTab = p->pSrc->a[0].pTab; + pExpr = p->pEList->a[0].pExpr; + iCol = pExpr->iColumn; /* Code an OP_VerifyCookie and OP_TableLock for . */ iDb = sqlite3SchemaToIndex(db, pTab->pSchema); sqlite3CodeVerifySchema(pParse, iDb); sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); @@ -76467,11 +76343,11 @@ if( !ExprHasProperty(pB, EP_IntValue) || pA->u.iValue!=pB->u.iValue ){ return 2; } }else if( pA->op!=TK_COLUMN && pA->u.zToken ){ if( ExprHasProperty(pB, EP_IntValue) || NEVER(pB->u.zToken==0) ) return 2; - if( sqlite3StrICmp(pA->u.zToken,pB->u.zToken)!=0 ){ + if( strcmp(pA->u.zToken,pB->u.zToken)!=0 ){ return 2; } } if( (pA->flags & EP_ExpCollate)!=(pB->flags & EP_ExpCollate) ) return 1; if( (pA->flags & EP_ExpCollate)!=0 && pA->pColl!=pB->pColl ) return 2; @@ -77610,10 +77486,112 @@ ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This file contains code associated with the ANALYZE command. +** +** The ANALYZE command gather statistics about the content of tables +** and indices. These statistics are made available to the query planner +** to help it make better decisions about how to perform queries. +** +** The following system tables are or have been supported: +** +** CREATE TABLE sqlite_stat1(tbl, idx, stat); +** CREATE TABLE sqlite_stat2(tbl, idx, sampleno, sample); +** CREATE TABLE sqlite_stat3(tbl, idx, nEq, nLt, nDLt, sample); +** +** Additional tables might be added in future releases of SQLite. +** The sqlite_stat2 table is not created or used unless the SQLite version +** is between 3.6.18 and 3.7.8, inclusive, and unless SQLite is compiled +** with SQLITE_ENABLE_STAT2. The sqlite_stat2 table is deprecated. +** The sqlite_stat2 table is superceded by sqlite_stat3, which is only +** created and used by SQLite versions 3.7.9 and later and with +** SQLITE_ENABLE_STAT3 defined. The fucntionality of sqlite_stat3 +** is a superset of sqlite_stat2. +** +** Format of sqlite_stat1: +** +** There is normally one row per index, with the index identified by the +** name in the idx column. The tbl column is the name of the table to +** which the index belongs. In each such row, the stat column will be +** a string consisting of a list of integers. The first integer in this +** list is the number of rows in the index and in the table. The second +** integer is the average number of rows in the index that have the same +** value in the first column of the index. The third integer is the average +** number of rows in the index that have the same value for the first two +** columns. The N-th integer (for N>1) is the average number of rows in +** the index which have the same value for the first N-1 columns. For +** a K-column index, there will be K+1 integers in the stat column. If +** the index is unique, then the last integer will be 1. +** +** The list of integers in the stat column can optionally be followed +** by the keyword "unordered". The "unordered" keyword, if it is present, +** must be separated from the last integer by a single space. If the +** "unordered" keyword is present, then the query planner assumes that +** the index is unordered and will not use the index for a range query. +** +** If the sqlite_stat1.idx column is NULL, then the sqlite_stat1.stat +** column contains a single integer which is the (estimated) number of +** rows in the table identified by sqlite_stat1.tbl. +** +** Format of sqlite_stat2: +** +** The sqlite_stat2 is only created and is only used if SQLite is compiled +** with SQLITE_ENABLE_STAT2 and if the SQLite version number is between +** 3.6.18 and 3.7.8. The "stat2" table contains additional information +** about the distribution of keys within an index. The index is identified by +** the "idx" column and the "tbl" column is the name of the table to which +** the index belongs. There are usually 10 rows in the sqlite_stat2 +** table for each index. +** +** The sqlite_stat2 entries for an index that have sampleno between 0 and 9 +** inclusive are samples of the left-most key value in the index taken at +** evenly spaced points along the index. Let the number of samples be S +** (10 in the standard build) and let C be the number of rows in the index. +** Then the sampled rows are given by: +** +** rownumber = (i*C*2 + C)/(S*2) +** +** For i between 0 and S-1. Conceptually, the index space is divided into +** S uniform buckets and the samples are the middle row from each bucket. +** +** The format for sqlite_stat2 is recorded here for legacy reference. This +** version of SQLite does not support sqlite_stat2. It neither reads nor +** writes the sqlite_stat2 table. This version of SQLite only supports +** sqlite_stat3. +** +** Format for sqlite_stat3: +** +** The sqlite_stat3 is an enhancement to sqlite_stat2. A new name is +** used to avoid compatibility problems. +** +** The format of the sqlite_stat3 table is similar to the format of +** the sqlite_stat2 table. There are multiple entries for each index. +** The idx column names the index and the tbl column is the table of the +** index. If the idx and tbl columns are the same, then the sample is +** of the INTEGER PRIMARY KEY. The sample column is a value taken from +** the left-most column of the index. The nEq column is the approximate +** number of entires in the index whose left-most column exactly matches +** the sample. nLt is the approximate number of entires whose left-most +** column is less than the sample. The nDLt column is the approximate +** number of distinct left-most entries in the index that are less than +** the sample. +** +** Future versions of SQLite might change to store a string containing +** multiple integers values in the nDLt column of sqlite_stat3. The first +** integer will be the number of prior index entires that are distinct in +** the left-most column. The second integer will be the number of prior index +** entries that are distinct in the first two columns. The third integer +** will be the number of prior index entries that are distinct in the first +** three columns. And so forth. With that extension, the nDLt field is +** similar in function to the sqlite_stat1.stat field. +** +** There can be an arbitrary number of sqlite_stat3 entries per index. +** The ANALYZE command will typically generate sqlite_stat3 tables +** that contain between 10 and 40 samples which are distributed across +** the key space, though not uniformly, and which include samples with +** largest possible nEq values. */ #ifndef SQLITE_OMIT_ANALYZE /* ** This routine generates code that opens the sqlite_stat1 table for @@ -77641,12 +77619,12 @@ static const struct { const char *zName; const char *zCols; } aTable[] = { { "sqlite_stat1", "tbl,idx,stat" }, -#ifdef SQLITE_ENABLE_STAT2 - { "sqlite_stat2", "tbl,idx,sampleno,sample" }, +#ifdef SQLITE_ENABLE_STAT3 + { "sqlite_stat3", "tbl,idx,neq,nlt,ndlt,sample" }, #endif }; int aRoot[] = {0, 0}; u8 aCreateTbl[] = {0, 0}; @@ -77658,10 +77636,13 @@ if( v==0 ) return; assert( sqlite3BtreeHoldsAllMutexes(db) ); assert( sqlite3VdbeDb(v)==db ); pDb = &db->aDb[iDb]; + /* Create new statistic tables if they do not exist, or clear them + ** if they do already exist. + */ for(i=0; i
zName))==0 ){ /* The sqlite_stat[12] table does not exist. Create it. Note that a @@ -77688,17 +77669,237 @@ sqlite3VdbeAddOp2(v, OP_Clear, aRoot[i], iDb); } } } - /* Open the sqlite_stat[12] tables for writing. */ + /* Open the sqlite_stat[13] tables for writing. */ for(i=0; i a[0])*mxSample; + p = sqlite3_malloc( n ); + if( p==0 ){ + sqlite3_result_error_nomem(context); + return; + } + memset(p, 0, n); + p->a = (struct Stat3Sample*)&p[1]; + p->nRow = nRow; + p->mxSample = mxSample; + p->nPSample = p->nRow/(mxSample/3+1) + 1; + sqlite3_randomness(sizeof(p->iPrn), &p->iPrn); + sqlite3_result_blob(context, p, sizeof(p), sqlite3_free); +} +static const FuncDef stat3InitFuncdef = { + 2, /* nArg */ + SQLITE_UTF8, /* iPrefEnc */ + 0, /* flags */ + 0, /* pUserData */ + 0, /* pNext */ + stat3Init, /* xFunc */ + 0, /* xStep */ + 0, /* xFinalize */ + "stat3_init", /* zName */ + 0, /* pHash */ + 0 /* pDestructor */ +}; + + +/* +** Implementation of the stat3_push(nEq,nLt,nDLt,rowid,P) SQL function. The +** arguments describe a single key instance. This routine makes the +** decision about whether or not to retain this key for the sqlite_stat3 +** table. +** +** The return value is NULL. +*/ +static void stat3Push( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Stat3Accum *p = (Stat3Accum*)sqlite3_value_blob(argv[4]); + tRowcnt nEq = sqlite3_value_int64(argv[0]); + tRowcnt nLt = sqlite3_value_int64(argv[1]); + tRowcnt nDLt = sqlite3_value_int64(argv[2]); + i64 rowid = sqlite3_value_int64(argv[3]); + u8 isPSample = 0; + u8 doInsert = 0; + int iMin = p->iMin; + struct Stat3Sample *pSample; + int i; + u32 h; + + UNUSED_PARAMETER(context); + UNUSED_PARAMETER(argc); + if( nEq==0 ) return; + h = p->iPrn = p->iPrn*1103515245 + 12345; + if( (nLt/p->nPSample)!=((nEq+nLt)/p->nPSample) ){ + doInsert = isPSample = 1; + }else if( p->nSample mxSample ){ + doInsert = 1; + }else{ + if( nEq>p->a[iMin].nEq || (nEq==p->a[iMin].nEq && h>p->a[iMin].iHash) ){ + doInsert = 1; + } + } + if( !doInsert ) return; + if( p->nSample==p->mxSample ){ + assert( p->nSample - iMin - 1 >= 0 ); + memmove(&p->a[iMin], &p->a[iMin+1], sizeof(p->a[0])*(p->nSample-iMin-1)); + pSample = &p->a[p->nSample-1]; + }else{ + pSample = &p->a[p->nSample++]; + } + pSample->iRowid = rowid; + pSample->nEq = nEq; + pSample->nLt = nLt; + pSample->nDLt = nDLt; + pSample->iHash = h; + pSample->isPSample = isPSample; + + /* Find the new minimum */ + if( p->nSample==p->mxSample ){ + pSample = p->a; + i = 0; + while( pSample->isPSample ){ + i++; + pSample++; + assert( i nSample ); + } + nEq = pSample->nEq; + h = pSample->iHash; + iMin = i; + for(i++, pSample++; i nSample; i++, pSample++){ + if( pSample->isPSample ) continue; + if( pSample->nEq nEq==nEq && pSample->iHash nEq; + h = pSample->iHash; + } + } + p->iMin = iMin; + } +} +static const FuncDef stat3PushFuncdef = { + 5, /* nArg */ + SQLITE_UTF8, /* iPrefEnc */ + 0, /* flags */ + 0, /* pUserData */ + 0, /* pNext */ + stat3Push, /* xFunc */ + 0, /* xStep */ + 0, /* xFinalize */ + "stat3_push", /* zName */ + 0, /* pHash */ + 0 /* pDestructor */ +}; + +/* +** Implementation of the stat3_get(P,N,...) SQL function. This routine is +** used to query the results. Content is returned for the Nth sqlite_stat3 +** row where N is between 0 and S-1 and S is the number of samples. The +** value returned depends on the number of arguments. +** +** argc==2 result: rowid +** argc==3 result: nEq +** argc==4 result: nLt +** argc==5 result: nDLt +*/ +static void stat3Get( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int n = sqlite3_value_int(argv[1]); + Stat3Accum *p = (Stat3Accum*)sqlite3_value_blob(argv[0]); + + assert( p!=0 ); + if( p->nSample<=n ) return; + switch( argc ){ + case 2: sqlite3_result_int64(context, p->a[n].iRowid); break; + case 3: sqlite3_result_int64(context, p->a[n].nEq); break; + case 4: sqlite3_result_int64(context, p->a[n].nLt); break; + default: sqlite3_result_int64(context, p->a[n].nDLt); break; + } +} +static const FuncDef stat3GetFuncdef = { + -1, /* nArg */ + SQLITE_UTF8, /* iPrefEnc */ + 0, /* flags */ + 0, /* pUserData */ + 0, /* pNext */ + stat3Get, /* xFunc */ + 0, /* xStep */ + 0, /* xFinalize */ + "stat3_get", /* zName */ + 0, /* pHash */ + 0 /* pDestructor */ +}; +#endif /* SQLITE_ENABLE_STAT3 */ + + + /* ** Generate code to do an analysis of all indices associated with ** a single table. */ @@ -77718,24 +77919,31 @@ int endOfLoop; /* The end of the loop */ int jZeroRows = -1; /* Jump from here if number of rows is zero */ int iDb; /* Index of database containing pTab */ int regTabname = iMem++; /* Register containing table name */ int regIdxname = iMem++; /* Register containing index name */ - int regSampleno = iMem++; /* Register containing next sample number */ - int regCol = iMem++; /* Content of a column analyzed table */ + int regStat1 = iMem++; /* The stat column of sqlite_stat1 */ +#ifdef SQLITE_ENABLE_STAT3 + int regNumEq = regStat1; /* Number of instances. Same as regStat1 */ + int regNumLt = iMem++; /* Number of keys less than regSample */ + int regNumDLt = iMem++; /* Number of distinct keys less than regSample */ + int regSample = iMem++; /* The next sample value */ + int regRowid = regSample; /* Rowid of a sample */ + int regAccum = iMem++; /* Register to hold Stat3Accum object */ + int regLoop = iMem++; /* Loop counter */ + int regCount = iMem++; /* Number of rows in the table or index */ + int regTemp1 = iMem++; /* Intermediate register */ + int regTemp2 = iMem++; /* Intermediate register */ + int once = 1; /* One-time initialization */ + int shortJump = 0; /* Instruction address */ + int iTabCur = pParse->nTab++; /* Table cursor */ +#endif + int regCol = iMem++; /* Content of a column in analyzed table */ int regRec = iMem++; /* Register holding completed record */ int regTemp = iMem++; /* Temporary use register */ - int regRowid = iMem++; /* Rowid for the inserted record */ - -#ifdef SQLITE_ENABLE_STAT2 - int addr = 0; /* Instruction address */ - int regTemp2 = iMem++; /* Temporary use register */ - int regSamplerecno = iMem++; /* Index of next sample to record */ - int regRecno = iMem++; /* Current sample index */ - int regLast = iMem++; /* Index of last sample to record */ - int regFirst = iMem++; /* Index of first sample to record */ -#endif + int regNewRowid = iMem++; /* Rowid for the inserted record */ + v = sqlite3GetVdbe(pParse); if( v==0 || NEVER(pTab==0) ){ return; } @@ -77764,13 +77972,18 @@ iIdxCur = pParse->nTab++; sqlite3VdbeAddOp4(v, OP_String8, 0, regTabname, 0, pTab->zName, 0); for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ int nCol; KeyInfo *pKey; + int addrIfNot = 0; /* address of OP_IfNot */ + int *aChngAddr; /* Array of jump instruction addresses */ if( pOnlyIdx && pOnlyIdx!=pIdx ) continue; + VdbeNoopComment((v, "Begin analysis of %s", pIdx->zName)); nCol = pIdx->nColumn; + aChngAddr = sqlite3DbMallocRaw(db, sizeof(int)*nCol); + if( aChngAddr==0 ) continue; pKey = sqlite3IndexKeyinfo(pParse, pIdx); if( iMem+1+(nCol*2)>pParse->nMem ){ pParse->nMem = iMem+1+(nCol*2); } @@ -77781,35 +77994,24 @@ VdbeComment((v, "%s", pIdx->zName)); /* Populate the register containing the index name. */ sqlite3VdbeAddOp4(v, OP_String8, 0, regIdxname, 0, pIdx->zName, 0); -#ifdef SQLITE_ENABLE_STAT2 - - /* If this iteration of the loop is generating code to analyze the - ** first index in the pTab->pIndex list, then register regLast has - ** not been populated. In this case populate it now. */ - if( pTab->pIndex==pIdx ){ - sqlite3VdbeAddOp2(v, OP_Integer, SQLITE_INDEX_SAMPLES, regSamplerecno); - sqlite3VdbeAddOp2(v, OP_Integer, SQLITE_INDEX_SAMPLES*2-1, regTemp); - sqlite3VdbeAddOp2(v, OP_Integer, SQLITE_INDEX_SAMPLES*2, regTemp2); - - sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regLast); - sqlite3VdbeAddOp2(v, OP_Null, 0, regFirst); - addr = sqlite3VdbeAddOp3(v, OP_Lt, regSamplerecno, 0, regLast); - sqlite3VdbeAddOp3(v, OP_Divide, regTemp2, regLast, regFirst); - sqlite3VdbeAddOp3(v, OP_Multiply, regLast, regTemp, regLast); - sqlite3VdbeAddOp2(v, OP_AddImm, regLast, SQLITE_INDEX_SAMPLES*2-2); - sqlite3VdbeAddOp3(v, OP_Divide, regTemp2, regLast, regLast); - sqlite3VdbeJumpHere(v, addr); - } - - /* Zero the regSampleno and regRecno registers. */ - sqlite3VdbeAddOp2(v, OP_Integer, 0, regSampleno); - sqlite3VdbeAddOp2(v, OP_Integer, 0, regRecno); - sqlite3VdbeAddOp2(v, OP_Copy, regFirst, regSamplerecno); -#endif +#ifdef SQLITE_ENABLE_STAT3 + if( once ){ + once = 0; + sqlite3OpenTable(pParse, iTabCur, iDb, pTab, OP_OpenRead); + } + sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regCount); + sqlite3VdbeAddOp2(v, OP_Integer, SQLITE_STAT3_SAMPLES, regTemp1); + sqlite3VdbeAddOp2(v, OP_Integer, 0, regNumEq); + sqlite3VdbeAddOp2(v, OP_Integer, 0, regNumLt); + sqlite3VdbeAddOp2(v, OP_Integer, -1, regNumDLt); + sqlite3VdbeAddOp4(v, OP_Function, 1, regCount, regAccum, + (char*)&stat3InitFuncdef, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, 2); +#endif /* SQLITE_ENABLE_STAT3 */ /* The block of memory cells initialized here is used as follows. ** ** iMem: ** The total number of rows in the table. @@ -77835,79 +78037,87 @@ /* Start the analysis loop. This loop runs through all the entries in ** the index b-tree. */ endOfLoop = sqlite3VdbeMakeLabel(v); sqlite3VdbeAddOp2(v, OP_Rewind, iIdxCur, endOfLoop); topOfLoop = sqlite3VdbeCurrentAddr(v); - sqlite3VdbeAddOp2(v, OP_AddImm, iMem, 1); + sqlite3VdbeAddOp2(v, OP_AddImm, iMem, 1); /* Increment row counter */ for(i=0; i azColl!=0 ); assert( pIdx->azColl[i]!=0 ); pColl = sqlite3LocateCollSeq(pParse, pIdx->azColl[i]); - sqlite3VdbeAddOp4(v, OP_Ne, regCol, 0, iMem+nCol+i+1, - (char*)pColl, P4_COLLSEQ); + aChngAddr[i] = sqlite3VdbeAddOp4(v, OP_Ne, regCol, 0, iMem+nCol+i+1, + (char*)pColl, P4_COLLSEQ); sqlite3VdbeChangeP5(v, SQLITE_NULLEQ); - } - if( db->mallocFailed ){ - /* If a malloc failure has occurred, then the result of the expression - ** passed as the second argument to the call to sqlite3VdbeJumpHere() - ** below may be negative. Which causes an assert() to fail (or an - ** out-of-bounds write if SQLITE_DEBUG is not defined). */ - return; + VdbeComment((v, "jump if column %d changed", i)); +#ifdef SQLITE_ENABLE_STAT3 + if( i==0 ){ + sqlite3VdbeAddOp2(v, OP_AddImm, regNumEq, 1); + VdbeComment((v, "incr repeat count")); + } +#endif } sqlite3VdbeAddOp2(v, OP_Goto, 0, endOfLoop); for(i=0; i nColumn, regRowid); + sqlite3VdbeAddOp3(v, OP_Add, regNumEq, regNumLt, regNumLt); + sqlite3VdbeAddOp2(v, OP_AddImm, regNumDLt, 1); + sqlite3VdbeAddOp2(v, OP_Integer, 1, regNumEq); +#endif } - sqlite3VdbeJumpHere(v, addr2); /* Set jump dest for the OP_Ne */ sqlite3VdbeAddOp2(v, OP_AddImm, iMem+i+1, 1); sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, iMem+nCol+i+1); } + sqlite3DbFree(db, aChngAddr); - /* End of the analysis loop. */ + /* Always jump here after updating the iMem+1...iMem+1+nCol counters */ sqlite3VdbeResolveLabel(v, endOfLoop); + sqlite3VdbeAddOp2(v, OP_Next, iIdxCur, topOfLoop); sqlite3VdbeAddOp1(v, OP_Close, iIdxCur); +#ifdef SQLITE_ENABLE_STAT3 + sqlite3VdbeAddOp4(v, OP_Function, 1, regNumEq, regTemp2, + (char*)&stat3PushFuncdef, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, 5); + sqlite3VdbeAddOp2(v, OP_Integer, -1, regLoop); + shortJump = + sqlite3VdbeAddOp2(v, OP_AddImm, regLoop, 1); + sqlite3VdbeAddOp4(v, OP_Function, 1, regAccum, regTemp1, + (char*)&stat3GetFuncdef, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, 2); + sqlite3VdbeAddOp1(v, OP_IsNull, regTemp1); + sqlite3VdbeAddOp3(v, OP_NotExists, iTabCur, shortJump, regTemp1); + sqlite3VdbeAddOp3(v, OP_Column, iTabCur, pIdx->aiColumn[0], regSample); + sqlite3ColumnDefault(v, pTab, pIdx->aiColumn[0], regSample); + sqlite3VdbeAddOp4(v, OP_Function, 1, regAccum, regNumEq, + (char*)&stat3GetFuncdef, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, 3); + sqlite3VdbeAddOp4(v, OP_Function, 1, regAccum, regNumLt, + (char*)&stat3GetFuncdef, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, 4); + sqlite3VdbeAddOp4(v, OP_Function, 1, regAccum, regNumDLt, + (char*)&stat3GetFuncdef, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, 5); + sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 6, regRec, "bbbbbb", 0); + sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur+1, regNewRowid); + sqlite3VdbeAddOp3(v, OP_Insert, iStatCur+1, regRec, regNewRowid); + sqlite3VdbeAddOp2(v, OP_Goto, 0, shortJump); + sqlite3VdbeJumpHere(v, shortJump+2); +#endif /* Store the results in sqlite_stat1. ** ** The result is a single row of the sqlite_stat1 table. The first ** two columns are the names of the table and index. The third column @@ -77923,50 +78133,51 @@ ** ** If K==0 then no entry is made into the sqlite_stat1 table. ** If K>0 then it is always the case the D>0 so division by zero ** is never possible. */ - sqlite3VdbeAddOp2(v, OP_SCopy, iMem, regSampleno); + sqlite3VdbeAddOp2(v, OP_SCopy, iMem, regStat1); if( jZeroRows<0 ){ jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, iMem); } for(i=0; i pIndex==0 ){ sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pTab->tnum, iDb); VdbeComment((v, "%s", pTab->zName)); - sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regSampleno); + sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regStat1); sqlite3VdbeAddOp1(v, OP_Close, iIdxCur); - jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, regSampleno); + jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, regStat1); }else{ sqlite3VdbeJumpHere(v, jZeroRows); jZeroRows = sqlite3VdbeAddOp0(v, OP_Goto); } sqlite3VdbeAddOp2(v, OP_Null, 0, regIdxname); sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regRec, "aaa", 0); - sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regRowid); - sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regRec, regRowid); + sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid); + sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regRec, regNewRowid); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); if( pParse->nMem nMem = regRec; sqlite3VdbeJumpHere(v, jZeroRows); } + /* ** Generate code that will cause the most recent index analysis to ** be loaded into internal hash tables where is can be used. */ @@ -77987,11 +78198,11 @@ int iStatCur; int iMem; sqlite3BeginWriteOperation(pParse, 0, iDb); iStatCur = pParse->nTab; - pParse->nTab += 2; + pParse->nTab += 3; openStatTable(pParse, iDb, iStatCur, 0, 0); iMem = pParse->nMem+1; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){ Table *pTab = (Table*)sqliteHashData(k); @@ -78012,11 +78223,11 @@ assert( pTab!=0 ); assert( sqlite3BtreeHoldsAllMutexes(pParse->db) ); iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); sqlite3BeginWriteOperation(pParse, 0, iDb); iStatCur = pParse->nTab; - pParse->nTab += 2; + pParse->nTab += 3; if( pOnlyIdx ){ openStatTable(pParse, iDb, iStatCur, pOnlyIdx->zName, "idx"); }else{ openStatTable(pParse, iDb, iStatCur, pTab->zName, "tbl"); } @@ -78117,11 +78328,11 @@ static int analysisLoader(void *pData, int argc, char **argv, char **NotUsed){ analysisInfo *pInfo = (analysisInfo*)pData; Index *pIndex; Table *pTable; int i, c, n; - unsigned int v; + tRowcnt v; const char *z; assert( argc==3 ); UNUSED_PARAMETER2(NotUsed, argc); @@ -78160,40 +78371,172 @@ /* ** If the Index.aSample variable is not NULL, delete the aSample[] array ** and its contents. */ SQLITE_PRIVATE void sqlite3DeleteIndexSamples(sqlite3 *db, Index *pIdx){ -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 if( pIdx->aSample ){ int j; - for(j=0; j nSample; j++){ IndexSample *p = &pIdx->aSample[j]; if( p->eType==SQLITE_TEXT || p->eType==SQLITE_BLOB ){ sqlite3DbFree(db, p->u.z); } } sqlite3DbFree(db, pIdx->aSample); + } + if( db && db->pnBytesFreed==0 ){ + pIdx->nSample = 0; + pIdx->aSample = 0; } #else UNUSED_PARAMETER(db); UNUSED_PARAMETER(pIdx); #endif } +#ifdef SQLITE_ENABLE_STAT3 /* -** Load the content of the sqlite_stat1 and sqlite_stat2 tables. The +** Load content from the sqlite_stat3 table into the Index.aSample[] +** arrays of all indices. +*/ +static int loadStat3(sqlite3 *db, const char *zDb){ + int rc; /* Result codes from subroutines */ + sqlite3_stmt *pStmt = 0; /* An SQL statement being run */ + char *zSql; /* Text of the SQL statement */ + Index *pPrevIdx = 0; /* Previous index in the loop */ + int idx = 0; /* slot in pIdx->aSample[] for next sample */ + int eType; /* Datatype of a sample */ + IndexSample *pSample; /* A slot in pIdx->aSample[] */ + + if( !sqlite3FindTable(db, "sqlite_stat3", zDb) ){ + return SQLITE_OK; + } + + zSql = sqlite3MPrintf(db, + "SELECT idx,count(*) FROM %Q.sqlite_stat3" + " GROUP BY idx", zDb); + if( !zSql ){ + return SQLITE_NOMEM; + } + rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0); + sqlite3DbFree(db, zSql); + if( rc ) return rc; + + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + char *zIndex; /* Index name */ + Index *pIdx; /* Pointer to the index object */ + int nSample; /* Number of samples */ + + zIndex = (char *)sqlite3_column_text(pStmt, 0); + if( zIndex==0 ) continue; + nSample = sqlite3_column_int(pStmt, 1); + pIdx = sqlite3FindIndex(db, zIndex, zDb); + if( pIdx==0 ) continue; + assert( pIdx->nSample==0 ); + pIdx->nSample = nSample; + pIdx->aSample = sqlite3MallocZero( nSample*sizeof(IndexSample) ); + pIdx->avgEq = pIdx->aiRowEst[1]; + if( pIdx->aSample==0 ){ + db->mallocFailed = 1; + sqlite3_finalize(pStmt); + return SQLITE_NOMEM; + } + } + rc = sqlite3_finalize(pStmt); + if( rc ) return rc; + + zSql = sqlite3MPrintf(db, + "SELECT idx,neq,nlt,ndlt,sample FROM %Q.sqlite_stat3", zDb); + if( !zSql ){ + return SQLITE_NOMEM; + } + rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0); + sqlite3DbFree(db, zSql); + if( rc ) return rc; + + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + char *zIndex; /* Index name */ + Index *pIdx; /* Pointer to the index object */ + int i; /* Loop counter */ + tRowcnt sumEq; /* Sum of the nEq values */ + + zIndex = (char *)sqlite3_column_text(pStmt, 0); + if( zIndex==0 ) continue; + pIdx = sqlite3FindIndex(db, zIndex, zDb); + if( pIdx==0 ) continue; + if( pIdx==pPrevIdx ){ + idx++; + }else{ + pPrevIdx = pIdx; + idx = 0; + } + assert( idx nSample ); + pSample = &pIdx->aSample[idx]; + pSample->nEq = (tRowcnt)sqlite3_column_int64(pStmt, 1); + pSample->nLt = (tRowcnt)sqlite3_column_int64(pStmt, 2); + pSample->nDLt = (tRowcnt)sqlite3_column_int64(pStmt, 3); + if( idx==pIdx->nSample-1 ){ + if( pSample->nDLt>0 ){ + for(i=0, sumEq=0; i<=idx-1; i++) sumEq += pIdx->aSample[i].nEq; + pIdx->avgEq = (pSample->nLt - sumEq)/pSample->nDLt; + } + if( pIdx->avgEq<=0 ) pIdx->avgEq = 1; + } + eType = sqlite3_column_type(pStmt, 4); + pSample->eType = (u8)eType; + switch( eType ){ + case SQLITE_INTEGER: { + pSample->u.i = sqlite3_column_int64(pStmt, 4); + break; + } + case SQLITE_FLOAT: { + pSample->u.r = sqlite3_column_double(pStmt, 4); + break; + } + case SQLITE_NULL: { + break; + } + default: assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); { + const char *z = (const char *)( + (eType==SQLITE_BLOB) ? + sqlite3_column_blob(pStmt, 4): + sqlite3_column_text(pStmt, 4) + ); + int n = z ? sqlite3_column_bytes(pStmt, 4) : 0; + pSample->nByte = n; + if( n < 1){ + pSample->u.z = 0; + }else{ + pSample->u.z = sqlite3Malloc(n); + if( pSample->u.z==0 ){ + db->mallocFailed = 1; + sqlite3_finalize(pStmt); + return SQLITE_NOMEM; + } + memcpy(pSample->u.z, z, n); + } + } + } + } + return sqlite3_finalize(pStmt); +} +#endif /* SQLITE_ENABLE_STAT3 */ + +/* +** Load the content of the sqlite_stat1 and sqlite_stat3 tables. The ** contents of sqlite_stat1 are used to populate the Index.aiRowEst[] -** arrays. The contents of sqlite_stat2 are used to populate the +** arrays. The contents of sqlite_stat3 are used to populate the ** Index.aSample[] arrays. ** ** If the sqlite_stat1 table is not present in the database, SQLITE_ERROR -** is returned. In this case, even if SQLITE_ENABLE_STAT2 was defined -** during compilation and the sqlite_stat2 table is present, no data is +** is returned. In this case, even if SQLITE_ENABLE_STAT3 was defined +** during compilation and the sqlite_stat3 table is present, no data is ** read from it. ** -** If SQLITE_ENABLE_STAT2 was defined during compilation and the -** sqlite_stat2 table is not present in the database, SQLITE_ERROR is +** If SQLITE_ENABLE_STAT3 was defined during compilation and the +** sqlite_stat3 table is not present in the database, SQLITE_ERROR is ** returned. However, in this case, data is read from the sqlite_stat1 ** table (if it is present) before returning. ** ** If an OOM error occurs, this function always sets db->mallocFailed. ** This means if the caller does not care about other errors, the return @@ -78211,12 +78554,14 @@ /* Clear any prior statistics */ assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); for(i=sqliteHashFirst(&db->aDb[iDb].pSchema->idxHash);i;i=sqliteHashNext(i)){ Index *pIdx = sqliteHashData(i); sqlite3DefaultRowEst(pIdx); +#ifdef SQLITE_ENABLE_STAT3 sqlite3DeleteIndexSamples(db, pIdx); pIdx->aSample = 0; +#endif } /* Check to make sure the sqlite_stat1 table exists */ sInfo.db = db; sInfo.zDatabase = db->aDb[iDb].zName; @@ -78224,91 +78569,23 @@ return SQLITE_ERROR; } /* Load new statistics out of the sqlite_stat1 table */ zSql = sqlite3MPrintf(db, - "SELECT tbl, idx, stat FROM %Q.sqlite_stat1", sInfo.zDatabase); + "SELECT tbl,idx,stat FROM %Q.sqlite_stat1", sInfo.zDatabase); if( zSql==0 ){ rc = SQLITE_NOMEM; }else{ rc = sqlite3_exec(db, zSql, analysisLoader, &sInfo, 0); sqlite3DbFree(db, zSql); } - /* Load the statistics from the sqlite_stat2 table. */ -#ifdef SQLITE_ENABLE_STAT2 - if( rc==SQLITE_OK && !sqlite3FindTable(db, "sqlite_stat2", sInfo.zDatabase) ){ - rc = SQLITE_ERROR; - } - if( rc==SQLITE_OK ){ - sqlite3_stmt *pStmt = 0; - - zSql = sqlite3MPrintf(db, - "SELECT idx,sampleno,sample FROM %Q.sqlite_stat2", sInfo.zDatabase); - if( !zSql ){ - rc = SQLITE_NOMEM; - }else{ - rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0); - sqlite3DbFree(db, zSql); - } - - if( rc==SQLITE_OK ){ - while( sqlite3_step(pStmt)==SQLITE_ROW ){ - char *zIndex; /* Index name */ - Index *pIdx; /* Pointer to the index object */ - - zIndex = (char *)sqlite3_column_text(pStmt, 0); - pIdx = zIndex ? sqlite3FindIndex(db, zIndex, sInfo.zDatabase) : 0; - if( pIdx ){ - int iSample = sqlite3_column_int(pStmt, 1); - if( iSample =0 ){ - int eType = sqlite3_column_type(pStmt, 2); - - if( pIdx->aSample==0 ){ - static const int sz = sizeof(IndexSample)*SQLITE_INDEX_SAMPLES; - pIdx->aSample = (IndexSample *)sqlite3DbMallocRaw(0, sz); - if( pIdx->aSample==0 ){ - db->mallocFailed = 1; - break; - } - memset(pIdx->aSample, 0, sz); - } - - assert( pIdx->aSample ); - { - IndexSample *pSample = &pIdx->aSample[iSample]; - pSample->eType = (u8)eType; - if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ - pSample->u.r = sqlite3_column_double(pStmt, 2); - }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ - const char *z = (const char *)( - (eType==SQLITE_BLOB) ? - sqlite3_column_blob(pStmt, 2): - sqlite3_column_text(pStmt, 2) - ); - int n = sqlite3_column_bytes(pStmt, 2); - if( n>24 ){ - n = 24; - } - pSample->nByte = (u8)n; - if( n < 1){ - pSample->u.z = 0; - }else{ - pSample->u.z = sqlite3DbStrNDup(0, z, n); - if( pSample->u.z==0 ){ - db->mallocFailed = 1; - break; - } - } - } - } - } - } - } - rc = sqlite3_finalize(pStmt); - } + /* Load the statistics from the sqlite_stat3 table. */ +#ifdef SQLITE_ENABLE_STAT3 + if( rc==SQLITE_OK ){ + rc = loadStat3(db, sInfo.zDatabase); } #endif if( rc==SQLITE_NOMEM ){ db->mallocFailed = 1; @@ -81120,11 +81397,15 @@ Parse *pParse, /* The parsing context */ int iDb, /* The database number */ const char *zType, /* "idx" or "tbl" */ const char *zName /* Name of index or table */ ){ - static const char *azStatTab[] = { "sqlite_stat1", "sqlite_stat2" }; + static const char *azStatTab[] = { + "sqlite_stat1", + "sqlite_stat2", + "sqlite_stat3", + }; int i; const char *zDbName = pParse->db->aDb[iDb].zName; for(i=0; i db, azStatTab[i], zDbName) ){ sqlite3NestedParse(pParse, @@ -81132,10 +81413,80 @@ zDbName, azStatTab[i], zType, zName ); } } } + +/* +** Generate code to drop a table. +*/ +SQLITE_PRIVATE void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, int isView){ + Vdbe *v; + sqlite3 *db = pParse->db; + Trigger *pTrigger; + Db *pDb = &db->aDb[iDb]; + + v = sqlite3GetVdbe(pParse); + assert( v!=0 ); + sqlite3BeginWriteOperation(pParse, 1, iDb); + +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( IsVirtual(pTab) ){ + sqlite3VdbeAddOp0(v, OP_VBegin); + } +#endif + + /* Drop all triggers associated with the table being dropped. Code + ** is generated to remove entries from sqlite_master and/or + ** sqlite_temp_master if required. + */ + pTrigger = sqlite3TriggerList(pParse, pTab); + while( pTrigger ){ + assert( pTrigger->pSchema==pTab->pSchema || + pTrigger->pSchema==db->aDb[1].pSchema ); + sqlite3DropTriggerPtr(pParse, pTrigger); + pTrigger = pTrigger->pNext; + } + +#ifndef SQLITE_OMIT_AUTOINCREMENT + /* Remove any entries of the sqlite_sequence table associated with + ** the table being dropped. This is done before the table is dropped + ** at the btree level, in case the sqlite_sequence table needs to + ** move as a result of the drop (can happen in auto-vacuum mode). + */ + if( pTab->tabFlags & TF_Autoincrement ){ + sqlite3NestedParse(pParse, + "DELETE FROM %Q.sqlite_sequence WHERE name=%Q", + pDb->zName, pTab->zName + ); + } +#endif + + /* Drop all SQLITE_MASTER table and index entries that refer to the + ** table. The program name loops through the master table and deletes + ** every row that refers to a table of the same name as the one being + ** dropped. Triggers are handled seperately because a trigger can be + ** created in the temp database that refers to a table in another + ** database. + */ + sqlite3NestedParse(pParse, + "DELETE FROM %Q.%s WHERE tbl_name=%Q and type!='trigger'", + pDb->zName, SCHEMA_TABLE(iDb), pTab->zName); + if( !isView && !IsVirtual(pTab) ){ + destroyTable(pParse, pTab); + } + + /* Remove the table entry from SQLite's internal schema and modify + ** the schema cookie. + */ + if( IsVirtual(pTab) ){ + sqlite3VdbeAddOp4(v, OP_VDestroy, iDb, 0, 0, pTab->zName, 0); + } + sqlite3VdbeAddOp4(v, OP_DropTable, iDb, 0, 0, pTab->zName, 0); + sqlite3ChangeCookie(pParse, iDb); + sqliteViewResetAll(db, iDb); +} /* ** This routine is called to do the work of a DROP TABLE statement. ** pName is the name of the table to be dropped. */ @@ -81201,11 +81552,12 @@ if( sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, zDb) ){ goto exit_drop_table; } } #endif - if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){ + if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 + && sqlite3StrNICmp(pTab->zName, "sqlite_stat", 11)!=0 ){ sqlite3ErrorMsg(pParse, "table %s may not be dropped", pTab->zName); goto exit_drop_table; } #ifndef SQLITE_OMIT_VIEW @@ -81225,72 +81577,15 @@ /* Generate code to remove the table from the master table ** on disk. */ v = sqlite3GetVdbe(pParse); if( v ){ - Trigger *pTrigger; - Db *pDb = &db->aDb[iDb]; sqlite3BeginWriteOperation(pParse, 1, iDb); - -#ifndef SQLITE_OMIT_VIRTUALTABLE - if( IsVirtual(pTab) ){ - sqlite3VdbeAddOp0(v, OP_VBegin); - } -#endif + sqlite3ClearStatTables(pParse, iDb, "tbl", pTab->zName); sqlite3FkDropTable(pParse, pName, pTab); - - /* Drop all triggers associated with the table being dropped. Code - ** is generated to remove entries from sqlite_master and/or - ** sqlite_temp_master if required. - */ - pTrigger = sqlite3TriggerList(pParse, pTab); - while( pTrigger ){ - assert( pTrigger->pSchema==pTab->pSchema || - pTrigger->pSchema==db->aDb[1].pSchema ); - sqlite3DropTriggerPtr(pParse, pTrigger); - pTrigger = pTrigger->pNext; - } - -#ifndef SQLITE_OMIT_AUTOINCREMENT - /* Remove any entries of the sqlite_sequence table associated with - ** the table being dropped. This is done before the table is dropped - ** at the btree level, in case the sqlite_sequence table needs to - ** move as a result of the drop (can happen in auto-vacuum mode). - */ - if( pTab->tabFlags & TF_Autoincrement ){ - sqlite3NestedParse(pParse, - "DELETE FROM %s.sqlite_sequence WHERE name=%Q", - pDb->zName, pTab->zName - ); - } -#endif - - /* Drop all SQLITE_MASTER table and index entries that refer to the - ** table. The program name loops through the master table and deletes - ** every row that refers to a table of the same name as the one being - ** dropped. Triggers are handled seperately because a trigger can be - ** created in the temp database that refers to a table in another - ** database. - */ - sqlite3NestedParse(pParse, - "DELETE FROM %Q.%s WHERE tbl_name=%Q and type!='trigger'", - pDb->zName, SCHEMA_TABLE(iDb), pTab->zName); - sqlite3ClearStatTables(pParse, iDb, "tbl", pTab->zName); - if( !isView && !IsVirtual(pTab) ){ - destroyTable(pParse, pTab); - } - - /* Remove the table entry from SQLite's internal schema and modify - ** the schema cookie. - */ - if( IsVirtual(pTab) ){ - sqlite3VdbeAddOp4(v, OP_VDestroy, iDb, 0, 0, pTab->zName, 0); - } - sqlite3VdbeAddOp4(v, OP_DropTable, iDb, 0, 0, pTab->zName, 0); - sqlite3ChangeCookie(pParse, iDb); - } - sqliteViewResetAll(db, iDb); + sqlite3CodeDropTable(pParse, pTab, iDb, isView); + } exit_drop_table: sqlite3SrcListDelete(db, pName); } @@ -81454,17 +81749,19 @@ */ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ Table *pTab = pIndex->pTable; /* The table that is indexed */ int iTab = pParse->nTab++; /* Btree cursor used for pTab */ int iIdx = pParse->nTab++; /* Btree cursor used for pIndex */ - int iSorter = iTab; /* Cursor opened by OpenSorter (if in use) */ + int iSorter; /* Cursor opened by OpenSorter (if in use) */ int addr1; /* Address of top of loop */ int addr2; /* Address to jump to for next iteration */ int tnum; /* Root page of index */ Vdbe *v; /* Generate code into this virtual machine */ KeyInfo *pKey; /* KeyInfo for index */ +#ifdef SQLITE_OMIT_MERGE_SORT int regIdxKey; /* Registers containing the index key */ +#endif int regRecord; /* Register holding assemblied index record */ sqlite3 *db = pParse->db; /* The database connection */ int iDb = sqlite3SchemaToIndex(db, pIndex->pSchema); #ifndef SQLITE_OMIT_AUTHORIZATION @@ -81494,21 +81791,22 @@ #ifndef SQLITE_OMIT_MERGE_SORT /* Open the sorter cursor if we are to use one. */ iSorter = pParse->nTab++; sqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0, 0, (char*)pKey, P4_KEYINFO); +#else + iSorter = iTab; #endif /* Open the table. Loop through all rows of the table, inserting index ** records into the sorter. */ sqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead); addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iTab, 0); - addr2 = addr1 + 1; regRecord = sqlite3GetTempReg(pParse); - regIdxKey = sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord, 1); #ifndef SQLITE_OMIT_MERGE_SORT + sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord, 1); sqlite3VdbeAddOp2(v, OP_SorterInsert, iSorter, regRecord); sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1+1); sqlite3VdbeJumpHere(v, addr1); addr1 = sqlite3VdbeAddOp2(v, OP_SorterSort, iSorter, 0); if( pIndex->onError!=OE_None ){ @@ -81524,10 +81822,12 @@ } sqlite3VdbeAddOp2(v, OP_SorterData, iSorter, regRecord); sqlite3VdbeAddOp3(v, OP_IdxInsert, iIdx, regRecord, 1); sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); #else + regIdxKey = sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord, 1); + addr2 = addr1 + 1; if( pIndex->onError!=OE_None ){ const int regRowid = regIdxKey + pIndex->nColumn; const int j2 = sqlite3VdbeCurrentAddr(v) + 2; void * const pRegKey = SQLITE_INT_TO_PTR(regIdxKey); @@ -81621,10 +81921,11 @@ ** before looking up the table. */ assert( pName1 && pName2 ); iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName); if( iDb<0 ) goto exit_create_index; + assert( pName && pName->z ); #ifndef SQLITE_OMIT_TEMPDB /* If the index name was unqualified, check if the the table ** is a temp table. If so, set the database to 1. Do not do this ** if initialising a database schema. @@ -81648,10 +81949,11 @@ pTblName->a[0].zDatabase); if( !pTab || db->mallocFailed ) goto exit_create_index; assert( db->aDb[iDb].pSchema==pTab->pSchema ); }else{ assert( pName==0 ); + assert( pStart==0 ); pTab = pParse->pNewTable; if( !pTab ) goto exit_create_index; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); } pDb = &db->aDb[iDb]; @@ -81690,10 +81992,11 @@ ** own name. */ if( pName ){ zName = sqlite3NameFromToken(db, pName); if( zName==0 ) goto exit_create_index; + assert( pName->z!=0 ); if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){ goto exit_create_index; } if( !db->init.busy ){ if( sqlite3FindTable(db, zName, 0)!=0 ){ @@ -81769,24 +82072,24 @@ */ nName = sqlite3Strlen30(zName); nCol = pList->nExpr; pIndex = sqlite3DbMallocZero(db, sizeof(Index) + /* Index structure */ + sizeof(tRowcnt)*(nCol+1) + /* Index.aiRowEst */ sizeof(int)*nCol + /* Index.aiColumn */ - sizeof(int)*(nCol+1) + /* Index.aiRowEst */ sizeof(char *)*nCol + /* Index.azColl */ sizeof(u8)*nCol + /* Index.aSortOrder */ nName + 1 + /* Index.zName */ nExtra /* Collation sequence names */ ); if( db->mallocFailed ){ goto exit_create_index; } - pIndex->azColl = (char**)(&pIndex[1]); + pIndex->aiRowEst = (tRowcnt*)(&pIndex[1]); + pIndex->azColl = (char**)(&pIndex->aiRowEst[nCol+1]); pIndex->aiColumn = (int *)(&pIndex->azColl[nCol]); - pIndex->aiRowEst = (unsigned *)(&pIndex->aiColumn[nCol]); - pIndex->aSortOrder = (u8 *)(&pIndex->aiRowEst[nCol+1]); + pIndex->aSortOrder = (u8 *)(&pIndex->aiColumn[nCol]); pIndex->zName = (char *)(&pIndex->aSortOrder[nCol]); zExtra = (char *)(&pIndex->zName[nName+1]); memcpy(pIndex->zName, zName, nName+1); pIndex->pTable = pTab; pIndex->nColumn = pList->nExpr; @@ -82059,13 +82362,13 @@ ** Apart from that, we have little to go on besides intuition as to ** how aiRowEst[] should be initialized. The numbers generated here ** are based on typical values found in actual indices. */ SQLITE_PRIVATE void sqlite3DefaultRowEst(Index *pIdx){ - unsigned *a = pIdx->aiRowEst; + tRowcnt *a = pIdx->aiRowEst; int i; - unsigned n; + tRowcnt n; assert( a!=0 ); a[0] = pIdx->pTable->nRowEst; if( a[0]<10 ) a[0] = 10; n = 10; for(i=1; i<=pIdx->nColumn; i++){ @@ -82545,17 +82848,14 @@ /* ** Commit a transaction */ SQLITE_PRIVATE void sqlite3CommitTransaction(Parse *pParse){ - sqlite3 *db; Vdbe *v; assert( pParse!=0 ); - db = pParse->db; - assert( db!=0 ); -/* if( db->aDb[0].pBt==0 ) return; */ + assert( pParse->db!=0 ); if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "COMMIT", 0, 0) ){ return; } v = sqlite3GetVdbe(pParse); if( v ){ @@ -82565,17 +82865,14 @@ /* ** Rollback a transaction */ SQLITE_PRIVATE void sqlite3RollbackTransaction(Parse *pParse){ - sqlite3 *db; Vdbe *v; assert( pParse!=0 ); - db = pParse->db; - assert( db!=0 ); -/* if( db->aDb[0].pBt==0 ) return; */ + assert( pParse->db!=0 ); if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "ROLLBACK", 0, 0) ){ return; } v = sqlite3GetVdbe(pParse); if( v ){ @@ -84377,20 +84674,19 @@ /* Verify that the call to _bytes() does not invalidate the _text() pointer */ assert( z2==(char*)sqlite3_value_text(argv[0]) ); if( z2 ){ z1 = contextMalloc(context, ((i64)n)+1); if( z1 ){ - memcpy(z1, z2, n+1); - for(i=0; z1[i]; i++){ - z1[i] = (char)sqlite3Toupper(z1[i]); + for(i=0; i mallocFailed==1 ){ fkTriggerDelete(db, pTrigger); return 0; } + assert( pStep!=0 ); switch( action ){ case OE_Restrict: pStep->op = TK_SELECT; break; @@ -88621,10 +88917,13 @@ */ if( (pParse->db->flags & SQLITE_ForeignKeys)!=0 && pDest->pFKey!=0 ){ return 0; } #endif + if( (pParse->db->flags & SQLITE_CountRows)!=0 ){ + return 0; + } /* If we get this far, it means either: ** ** * We can always do the transfer if the table contains an ** an integer primary key @@ -89698,11 +89997,11 @@ sqlite3_vfs *pVfs = db->pVfs; void *handle; int (*xInit)(sqlite3*,char**,const sqlite3_api_routines*); char *zErrmsg = 0; void **aHandle; - const int nMsg = 300; + int nMsg = 300 + sqlite3Strlen30(zFile); if( pzErrMsg ) *pzErrMsg = 0; /* Ticket #1863. To avoid a creating security problems for older ** applications that relink against newer versions of SQLite, the @@ -89735,10 +90034,11 @@ } xInit = (int(*)(sqlite3*,char**,const sqlite3_api_routines*)) sqlite3OsDlSym(pVfs, handle, zProc); if( xInit==0 ){ if( pzErrMsg ){ + nMsg += sqlite3Strlen30(zProc); *pzErrMsg = zErrmsg = sqlite3_malloc(nMsg); if( zErrmsg ){ sqlite3_snprintf(nMsg, zErrmsg, "no entry point [%s] in shared library [%s]", zProc,zFile); sqlite3OsDlError(pVfs, nMsg-1, zErrmsg); @@ -90420,11 +90720,11 @@ ){ int iReg; if( sqlite3ReadSchema(pParse) ) goto pragma_out; sqlite3CodeVerifySchema(pParse, iDb); iReg = ++pParse->nMem; - if( zLeft[0]=='p' ){ + if( sqlite3Tolower(zLeft[0])=='p' ){ sqlite3VdbeAddOp2(v, OP_Pagecount, iDb, iReg); }else{ sqlite3VdbeAddOp3(v, OP_MaxPgcnt, iDb, iReg, sqlite3Atoi(zRight)); } sqlite3VdbeAddOp2(v, OP_ResultRow, iReg, 1); @@ -90486,12 +90786,14 @@ */ if( sqlite3StrICmp(zLeft,"journal_mode")==0 ){ int eMode; /* One of the PAGER_JOURNALMODE_XXX symbols */ int ii; /* Loop counter */ - /* Force the schema to be loaded on all databases. This cases all - ** database files to be opened and the journal_modes set. */ + /* Force the schema to be loaded on all databases. This causes all + ** database files to be opened and the journal_modes set. This is + ** necessary because subsequent processing must know if the databases + ** are in WAL mode. */ if( sqlite3ReadSchema(pParse) ){ goto pragma_out; } sqlite3VdbeSetNumCols(v, 1); @@ -91031,11 +91333,11 @@ { OP_IfNeg, 1, 0, 0}, /* 1 */ { OP_String8, 0, 3, 0}, /* 2 */ { OP_ResultRow, 3, 1, 0}, }; - int isQuick = (zLeft[0]=='q'); + int isQuick = (sqlite3Tolower(zLeft[0])=='q'); /* Initialize the VDBE program */ if( sqlite3ReadSchema(pParse) ) goto pragma_out; pParse->nMem = 6; sqlite3VdbeSetNumCols(v, 1); @@ -92406,10 +92708,11 @@ Select standin; sqlite3 *db = pParse->db; pNew = sqlite3DbMallocZero(db, sizeof(*pNew) ); assert( db->mallocFailed || !pOffset || pLimit ); /* OFFSET implies LIMIT */ if( pNew==0 ){ + assert( db->mallocFailed ); pNew = &standin; memset(pNew, 0, sizeof(*pNew)); } if( pEList==0 ){ pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db,TK_ALL,0)); @@ -92433,10 +92736,11 @@ if( pNew!=&standin ) sqlite3DbFree(db, pNew); pNew = 0; }else{ assert( pNew->pSrc!=0 || pParse->nErr>0 ); } + assert( pNew!=&standin ); return pNew; } /* ** Delete the given Select structure and all of its substructures. @@ -93611,11 +93915,14 @@ /* If the column contains an "AS " phrase, use as the name */ zName = sqlite3DbStrDup(db, zName); }else{ Expr *pColExpr = p; /* The expression that is the result column name */ Table *pTab; /* Table associated with this expression */ - while( pColExpr->op==TK_DOT ) pColExpr = pColExpr->pRight; + while( pColExpr->op==TK_DOT ){ + pColExpr = pColExpr->pRight; + assert( pColExpr!=0 ); + } if( pColExpr->op==TK_COLUMN && ALWAYS(pColExpr->pTab!=0) ){ /* For columns use the column name name */ int iCol = pColExpr->iColumn; pTab = pColExpr->pTab; if( iCol<0 ) iCol = pTab->iPKey; @@ -98609,10 +98916,11 @@ break; } } } for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ + assert( aRegIdx ); if( openAll || aRegIdx[i]>0 ){ KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx); sqlite3VdbeAddOp4(v, OP_OpenWrite, iCur+i+1, pIdx->tnum, iDb, (char*)pKey, P4_KEYINFO_HANDOFF); assert( pParse->nTab>iCur+i+1 ); @@ -98782,10 +99090,11 @@ sqlite3VdbeAddOp2(v, OP_Goto, 0, addr); sqlite3VdbeJumpHere(v, addr); /* Close all tables */ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ + assert( aRegIdx ); if( openAll || aRegIdx[i]>0 ){ sqlite3VdbeAddOp2(v, OP_Close, iCur+i+1, 0); } } sqlite3VdbeAddOp2(v, OP_Close, iCur, 0); @@ -98969,11 +99278,11 @@ if( SQLITE_OK!=sqlite3_prepare(db, zSql, -1, &pStmt, 0) ){ sqlite3SetString(pzErrMsg, db, sqlite3_errmsg(db)); return sqlite3_errcode(db); } VVA_ONLY( rc = ) sqlite3_step(pStmt); - assert( rc!=SQLITE_ROW ); + assert( rc!=SQLITE_ROW || (db->flags&SQLITE_CountRows) ); return vacuumFinalize(db, pStmt, pzErrMsg); } /* ** Execute zSql on database db. The statement returns exactly @@ -99187,17 +99496,15 @@ " WHERE type='view' OR type='trigger'" " OR (type='table' AND rootpage=0)" ); if( rc ) goto end_of_vacuum; - /* At this point, unless the main db was completely empty, there is now a - ** transaction open on the vacuum database, but not on the main database. - ** Open a btree level transaction on the main database. This allows a - ** call to sqlite3BtreeCopyFile(). The main database btree level - ** transaction is then committed, so the SQL level never knows it was - ** opened for writing. This way, the SQL transaction used to create the - ** temporary database never needs to be committed. + /* At this point, there is a write transaction open on both the + ** vacuum database and the main database. Assuming no error occurs, + ** both transactions are closed by this block - the main database + ** transaction by sqlite3BtreeCopyFile() and the other by an explicit + ** call to sqlite3BtreeCommit(). */ { u32 meta; int i; @@ -100457,25 +100764,35 @@ #define TERM_CODED 0x04 /* This term is already coded */ #define TERM_COPIED 0x08 /* Has a child */ #define TERM_ORINFO 0x10 /* Need to free the WhereTerm.u.pOrInfo object */ #define TERM_ANDINFO 0x20 /* Need to free the WhereTerm.u.pAndInfo obj */ #define TERM_OR_OK 0x40 /* Used during OR-clause processing */ -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 # define TERM_VNULL 0x80 /* Manufactured x>NULL or x<=NULL term */ #else -# define TERM_VNULL 0x00 /* Disabled if not using stat2 */ +# define TERM_VNULL 0x00 /* Disabled if not using stat3 */ #endif /* ** An instance of the following structure holds all information about a ** WHERE clause. Mostly this is a container for one or more WhereTerms. +** +** Explanation of pOuter: For a WHERE clause of the form +** +** a AND ((b AND c) OR (d AND e)) AND f +** +** There are separate WhereClause objects for the whole clause and for +** the subclauses "(b AND c)" and "(d AND e)". The pOuter field of the +** subclauses points to the WhereClause object for the whole clause. */ struct WhereClause { Parse *pParse; /* The parser context */ WhereMaskSet *pMaskSet; /* Mapping of table cursor numbers to bitmasks */ Bitmask vmask; /* Bitmask identifying virtual table cursors */ + WhereClause *pOuter; /* Outer conjunction */ u8 op; /* Split operator. TK_AND or TK_OR */ + u16 wctrlFlags; /* Might include WHERE_AND_ONLY */ int nTerm; /* Number of terms */ int nSlot; /* Number of entries in a[] */ WhereTerm *a; /* Each a[] describes a term of the WHERE cluase */ #if defined(SQLITE_SMALL_STACK) WhereTerm aStatic[1]; /* Initial static space for a[] */ @@ -100600,18 +100917,21 @@ ** Initialize a preallocated WhereClause structure. */ static void whereClauseInit( WhereClause *pWC, /* The WhereClause to be initialized */ Parse *pParse, /* The parsing context */ - WhereMaskSet *pMaskSet /* Mapping from table cursor numbers to bitmasks */ + WhereMaskSet *pMaskSet, /* Mapping from table cursor numbers to bitmasks */ + u16 wctrlFlags /* Might include WHERE_AND_ONLY */ ){ pWC->pParse = pParse; pWC->pMaskSet = pMaskSet; + pWC->pOuter = 0; pWC->nTerm = 0; pWC->nSlot = ArraySize(pWC->aStatic); pWC->a = pWC->aStatic; pWC->vmask = 0; + pWC->wctrlFlags = wctrlFlags; } /* Forward reference */ static void whereClauseClear(WhereClause*); @@ -100923,40 +101243,42 @@ ){ WhereTerm *pTerm; int k; assert( iCur>=0 ); op &= WO_ALL; - for(pTerm=pWC->a, k=pWC->nTerm; k; k--, pTerm++){ - if( pTerm->leftCursor==iCur - && (pTerm->prereqRight & notReady)==0 - && pTerm->u.leftColumn==iColumn - && (pTerm->eOperator & op)!=0 - ){ - if( pIdx && pTerm->eOperator!=WO_ISNULL ){ - Expr *pX = pTerm->pExpr; - CollSeq *pColl; - char idxaff; - int j; - Parse *pParse = pWC->pParse; - - idxaff = pIdx->pTable->aCol[iColumn].affinity; - if( !sqlite3IndexAffinityOk(pX, idxaff) ) continue; - - /* Figure out the collation sequence required from an index for - ** it to be useful for optimising expression pX. Store this - ** value in variable pColl. - */ - assert(pX->pLeft); - pColl = sqlite3BinaryCompareCollSeq(pParse, pX->pLeft, pX->pRight); - assert(pColl || pParse->nErr); - - for(j=0; pIdx->aiColumn[j]!=iColumn; j++){ - if( NEVER(j>=pIdx->nColumn) ) return 0; - } - if( pColl && sqlite3StrICmp(pColl->zName, pIdx->azColl[j]) ) continue; - } - return pTerm; + for(; pWC; pWC=pWC->pOuter){ + for(pTerm=pWC->a, k=pWC->nTerm; k; k--, pTerm++){ + if( pTerm->leftCursor==iCur + && (pTerm->prereqRight & notReady)==0 + && pTerm->u.leftColumn==iColumn + && (pTerm->eOperator & op)!=0 + ){ + if( pIdx && pTerm->eOperator!=WO_ISNULL ){ + Expr *pX = pTerm->pExpr; + CollSeq *pColl; + char idxaff; + int j; + Parse *pParse = pWC->pParse; + + idxaff = pIdx->pTable->aCol[iColumn].affinity; + if( !sqlite3IndexAffinityOk(pX, idxaff) ) continue; + + /* Figure out the collation sequence required from an index for + ** it to be useful for optimising expression pX. Store this + ** value in variable pColl. + */ + assert(pX->pLeft); + pColl = sqlite3BinaryCompareCollSeq(pParse, pX->pLeft, pX->pRight); + assert(pColl || pParse->nErr); + + for(j=0; pIdx->aiColumn[j]!=iColumn; j++){ + if( NEVER(j>=pIdx->nColumn) ) return 0; + } + if( pColl && sqlite3StrICmp(pColl->zName, pIdx->azColl[j]) ) continue; + } + return pTerm; + } } } return 0; } @@ -101029,11 +101351,11 @@ int iCol = pRight->iColumn; pVal = sqlite3VdbeGetValue(pReprepare, iCol, SQLITE_AFF_NONE); if( pVal && sqlite3_value_type(pVal)==SQLITE_TEXT ){ z = (char *)sqlite3_value_text(pVal); } - sqlite3VdbeSetVarmask(pParse->pVdbe, iCol); /* IMP: R-23257-02778 */ + sqlite3VdbeSetVarmask(pParse->pVdbe, iCol); /* IMP: R-31526-56213 */ assert( pRight->op==TK_VARIABLE || pRight->op==TK_REGISTER ); }else if( op==TK_STRING ){ z = pRight->u.zToken; } if( z ){ @@ -101047,11 +101369,11 @@ pPrefix = sqlite3Expr(db, TK_STRING, z); if( pPrefix ) pPrefix->u.zToken[cnt] = 0; *ppPrefix = pPrefix; if( op==TK_VARIABLE ){ Vdbe *v = pParse->pVdbe; - sqlite3VdbeSetVarmask(v, pRight->iColumn); /* IMP: R-23257-02778 */ + sqlite3VdbeSetVarmask(v, pRight->iColumn); /* IMP: R-31526-56213 */ if( *pisComplete && pRight->u.zToken[1] ){ /* If the rhs of the LIKE expression is a variable, and the current ** value of the variable means there is no need to invoke the LIKE ** function, then no OP_Variable will be added to the program. ** This causes problems for the sqlite3_bind_parameter_name() @@ -101216,11 +101538,11 @@ assert( pExpr->op==TK_OR ); pTerm->u.pOrInfo = pOrInfo = sqlite3DbMallocZero(db, sizeof(*pOrInfo)); if( pOrInfo==0 ) return; pTerm->wtFlags |= TERM_ORINFO; pOrWc = &pOrInfo->wc; - whereClauseInit(pOrWc, pWC->pParse, pMaskSet); + whereClauseInit(pOrWc, pWC->pParse, pMaskSet, pWC->wctrlFlags); whereSplit(pOrWc, pExpr, TK_OR); exprAnalyzeAll(pSrc, pOrWc); if( db->mallocFailed ) return; assert( pOrWc->nTerm>=2 ); @@ -101243,13 +101565,14 @@ Bitmask b = 0; pOrTerm->u.pAndInfo = pAndInfo; pOrTerm->wtFlags |= TERM_ANDINFO; pOrTerm->eOperator = WO_AND; pAndWC = &pAndInfo->wc; - whereClauseInit(pAndWC, pWC->pParse, pMaskSet); + whereClauseInit(pAndWC, pWC->pParse, pMaskSet, pWC->wctrlFlags); whereSplit(pAndWC, pOrTerm->pExpr, TK_AND); exprAnalyzeAll(pSrc, pAndWC); + pAndWC->pOuter = pWC; testcase( db->mallocFailed ); if( !db->mallocFailed ){ for(j=0, pAndTerm=pAndWC->a; j nTerm; j++, pAndTerm++){ assert( pAndTerm->pExpr ); if( allowedOp(pAndTerm->pExpr->op) ){ @@ -101679,12 +102002,12 @@ pNewTerm->prereqAll = pTerm->prereqAll; } } #endif /* SQLITE_OMIT_VIRTUALTABLE */ -#ifdef SQLITE_ENABLE_STAT2 - /* When sqlite_stat2 histogram data is available an operator of the +#ifdef SQLITE_ENABLE_STAT3 + /* When sqlite_stat3 histogram data is available an operator of the ** form "x IS NOT NULL" can sometimes be evaluated more efficiently ** as "x>NULL" if x is not an INTEGER PRIMARY KEY. So construct a ** virtual term of that form. ** ** Note that the virtual term must be tagged with TERM_VNULL. This @@ -101718,11 +102041,11 @@ pTerm->nChild = 1; pTerm->wtFlags |= TERM_COPIED; pNewTerm->prereqAll = pTerm->prereqAll; } } -#endif /* SQLITE_ENABLE_STAT2 */ +#endif /* SQLITE_ENABLE_STAT */ /* Prevent ON clause terms of a LEFT JOIN from being used to drive ** an index for tables to the left of the join. */ pTerm->prereqRight |= extraRight; @@ -102140,14 +102463,17 @@ const int iCur = pSrc->iCursor; /* The cursor of the table to be accessed */ const Bitmask maskSrc = getMask(pWC->pMaskSet, iCur); /* Bitmask for pSrc */ WhereTerm * const pWCEnd = &pWC->a[pWC->nTerm]; /* End of pWC->a[] */ WhereTerm *pTerm; /* A single term of the WHERE clause */ - /* No OR-clause optimization allowed if the INDEXED BY or NOT INDEXED clauses - ** are used */ + /* The OR-clause optimization is disallowed if the INDEXED BY or + ** NOT INDEXED clauses are used or if the WHERE_AND_ONLY bit is set. */ if( pSrc->notIndexed || pSrc->pIndex!=0 ){ return; + } + if( pWC->wctrlFlags & WHERE_AND_ONLY ){ + return; } /* Search the WHERE clause terms for a usable WO_OR term. */ for(pTerm=pWC->a; pTerm eOperator==WO_OR @@ -102172,10 +102498,11 @@ bestIndex(pParse, pAndWC, pSrc, notReady, notValid, 0, &sTermCost); }else if( pOrTerm->leftCursor==iCur ){ WhereClause tempWC; tempWC.pParse = pWC->pParse; tempWC.pMaskSet = pWC->pMaskSet; + tempWC.pOuter = pWC; tempWC.op = TK_AND; tempWC.a = pOrTerm; tempWC.nTerm = 1; bestIndex(pParse, &tempWC, pSrc, notReady, notValid, 0, &sTermCost); }else{ @@ -102766,71 +103093,89 @@ */ bestOrClauseIndex(pParse, pWC, pSrc, notReady, notValid, pOrderBy, pCost); } #endif /* SQLITE_OMIT_VIRTUALTABLE */ +#ifdef SQLITE_ENABLE_STAT3 /* -** Argument pIdx is a pointer to an index structure that has an array of -** SQLITE_INDEX_SAMPLES evenly spaced samples of the first indexed column -** stored in Index.aSample. These samples divide the domain of values stored -** the index into (SQLITE_INDEX_SAMPLES+1) regions. -** Region 0 contains all values less than the first sample value. Region -** 1 contains values between the first and second samples. Region 2 contains -** values between samples 2 and 3. And so on. Region SQLITE_INDEX_SAMPLES -** contains values larger than the last sample. -** -** If the index contains many duplicates of a single value, then it is -** possible that two or more adjacent samples can hold the same value. -** When that is the case, the smallest possible region code is returned -** when roundUp is false and the largest possible region code is returned -** when roundUp is true. -** -** If successful, this function determines which of the regions value -** pVal lies in, sets *piRegion to the region index (a value between 0 -** and SQLITE_INDEX_SAMPLES+1, inclusive) and returns SQLITE_OK. -** Or, if an OOM occurs while converting text values between encodings, -** SQLITE_NOMEM is returned and *piRegion is undefined. -*/ -#ifdef SQLITE_ENABLE_STAT2 -static int whereRangeRegion( +** Estimate the location of a particular key among all keys in an +** index. Store the results in aStat as follows: +** +** aStat[0] Est. number of rows less than pVal +** aStat[1] Est. number of rows equal to pVal +** +** Return SQLITE_OK on success. +*/ +static int whereKeyStats( Parse *pParse, /* Database connection */ Index *pIdx, /* Index to consider domain of */ sqlite3_value *pVal, /* Value to consider */ - int roundUp, /* Return largest valid region if true */ - int *piRegion /* OUT: Region of domain in which value lies */ + int roundUp, /* Round up if true. Round down if false */ + tRowcnt *aStat /* OUT: stats written here */ ){ + tRowcnt n; + IndexSample *aSample; + int i, eType; + int isEq = 0; + i64 v; + double r, rS; + assert( roundUp==0 || roundUp==1 ); - if( ALWAYS(pVal) ){ - IndexSample *aSample = pIdx->aSample; - int i = 0; - int eType = sqlite3_value_type(pVal); - - if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ - double r = sqlite3_value_double(pVal); - for(i=0; i =SQLITE_TEXT ) break; - if( roundUp ){ - if( aSample[i].u.r>r ) break; - }else{ - if( aSample[i].u.r>=r ) break; - } - } - }else if( eType==SQLITE_NULL ){ - i = 0; - if( roundUp ){ - while( i nSample>0 ); + if( pVal==0 ) return SQLITE_ERROR; + n = pIdx->aiRowEst[0]; + aSample = pIdx->aSample; + eType = sqlite3_value_type(pVal); + + if( eType==SQLITE_INTEGER ){ + v = sqlite3_value_int64(pVal); + r = (i64)v; + for(i=0; i nSample; i++){ + if( aSample[i].eType==SQLITE_NULL ) continue; + if( aSample[i].eType>=SQLITE_TEXT ) break; + if( aSample[i].eType==SQLITE_INTEGER ){ + if( aSample[i].u.i>=v ){ + isEq = aSample[i].u.i==v; + break; + } + }else{ + assert( aSample[i].eType==SQLITE_FLOAT ); + if( aSample[i].u.r>=r ){ + isEq = aSample[i].u.r==r; + break; + } + } + } + }else if( eType==SQLITE_FLOAT ){ + r = sqlite3_value_double(pVal); + for(i=0; i nSample; i++){ + if( aSample[i].eType==SQLITE_NULL ) continue; + if( aSample[i].eType>=SQLITE_TEXT ) break; + if( aSample[i].eType==SQLITE_FLOAT ){ + rS = aSample[i].u.r; + }else{ + rS = aSample[i].u.i; + } + if( rS>=r ){ + isEq = rS==r; + break; + } + } + }else if( eType==SQLITE_NULL ){ + i = 0; + if( aSample[0].eType==SQLITE_NULL ) isEq = 1; + }else{ + assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); + for(i=0; i nSample; i++){ + if( aSample[i].eType==SQLITE_TEXT || aSample[i].eType==SQLITE_BLOB ){ + break; + } + } + if( i nSample ){ sqlite3 *db = pParse->db; CollSeq *pColl; const u8 *z; - int n; - - /* pVal comes from sqlite3ValueFromExpr() so the type cannot be NULL */ - assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); - if( eType==SQLITE_BLOB ){ z = (const u8 *)sqlite3_value_blob(pVal); pColl = db->pDfltColl; assert( pColl->enc==SQLITE_UTF8 ); }else{ @@ -102845,16 +103190,16 @@ return SQLITE_NOMEM; } assert( z && pColl && pColl->xCmp ); } n = sqlite3ValueBytes(pVal, pColl->enc); - - for(i=0; i nSample; i++){ int c; int eSampletype = aSample[i].eType; - if( eSampletype==SQLITE_NULL || eSampletype enc!=SQLITE_UTF8 ){ int nSample; char *zSample = sqlite3Utf8to16( db, pColl->enc, aSample[i].u.z, aSample[i].nByte, &nSample @@ -102868,20 +103213,51 @@ }else #endif { c = pColl->xCmp(pColl->pUser, aSample[i].nByte, aSample[i].u.z, n, z); } - if( c-roundUp>=0 ) break; + if( c>=0 ){ + if( c==0 ) isEq = 1; + break; + } } } + } - assert( i>=0 && i<=SQLITE_INDEX_SAMPLES ); - *piRegion = i; + /* At this point, aSample[i] is the first sample that is greater than + ** or equal to pVal. Or if i==pIdx->nSample, then all samples are less + ** than pVal. If aSample[i]==pVal, then isEq==1. + */ + if( isEq ){ + assert( i nSample ); + aStat[0] = aSample[i].nLt; + aStat[1] = aSample[i].nEq; + }else{ + tRowcnt iLower, iUpper, iGap; + if( i==0 ){ + iLower = 0; + iUpper = aSample[0].nLt; + }else{ + iUpper = i>=pIdx->nSample ? n : aSample[i].nLt; + iLower = aSample[i-1].nEq + aSample[i-1].nLt; + } + aStat[1] = pIdx->avgEq; + if( iLower>=iUpper ){ + iGap = 0; + }else{ + iGap = iUpper - iLower; + } + if( roundUp ){ + iGap = (iGap*2)/3; + }else{ + iGap = iGap/3; + } + aStat[0] = iLower + iGap; } return SQLITE_OK; } -#endif /* #ifdef SQLITE_ENABLE_STAT2 */ +#endif /* SQLITE_ENABLE_STAT3 */ /* ** If expression pExpr represents a literal value, set *pp to point to ** an sqlite3_value structure containing the same value, with affinity ** aff applied to it, before returning. It is the responsibility of the @@ -102895,11 +103271,11 @@ ** ** If neither of the above apply, set *pp to NULL. ** ** If an error occurs, return an error code. Otherwise, SQLITE_OK. */ -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 static int valueFromExpr( Parse *pParse, Expr *pExpr, u8 aff, sqlite3_value **pp @@ -102906,11 +103282,11 @@ ){ if( pExpr->op==TK_VARIABLE || (pExpr->op==TK_REGISTER && pExpr->op2==TK_VARIABLE) ){ int iVar = pExpr->iColumn; - sqlite3VdbeSetVarmask(pParse->pVdbe, iVar); /* IMP: R-23257-02778 */ + sqlite3VdbeSetVarmask(pParse->pVdbe, iVar); /* IMP: R-31526-56213 */ *pp = sqlite3VdbeGetValue(pParse->pReprepare, iVar, aff); return SQLITE_OK; } return sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, aff, pp); } @@ -102943,106 +103319,92 @@ ** ** ... FROM t1 WHERE a > ? AND a < ? ... ** ** then nEq should be passed 0. ** -** The returned value is an integer between 1 and 100, inclusive. A return -** value of 1 indicates that the proposed range scan is expected to visit -** approximately 1/100th (1%) of the rows selected by the nEq equality -** constraints (if any). A return value of 100 indicates that it is expected -** that the range scan will visit every row (100%) selected by the equality -** constraints. +** The returned value is an integer divisor to reduce the estimated +** search space. A return value of 1 means that range constraints are +** no help at all. A return value of 2 means range constraints are +** expected to reduce the search space by half. And so forth... ** -** In the absence of sqlite_stat2 ANALYZE data, each range inequality -** reduces the search space by 3/4ths. Hence a single constraint (x>?) -** results in a return of 25 and a range constraint (x>? AND x) results -** in a return of 6. +** In the absence of sqlite_stat3 ANALYZE data, each range inequality +** reduces the search space by a factor of 4. Hence a single constraint (x>?) +** results in a return of 4 and a range constraint (x>? AND x) results +** in a return of 16. */ static int whereRangeScanEst( Parse *pParse, /* Parsing & code generating context */ Index *p, /* The index containing the range-compared column; "x" */ int nEq, /* index into p->aCol[] of the range-compared column */ WhereTerm *pLower, /* Lower bound on the range. ex: "x>123" Might be NULL */ WhereTerm *pUpper, /* Upper bound on the range. ex: "x<455" Might be NULL */ - int *piEst /* OUT: Return value */ + double *pRangeDiv /* OUT: Reduce search space by this divisor */ ){ int rc = SQLITE_OK; -#ifdef SQLITE_ENABLE_STAT2 - - if( nEq==0 && p->aSample ){ - sqlite3_value *pLowerVal = 0; - sqlite3_value *pUpperVal = 0; - int iEst; - int iLower = 0; - int iUpper = SQLITE_INDEX_SAMPLES; - int roundUpUpper = 0; - int roundUpLower = 0; +#ifdef SQLITE_ENABLE_STAT3 + + if( nEq==0 && p->nSample ){ + sqlite3_value *pRangeVal; + tRowcnt iLower = 0; + tRowcnt iUpper = p->aiRowEst[0]; + tRowcnt a[2]; u8 aff = p->pTable->aCol[p->aiColumn[0]].affinity; if( pLower ){ Expr *pExpr = pLower->pExpr->pRight; - rc = valueFromExpr(pParse, pExpr, aff, &pLowerVal); + rc = valueFromExpr(pParse, pExpr, aff, &pRangeVal); assert( pLower->eOperator==WO_GT || pLower->eOperator==WO_GE ); - roundUpLower = (pLower->eOperator==WO_GT) ?1:0; + if( rc==SQLITE_OK + && whereKeyStats(pParse, p, pRangeVal, 0, a)==SQLITE_OK + ){ + iLower = a[0]; + if( pLower->eOperator==WO_GT ) iLower += a[1]; + } + sqlite3ValueFree(pRangeVal); } if( rc==SQLITE_OK && pUpper ){ Expr *pExpr = pUpper->pExpr->pRight; - rc = valueFromExpr(pParse, pExpr, aff, &pUpperVal); + rc = valueFromExpr(pParse, pExpr, aff, &pRangeVal); assert( pUpper->eOperator==WO_LT || pUpper->eOperator==WO_LE ); - roundUpUpper = (pUpper->eOperator==WO_LE) ?1:0; - } - - if( rc!=SQLITE_OK || (pLowerVal==0 && pUpperVal==0) ){ - sqlite3ValueFree(pLowerVal); - sqlite3ValueFree(pUpperVal); - goto range_est_fallback; - }else if( pLowerVal==0 ){ - rc = whereRangeRegion(pParse, p, pUpperVal, roundUpUpper, &iUpper); - if( pLower ) iLower = iUpper/2; - }else if( pUpperVal==0 ){ - rc = whereRangeRegion(pParse, p, pLowerVal, roundUpLower, &iLower); - if( pUpper ) iUpper = (iLower + SQLITE_INDEX_SAMPLES + 1)/2; - }else{ - rc = whereRangeRegion(pParse, p, pUpperVal, roundUpUpper, &iUpper); - if( rc==SQLITE_OK ){ - rc = whereRangeRegion(pParse, p, pLowerVal, roundUpLower, &iLower); - } - } - WHERETRACE(("range scan regions: %d..%d\n", iLower, iUpper)); - - iEst = iUpper - iLower; - testcase( iEst==SQLITE_INDEX_SAMPLES ); - assert( iEst<=SQLITE_INDEX_SAMPLES ); - if( iEst<1 ){ - *piEst = 50/SQLITE_INDEX_SAMPLES; - }else{ - *piEst = (iEst*100)/SQLITE_INDEX_SAMPLES; - } - sqlite3ValueFree(pLowerVal); - sqlite3ValueFree(pUpperVal); - return rc; - } -range_est_fallback: + if( rc==SQLITE_OK + && whereKeyStats(pParse, p, pRangeVal, 1, a)==SQLITE_OK + ){ + iUpper = a[0]; + if( pUpper->eOperator==WO_LE ) iUpper += a[1]; + } + sqlite3ValueFree(pRangeVal); + } + if( rc==SQLITE_OK ){ + if( iUpper<=iLower ){ + *pRangeDiv = (double)p->aiRowEst[0]; + }else{ + *pRangeDiv = (double)p->aiRowEst[0]/(double)(iUpper - iLower); + } + WHERETRACE(("range scan regions: %u..%u div=%g\n", + (u32)iLower, (u32)iUpper, *pRangeDiv)); + return SQLITE_OK; + } + } #else UNUSED_PARAMETER(pParse); UNUSED_PARAMETER(p); UNUSED_PARAMETER(nEq); #endif assert( pLower || pUpper ); - *piEst = 100; - if( pLower && (pLower->wtFlags & TERM_VNULL)==0 ) *piEst /= 4; - if( pUpper ) *piEst /= 4; + *pRangeDiv = (double)1; + if( pLower && (pLower->wtFlags & TERM_VNULL)==0 ) *pRangeDiv *= (double)4; + if( pUpper ) *pRangeDiv *= (double)4; return rc; } -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 /* ** Estimate the number of rows that will be returned based on ** an equality constraint x=VALUE and where that VALUE occurs in ** the histogram data. This only works when x is the left-most -** column of an index and sqlite_stat2 histogram data is available +** column of an index and sqlite_stat3 histogram data is available ** for that index. When pExpr==NULL that means the constraint is ** "x IS NULL" instead of "x=VALUE". ** ** Write the estimated row count into *pnRow and return SQLITE_OK. ** If unable to make an estimate, leave *pnRow unchanged and return @@ -103058,44 +103420,36 @@ Index *p, /* The index whose left-most column is pTerm */ Expr *pExpr, /* Expression for VALUE in the x=VALUE constraint */ double *pnRow /* Write the revised row estimate here */ ){ sqlite3_value *pRhs = 0; /* VALUE on right-hand side of pTerm */ - int iLower, iUpper; /* Range of histogram regions containing pRhs */ u8 aff; /* Column affinity */ int rc; /* Subfunction return code */ - double nRowEst; /* New estimate of the number of rows */ + tRowcnt a[2]; /* Statistics */ assert( p->aSample!=0 ); + assert( p->nSample>0 ); aff = p->pTable->aCol[p->aiColumn[0]].affinity; if( pExpr ){ rc = valueFromExpr(pParse, pExpr, aff, &pRhs); if( rc ) goto whereEqualScanEst_cancel; }else{ pRhs = sqlite3ValueNew(pParse->db); } if( pRhs==0 ) return SQLITE_NOTFOUND; - rc = whereRangeRegion(pParse, p, pRhs, 0, &iLower); - if( rc ) goto whereEqualScanEst_cancel; - rc = whereRangeRegion(pParse, p, pRhs, 1, &iUpper); - if( rc ) goto whereEqualScanEst_cancel; - WHERETRACE(("equality scan regions: %d..%d\n", iLower, iUpper)); - if( iLower>=iUpper ){ - nRowEst = p->aiRowEst[0]/(SQLITE_INDEX_SAMPLES*2); - if( nRowEst<*pnRow ) *pnRow = nRowEst; - }else{ - nRowEst = (iUpper-iLower)*p->aiRowEst[0]/SQLITE_INDEX_SAMPLES; - *pnRow = nRowEst; - } - + rc = whereKeyStats(pParse, p, pRhs, 0, a); + if( rc==SQLITE_OK ){ + WHERETRACE(("equality scan regions: %d\n", (int)a[1])); + *pnRow = a[1]; + } whereEqualScanEst_cancel: sqlite3ValueFree(pRhs); return rc; } -#endif /* defined(SQLITE_ENABLE_STAT2) */ +#endif /* defined(SQLITE_ENABLE_STAT3) */ -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 /* ** Estimate the number of rows that will be returned based on ** an IN constraint where the right-hand side of the IN operator ** is a list of values. Example: ** @@ -103114,64 +103468,29 @@ Parse *pParse, /* Parsing & code generating context */ Index *p, /* The index whose left-most column is pTerm */ ExprList *pList, /* The value list on the RHS of "x IN (v1,v2,v3,...)" */ double *pnRow /* Write the revised row estimate here */ ){ - sqlite3_value *pVal = 0; /* One value from list */ - int iLower, iUpper; /* Range of histogram regions containing pRhs */ - u8 aff; /* Column affinity */ - int rc = SQLITE_OK; /* Subfunction return code */ - double nRowEst; /* New estimate of the number of rows */ - int nSpan = 0; /* Number of histogram regions spanned */ - int nSingle = 0; /* Histogram regions hit by a single value */ - int nNotFound = 0; /* Count of values that are not constants */ - int i; /* Loop counter */ - u8 aSpan[SQLITE_INDEX_SAMPLES+1]; /* Histogram regions that are spanned */ - u8 aSingle[SQLITE_INDEX_SAMPLES+1]; /* Histogram regions hit once */ + int rc = SQLITE_OK; /* Subfunction return code */ + double nEst; /* Number of rows for a single term */ + double nRowEst = (double)0; /* New estimate of the number of rows */ + int i; /* Loop counter */ assert( p->aSample!=0 ); - aff = p->pTable->aCol[p->aiColumn[0]].affinity; - memset(aSpan, 0, sizeof(aSpan)); - memset(aSingle, 0, sizeof(aSingle)); - for(i=0; i nExpr; i++){ - sqlite3ValueFree(pVal); - rc = valueFromExpr(pParse, pList->a[i].pExpr, aff, &pVal); - if( rc ) break; - if( pVal==0 || sqlite3_value_type(pVal)==SQLITE_NULL ){ - nNotFound++; - continue; - } - rc = whereRangeRegion(pParse, p, pVal, 0, &iLower); - if( rc ) break; - rc = whereRangeRegion(pParse, p, pVal, 1, &iUpper); - if( rc ) break; - if( iLower>=iUpper ){ - aSingle[iLower] = 1; - }else{ - assert( iLower>=0 && iUpper<=SQLITE_INDEX_SAMPLES ); - while( iLower nExpr; i++){ + nEst = p->aiRowEst[0]; + rc = whereEqualScanEst(pParse, p, pList->a[i].pExpr, &nEst); + nRowEst += nEst; } if( rc==SQLITE_OK ){ - for(i=nSpan=0; i<=SQLITE_INDEX_SAMPLES; i++){ - if( aSpan[i] ){ - nSpan++; - }else if( aSingle[i] ){ - nSingle++; - } - } - nRowEst = (nSpan*2+nSingle)*p->aiRowEst[0]/(2*SQLITE_INDEX_SAMPLES) - + nNotFound*p->aiRowEst[1]; if( nRowEst > p->aiRowEst[0] ) nRowEst = p->aiRowEst[0]; *pnRow = nRowEst; - WHERETRACE(("IN row estimate: nSpan=%d, nSingle=%d, nNotFound=%d, est=%g\n", - nSpan, nSingle, nNotFound, nRowEst)); + WHERETRACE(("IN row estimate: est=%g\n", nRowEst)); } - sqlite3ValueFree(pVal); return rc; } -#endif /* defined(SQLITE_ENABLE_STAT2) */ +#endif /* defined(SQLITE_ENABLE_STAT3) */ /* ** Find the best query plan for accessing a particular table. Write the ** best query plan and its cost into the WhereCost object supplied as the @@ -103214,11 +103533,11 @@ Index *pProbe; /* An index we are evaluating */ Index *pIdx; /* Copy of pProbe, or zero for IPK index */ int eqTermMask; /* Current mask of valid equality operators */ int idxEqTermMask; /* Index mask of valid equality operators */ Index sPk; /* A fake index object for the primary key */ - unsigned int aiRowEstPk[2]; /* The aiRowEst[] value for the sPk index */ + tRowcnt aiRowEstPk[2]; /* The aiRowEst[] value for the sPk index */ int aiColumnPk = -1; /* The aColumn[] value for the sPk index */ int wsFlagMask; /* Allowed flags in pCost->plan.wsFlag */ /* Initialize the cost to a worst-case value */ memset(pCost, 0, sizeof(*pCost)); @@ -103269,14 +103588,14 @@ } /* Loop over all indices looking for the best one to use */ for(; pProbe; pIdx=pProbe=pProbe->pNext){ - const unsigned int * const aiRowEst = pProbe->aiRowEst; + const tRowcnt * const aiRowEst = pProbe->aiRowEst; double cost; /* Cost of using pProbe */ double nRow; /* Estimated number of rows in result set */ - double log10N; /* base-10 logarithm of nRow (inexact) */ + double log10N = (double)1; /* base-10 logarithm of nRow (inexact) */ int rev; /* True to scan in reverse order */ int wsFlags = 0; Bitmask used = 0; /* The following variables are populated based on the properties of @@ -103312,18 +103631,16 @@ ** Set to true if there was at least one "x IN (SELECT ...)" term used ** in determining the value of nInMul. Note that the RHS of the ** IN operator must be a SELECT, not a value list, for this variable ** to be true. ** - ** estBound: - ** An estimate on the amount of the table that must be searched. A - ** value of 100 means the entire table is searched. Range constraints - ** might reduce this to a value less than 100 to indicate that only - ** a fraction of the table needs searching. In the absence of - ** sqlite_stat2 ANALYZE data, a single inequality reduces the search - ** space to 1/4rd its original size. So an x>? constraint reduces - ** estBound to 25. Two constraints (x>? AND x) reduce estBound to 6. + ** rangeDiv: + ** An estimate of a divisor by which to reduce the search space due + ** to inequality constraints. In the absence of sqlite_stat3 ANALYZE + ** data, a single inequality reduces the search space to 1/4rd its + ** original size (rangeDiv==4). Two inequalities reduce the search + ** space to 1/16th of its original size (rangeDiv==16). ** ** bSort: ** Boolean. True if there is an ORDER BY clause that will require an ** external sort (i.e. scanning the index being evaluated will not ** correctly order records). @@ -103344,26 +103661,27 @@ ** SELECT a, b, c FROM tbl WHERE a = 1; */ int nEq; /* Number of == or IN terms matching index */ int bInEst = 0; /* True if "x IN (SELECT...)" seen */ int nInMul = 1; /* Number of distinct equalities to lookup */ - int estBound = 100; /* Estimated reduction in search space */ + double rangeDiv = (double)1; /* Estimated reduction in search space */ int nBound = 0; /* Number of range constraints seen */ int bSort = !!pOrderBy; /* True if external sort required */ int bDist = !!pDistinct; /* True if index cannot help with DISTINCT */ int bLookup = 0; /* True if not a covering index */ WhereTerm *pTerm; /* A single term of the WHERE clause */ -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 WhereTerm *pFirstTerm = 0; /* First term matching the index */ #endif /* Determine the values of nEq and nInMul */ for(nEq=0; nEq nColumn; nEq++){ int j = pProbe->aiColumn[nEq]; pTerm = findTerm(pWC, iCur, j, notReady, eqTermMask, pIdx); if( pTerm==0 ) break; wsFlags |= (WHERE_COLUMN_EQ|WHERE_ROWID_EQ); + testcase( pTerm->pWC!=pWC ); if( pTerm->eOperator & WO_IN ){ Expr *pExpr = pTerm->pExpr; wsFlags |= WHERE_COLUMN_IN; if( ExprHasProperty(pExpr, EP_xIsSelect) ){ /* "x IN (SELECT ...)": Assume the SELECT returns 25 rows */ @@ -103374,32 +103692,34 @@ nInMul *= pExpr->x.pList->nExpr; } }else if( pTerm->eOperator & WO_ISNULL ){ wsFlags |= WHERE_COLUMN_NULL; } -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 if( nEq==0 && pProbe->aSample ) pFirstTerm = pTerm; #endif used |= pTerm->prereqRight; } - /* Determine the value of estBound. */ + /* Determine the value of rangeDiv */ if( nEq nColumn && pProbe->bUnordered==0 ){ int j = pProbe->aiColumn[nEq]; if( findTerm(pWC, iCur, j, notReady, WO_LT|WO_LE|WO_GT|WO_GE, pIdx) ){ WhereTerm *pTop = findTerm(pWC, iCur, j, notReady, WO_LT|WO_LE, pIdx); WhereTerm *pBtm = findTerm(pWC, iCur, j, notReady, WO_GT|WO_GE, pIdx); - whereRangeScanEst(pParse, pProbe, nEq, pBtm, pTop, &estBound); + whereRangeScanEst(pParse, pProbe, nEq, pBtm, pTop, &rangeDiv); if( pTop ){ nBound = 1; wsFlags |= WHERE_TOP_LIMIT; used |= pTop->prereqRight; + testcase( pTop->pWC!=pWC ); } if( pBtm ){ nBound++; wsFlags |= WHERE_BTM_LIMIT; used |= pBtm->prereqRight; + testcase( pBtm->pWC!=pWC ); } wsFlags |= (WHERE_COLUMN_RANGE|WHERE_ROWID_RANGE); } }else if( pProbe->onError!=OE_None ){ testcase( wsFlags & WHERE_COLUMN_IN ); @@ -103458,32 +103778,34 @@ if( bInEst && nRow*2>aiRowEst[0] ){ nRow = aiRowEst[0]/2; nInMul = (int)(nRow / aiRowEst[nEq]); } -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 /* If the constraint is of the form x=VALUE or x IN (E1,E2,...) ** and we do not think that values of x are unique and if histogram ** data is available for column x, then it might be possible ** to get a better estimate on the number of rows based on ** VALUE and how common that value is according to the histogram. */ if( nRow>(double)1 && nEq==1 && pFirstTerm!=0 && aiRowEst[1]>1 ){ + assert( (pFirstTerm->eOperator & (WO_EQ|WO_ISNULL|WO_IN))!=0 ); if( pFirstTerm->eOperator & (WO_EQ|WO_ISNULL) ){ testcase( pFirstTerm->eOperator==WO_EQ ); testcase( pFirstTerm->eOperator==WO_ISNULL ); whereEqualScanEst(pParse, pProbe, pFirstTerm->pExpr->pRight, &nRow); - }else if( pFirstTerm->eOperator==WO_IN && bInEst==0 ){ + }else if( bInEst==0 ){ + assert( pFirstTerm->eOperator==WO_IN ); whereInScanEst(pParse, pProbe, pFirstTerm->pExpr->x.pList, &nRow); } } -#endif /* SQLITE_ENABLE_STAT2 */ +#endif /* SQLITE_ENABLE_STAT3 */ /* Adjust the number of output rows and downward to reflect rows ** that are excluded by range constraints. */ - nRow = (nRow * (double)estBound) / (double)100; + nRow = nRow/rangeDiv; if( nRow<1 ) nRow = 1; /* Experiments run on real SQLite databases show that the time needed ** to do a binary search to locate a row in a table or index is roughly ** log10(N) times the time to move from one row to the next row within @@ -103608,14 +103930,14 @@ if( nRow<2 ) nRow = 2; } WHERETRACE(( - "%s(%s): nEq=%d nInMul=%d estBound=%d bSort=%d bLookup=%d wsFlags=0x%x\n" + "%s(%s): nEq=%d nInMul=%d rangeDiv=%d bSort=%d bLookup=%d wsFlags=0x%x\n" " notReady=0x%llx log10N=%.1f nRow=%.1f cost=%.1f used=0x%llx\n", pSrc->pTab->zName, (pIdx ? pIdx->zName : "ipk"), - nEq, nInMul, estBound, bSort, bLookup, wsFlags, + nEq, nInMul, (int)rangeDiv, bSort, bLookup, wsFlags, notReady, log10N, nRow, cost, used )); /* If this index is the best we have seen so far, then record this ** index and its cost in the pCost structure. @@ -104115,11 +104437,12 @@ */ static Bitmask codeOneLoopStart( WhereInfo *pWInfo, /* Complete information about the WHERE clause */ int iLevel, /* Which level of pWInfo->a[] should be coded */ u16 wctrlFlags, /* One of the WHERE_* flags defined in sqliteInt.h */ - Bitmask notReady /* Which tables are currently available */ + Bitmask notReady, /* Which tables are currently available */ + Expr *pWhere /* Complete WHERE clause */ ){ int j, k; /* Loop counters */ int iCur; /* The VDBE cursor for the table */ int addrNxt; /* Where to jump to continue with the next IN case */ int omitTable; /* True if we use the index only */ @@ -104597,11 +104920,12 @@ int regRowset = 0; /* Register for RowSet object */ int regRowid = 0; /* Register holding rowid */ int iLoopBody = sqlite3VdbeMakeLabel(v); /* Start of loop body */ int iRetInit; /* Address of regReturn init */ int untestedTerms = 0; /* Some terms not completely tested */ - int ii; + int ii; /* Loop counter */ + Expr *pAndExpr = 0; /* An ".. AND (...)" expression */ pTerm = pLevel->plan.u.pTerm; assert( pTerm!=0 ); assert( pTerm->eOperator==WO_OR ); assert( (pTerm->wtFlags & TERM_ORINFO)!=0 ); @@ -104646,18 +104970,33 @@ regRowset = ++pParse->nMem; regRowid = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Null, 0, regRowset); } iRetInit = sqlite3VdbeAddOp2(v, OP_Integer, 0, regReturn); + + /* If the original WHERE clause is z of the form: (x1 OR x2 OR ...) AND y + ** Then for every term xN, evaluate as the subexpression: xN AND z + ** That way, terms in y that are factored into the disjunction will + ** be picked up by the recursive calls to sqlite3WhereBegin() below. + */ + if( pWC->nTerm>1 ){ + pAndExpr = sqlite3ExprAlloc(pParse->db, TK_AND, 0, 0); + pAndExpr->pRight = pWhere; + } for(ii=0; ii nTerm; ii++){ WhereTerm *pOrTerm = &pOrWc->a[ii]; if( pOrTerm->leftCursor==iCur || pOrTerm->eOperator==WO_AND ){ WhereInfo *pSubWInfo; /* Info for single OR-term scan */ + Expr *pOrExpr = pOrTerm->pExpr; + if( pAndExpr ){ + pAndExpr->pLeft = pOrExpr; + pOrExpr = pAndExpr; + } /* Loop through table entries that match term pOrTerm. */ - pSubWInfo = sqlite3WhereBegin(pParse, pOrTab, pOrTerm->pExpr, 0, 0, - WHERE_OMIT_OPEN | WHERE_OMIT_CLOSE | + pSubWInfo = sqlite3WhereBegin(pParse, pOrTab, pOrExpr, 0, 0, + WHERE_OMIT_OPEN_CLOSE | WHERE_AND_ONLY | WHERE_FORCE_TABLE | WHERE_ONETABLE_ONLY); if( pSubWInfo ){ explainOneScan( pParse, pOrTab, &pSubWInfo->a[0], iLevel, pLevel->iFrom, 0 ); @@ -104681,10 +105020,11 @@ /* Finish the loop through table entries that match term pOrTerm. */ sqlite3WhereEnd(pSubWInfo); } } } + sqlite3DbFree(pParse->db, pAndExpr); sqlite3VdbeChangeP1(v, iRetInit, sqlite3VdbeCurrentAddr(v)); sqlite3VdbeAddOp2(v, OP_Goto, 0, pLevel->addrBrk); sqlite3VdbeResolveLabel(v, iLoopBody); if( pWInfo->nLevel>1 ) sqlite3StackFree(pParse->db, pOrTab); @@ -104962,11 +105302,11 @@ /* Split the WHERE clause into separate subexpressions where each ** subexpression is separated by an AND operator. */ initMaskSet(pMaskSet); - whereClauseInit(pWC, pParse, pMaskSet); + whereClauseInit(pWC, pParse, pMaskSet, wctrlFlags); sqlite3ExprCodeConstants(pParse, pWhere); whereSplit(pWC, pWhere, TK_AND); /* IMP: R-15842-53296 */ /* Special case: a WHERE clause that is constant. Evaluate the ** expression and either jump over all of the code or fall thru. @@ -105201,11 +105541,12 @@ assert( bestJ>=0 ); assert( notReady & getMask(pMaskSet, pTabList->a[bestJ].iCursor) ); WHERETRACE(("*** Optimizer selects table %d for loop %d" " with cost=%g and nRow=%g\n", bestJ, pLevel-pWInfo->a, bestPlan.rCost, bestPlan.plan.nRow)); - if( (bestPlan.plan.wsFlags & WHERE_ORDERBY)!=0 ){ + /* The ALWAYS() that follows was added to hush up clang scan-build */ + if( (bestPlan.plan.wsFlags & WHERE_ORDERBY)!=0 && ALWAYS(ppOrderBy) ){ *ppOrderBy = 0; } if( (bestPlan.plan.wsFlags & WHERE_DISTINCT)!=0 ){ assert( pWInfo->eDistinct==0 ); pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; @@ -105290,11 +105631,11 @@ int iCur = pTabItem->iCursor; sqlite3VdbeAddOp4(v, OP_VOpen, iCur, 0, 0, pVTab, P4_VTAB); }else #endif if( (pLevel->plan.wsFlags & WHERE_IDX_ONLY)==0 - && (wctrlFlags & WHERE_OMIT_OPEN)==0 ){ + && (wctrlFlags & WHERE_OMIT_OPEN_CLOSE)==0 ){ int op = pWInfo->okOnePass ? OP_OpenWrite : OP_OpenRead; sqlite3OpenTable(pParse, pTabItem->iCursor, iDb, pTab, op); testcase( pTab->nCol==BMS-1 ); testcase( pTab->nCol==BMS ); if( !pWInfo->okOnePass && pTab->nCol a[i]; explainOneScan(pParse, pTabList, pLevel, i, pLevel->iFrom, wctrlFlags); - notReady = codeOneLoopStart(pWInfo, i, wctrlFlags, notReady); + notReady = codeOneLoopStart(pWInfo, i, wctrlFlags, notReady, pWhere); pWInfo->iContinue = pLevel->addrCont; } #ifdef SQLITE_TEST /* For testing and debugging use only */ /* Record in the query plan information about the current table @@ -105470,11 +105811,11 @@ struct SrcList_item *pTabItem = &pTabList->a[pLevel->iFrom]; Table *pTab = pTabItem->pTab; assert( pTab!=0 ); if( (pTab->tabFlags & TF_Ephemeral)==0 && pTab->pSelect==0 - && (pWInfo->wctrlFlags & WHERE_OMIT_CLOSE)==0 + && (pWInfo->wctrlFlags & WHERE_OMIT_OPEN_CLOSE)==0 ){ int ws = pLevel->plan.wsFlags; if( !pWInfo->okOnePass && (ws & WHERE_IDX_ONLY)==0 ){ sqlite3VdbeAddOp1(v, OP_Close, pTabItem->iCursor); } @@ -108817,11 +109158,13 @@ sqlite3ParserTOKENTYPE yyminor /* The value for the token */ sqlite3ParserARG_PDECL /* Optional %extra_argument parameter */ ){ YYMINORTYPE yyminorunion; int yyact; /* The parser action. */ +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) int yyendofinput; /* True if we are at the end of input */ +#endif #ifdef YYERRORSYMBOL int yyerrorhit = 0; /* True if yymajor has invoked an error */ #endif yyParser *yypParser; /* The parser */ @@ -108840,11 +109183,13 @@ yypParser->yyerrcnt = -1; yypParser->yystack[0].stateno = 0; yypParser->yystack[0].major = 0; } yyminorunion.yy0 = yyminor; +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) yyendofinput = (yymajor==0); +#endif sqlite3ParserARG_STORE; #ifndef NDEBUG if( yyTraceFILE ){ fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]); @@ -108852,11 +109197,10 @@ #endif do{ yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor); if( yyact yyerrcnt--; yymajor = YYNOCODE; }else if( yyact < YYNSTATE + YYNRULE ){ yy_reduce(yypParser,yyact-YYNSTATE); @@ -110244,11 +110588,11 @@ ** ** * Recursive calls to this routine from thread X return immediately ** without blocking. */ SQLITE_API int sqlite3_initialize(void){ - sqlite3_mutex *pMaster; /* The main static mutex */ + MUTEX_LOGIC( sqlite3_mutex *pMaster; ) /* The main static mutex */ int rc; /* Result code */ #ifdef SQLITE_OMIT_WSD rc = sqlite3_wsd_init(4096, 24); if( rc!=SQLITE_OK ){ @@ -110278,11 +110622,11 @@ ** This operation is protected by the STATIC_MASTER mutex. Note that ** MutexAlloc() is called for a static mutex prior to initializing the ** malloc subsystem - this implies that the allocation of a static ** mutex must not require support from the malloc subsystem. */ - pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + MUTEX_LOGIC( pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) sqlite3_mutex_enter(pMaster); sqlite3GlobalConfig.isMutexInit = 1; if( !sqlite3GlobalConfig.isMallocInit ){ rc = sqlite3MallocInit(); } @@ -111352,17 +111696,17 @@ sqlite3 *db, const char *zName, int nArg ){ int nName = sqlite3Strlen30(zName); - int rc; + int rc = SQLITE_OK; sqlite3_mutex_enter(db->mutex); if( sqlite3FindFunction(db, zName, nName, nArg, SQLITE_UTF8, 0)==0 ){ - sqlite3CreateFunc(db, zName, nArg, SQLITE_UTF8, - 0, sqlite3InvalidFunction, 0, 0, 0); + rc = sqlite3CreateFunc(db, zName, nArg, SQLITE_UTF8, + 0, sqlite3InvalidFunction, 0, 0, 0); } - rc = sqlite3ApiExit(db, SQLITE_OK); + rc = sqlite3ApiExit(db, rc); sqlite3_mutex_leave(db->mutex); return rc; } #ifndef SQLITE_OMIT_TRACE @@ -112420,10 +112764,11 @@ if( db ){ assert( db->mutex!=0 || isThreadsafe==0 || sqlite3GlobalConfig.bFullMutex==0 ); sqlite3_mutex_leave(db->mutex); } rc = sqlite3_errcode(db); + assert( db!=0 || rc==SQLITE_NOMEM ); if( rc==SQLITE_NOMEM ){ sqlite3_close(db); db = 0; }else if( rc!=SQLITE_OK ){ db->magic = SQLITE_MAGIC_SICK; @@ -114148,10 +114493,17 @@ #else # define TESTONLY(X) #endif #endif /* SQLITE_AMALGAMATION */ + +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3Fts3Corrupt(void); +# define FTS_CORRUPT_VTAB sqlite3Fts3Corrupt() +#else +# define FTS_CORRUPT_VTAB SQLITE_CORRUPT_VTAB +#endif typedef struct Fts3Table Fts3Table; typedef struct Fts3Cursor Fts3Cursor; typedef struct Fts3Expr Fts3Expr; typedef struct Fts3Phrase Fts3Phrase; @@ -114649,11 +115001,11 @@ char **pp, char *pStart, sqlite3_int64 *pVal ){ sqlite3_int64 iVal; - char *p = *pp; + char *p; /* Pointer p now points at the first byte past the varint we are ** interested in. So, unless the doclist is corrupt, the 0x80 bit is ** clear on character p[-1]. */ for(p = (*pp)-2; p>=pStart && *p&0x80; p--); @@ -115050,11 +115402,11 @@ ** the output value undefined. Otherwise SQLITE_OK is returned. ** ** This function is used when parsing the "prefix=" FTS4 parameter. */ static int fts3GobbleInt(const char **pp, int *pnOut){ - const char *p = *pp; /* Iterator pointer */ + const char *p; /* Iterator pointer */ int nInt = 0; /* Output value */ for(p=*pp; p[0]>='0' && p[0]<='9'; p++){ nInt = nInt * 10 + (p[0] - '0'); } @@ -115549,11 +115901,11 @@ if( rc==SQLITE_OK ){ /* If no row was found and no error has occured, then the %_content ** table is missing a row that is present in the full-text index. ** The data structures are corrupt. */ - rc = SQLITE_CORRUPT_VTAB; + rc = FTS_CORRUPT_VTAB; } pCsr->isEof = 1; if( pContext ){ sqlite3_result_error_code(pContext, rc); } @@ -115609,11 +115961,11 @@ ** nNode bytes of content (see sqlite3Fts3ReadBlock() for details). */ zCsr += sqlite3Fts3GetVarint(zCsr, &iChild); zCsr += sqlite3Fts3GetVarint(zCsr, &iChild); if( zCsr>zEnd ){ - return SQLITE_CORRUPT_VTAB; + return FTS_CORRUPT_VTAB; } while( zCsr zEnd ){ - rc = SQLITE_CORRUPT_VTAB; + rc = FTS_CORRUPT_VTAB; goto finish_scan; } if( nPrefix+nSuffix>nAlloc ){ char *zNew; nAlloc = (nPrefix+nSuffix) * 2; @@ -115640,10 +115992,11 @@ rc = SQLITE_NOMEM; goto finish_scan; } zBuffer = zNew; } + assert( zBuffer ); memcpy(&zBuffer[nPrefix], zCsr, nSuffix); nBuffer = nPrefix + nSuffix; zCsr += nSuffix; /* Compare the term we are searching for with the term just loaded from @@ -117076,11 +117429,11 @@ ** moves *ppPoslist so that it instead points to the first byte of the ** same position list. */ static void fts3ReversePoslist(char *pStart, char **ppPoslist){ char *p = &(*ppPoslist)[-2]; - char c; + char c = 0; while( p>pStart && (c=*p--)==0 ); while( p>pStart && (*p & 0x80) | c ){ c = *p--; } @@ -118070,11 +118423,11 @@ while( a nDoc = nDoc; pCsr->nRowAvg = (int)(((nByte / nDoc) + p->nPgsz) / p->nPgsz); assert( pCsr->nRowAvg>0 ); @@ -118546,12 +118899,15 @@ } aPoslist = pExpr->pRight->pPhrase->doclist.pList; nToken = pExpr->pRight->pPhrase->nToken; for(p=pExpr->pLeft; p && res; p=p->pLeft){ - int nNear = p->pParent->nNear; - Fts3Phrase *pPhrase = ( + int nNear; + Fts3Phrase *pPhrase; + assert( p->pParent && p->pParent->pLeft==p ); + nNear = p->pParent->nNear; + pPhrase = ( p->eType==FTSQUERY_NEAR ? p->pRight->pPhrase : p->pPhrase ); res = fts3EvalNearTrim(nNear, aTmp, &aPoslist, &nToken, pPhrase); } } @@ -119037,10 +119393,19 @@ pPhrase->aToken[i].pSegcsr = 0; } } } +/* +** Return SQLITE_CORRUPT_VTAB. +*/ +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3Fts3Corrupt(){ + return SQLITE_CORRUPT_VTAB; +} +#endif + #if !SQLITE_CORE /* ** Initialize API pointer table, if required. */ SQLITE_API int sqlite3_extension_init( @@ -119834,12 +120199,16 @@ p->pPhrase = (Fts3Phrase *)&p[1]; p->pPhrase->iColumn = pParse->iDefaultCol; p->pPhrase->nToken = nToken; zBuf = (char *)&p->pPhrase->aToken[nToken]; - memcpy(zBuf, zTemp, nTemp); - sqlite3_free(zTemp); + if( zTemp ){ + memcpy(zBuf, zTemp, nTemp); + sqlite3_free(zTemp); + }else{ + assert( nTemp==0 ); + } for(jj=0; jj pPhrase->nToken; jj++){ p->pPhrase->aToken[jj].z = zBuf; zBuf += p->pPhrase->aToken[jj].n; } @@ -122594,11 +122963,11 @@ sqlite3_bind_int64(pStmt, 1, iDocid); } rc = sqlite3_step(pStmt); if( rc!=SQLITE_ROW || sqlite3_column_type(pStmt, 0)!=SQLITE_BLOB ){ rc = sqlite3_reset(pStmt); - if( rc==SQLITE_OK ) rc = SQLITE_CORRUPT_VTAB; + if( rc==SQLITE_OK ) rc = FTS_CORRUPT_VTAB; pStmt = 0; }else{ rc = SQLITE_OK; } } @@ -123398,11 +123767,11 @@ pNext += sqlite3Fts3GetVarint32(pNext, &nPrefix); pNext += sqlite3Fts3GetVarint32(pNext, &nSuffix); if( nPrefix<0 || nSuffix<=0 || &pNext[nSuffix]>&pReader->aNode[pReader->nNode] ){ - return SQLITE_CORRUPT_VTAB; + return FTS_CORRUPT_VTAB; } if( nPrefix+nSuffix>pReader->nTermAlloc ){ int nNew = (nPrefix+nSuffix)*2; char *zNew = sqlite3_realloc(pReader->zTerm, nNew); @@ -123428,11 +123797,11 @@ ** of these statements is untrue, then the data structure is corrupt. */ if( &pReader->aDoclist[pReader->nDoclist]>&pReader->aNode[pReader->nNode] || (pReader->nPopulate==0 && pReader->aDoclist[pReader->nDoclist-1]) ){ - return SQLITE_CORRUPT_VTAB; + return FTS_CORRUPT_VTAB; } return SQLITE_OK; } /* @@ -125382,11 +125751,10 @@ sqlite_int64 *pRowid /* OUT: The affected (or effected) rowid */ ){ Fts3Table *p = (Fts3Table *)pVtab; int rc = SQLITE_OK; /* Return Code */ int isRemove = 0; /* True for an UPDATE or DELETE */ - sqlite3_int64 iRemove = 0; /* Rowid removed by UPDATE or DELETE */ u32 *aSzIns = 0; /* Sizes of inserted documents */ u32 *aSzDel; /* Sizes of deleted documents */ int nChng = 0; /* Net change in number of documents */ int bInsertDone = 0; @@ -125465,23 +125833,23 @@ /* If this is a DELETE or UPDATE operation, remove the old record. */ if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){ assert( sqlite3_value_type(apVal[0])==SQLITE_INTEGER ); rc = fts3DeleteByRowid(p, apVal[0], &nChng, aSzDel); isRemove = 1; - iRemove = sqlite3_value_int64(apVal[0]); } /* If this is an INSERT or UPDATE operation, insert the new record. */ if( nArg>1 && rc==SQLITE_OK ){ if( bInsertDone==0 ){ rc = fts3InsertData(p, apVal, pRowid); - if( rc==SQLITE_CONSTRAINT ) rc = SQLITE_CORRUPT_VTAB; + if( rc==SQLITE_CONSTRAINT ) rc = FTS_CORRUPT_VTAB; } - if( rc==SQLITE_OK && (!isRemove || *pRowid!=iRemove) ){ + if( rc==SQLITE_OK && (!isRemove || *pRowid!=p->iPrevDocid ) ){ rc = fts3PendingTermsDocid(p, *pRowid); } if( rc==SQLITE_OK ){ + assert( p->iPrevDocid==*pRowid ); rc = fts3InsertTerms(p, apVal, aSzIns); } if( p->bHasDocsize ){ fts3InsertDocsize(&rc, p, aSzIns); } @@ -126371,11 +126739,11 @@ pStmt = *ppStmt; assert( sqlite3_data_count(pStmt)==1 ); a = sqlite3_column_blob(pStmt, 0); a += sqlite3Fts3GetVarint(a, &nDoc); - if( nDoc==0 ) return SQLITE_CORRUPT_VTAB; + if( nDoc==0 ) return FTS_CORRUPT_VTAB; *pnDoc = (u32)nDoc; if( paLen ) *paLen = a; return SQLITE_OK; } @@ -126950,11 +127318,11 @@ sqlite3_snprintf(sizeof(aBuffer), aBuffer, "%d %d %d %d ", iCol, pTerm-sCtx.aTerm, iStart, iEnd-iStart ); rc = fts3StringAppend(&res, aBuffer, -1); }else if( rc==SQLITE_DONE ){ - rc = SQLITE_CORRUPT_VTAB; + rc = FTS_CORRUPT_VTAB; } } } if( rc==SQLITE_DONE ){ rc = SQLITE_OK; @@ -128291,11 +128659,12 @@ pCsr->nConstraint = argc; if( !pCsr->aConstraint ){ rc = SQLITE_NOMEM; }else{ memset(pCsr->aConstraint, 0, sizeof(RtreeConstraint)*argc); - assert( (idxStr==0 && argc==0) || (int)strlen(idxStr)==argc*2 ); + assert( (idxStr==0 && argc==0) + || (idxStr && (int)strlen(idxStr)==argc*2) ); for(ii=0; ii aConstraint[ii]; p->op = idxStr[ii*2]; p->iCoord = idxStr[ii*2+1]-'a'; if( p->op==RTREE_MATCH ){ @@ -128592,11 +128961,14 @@ int iCell; sqlite3_int64 iBest = 0; float fMinGrowth = 0.0; float fMinArea = 0.0; +#if VARIANT_RSTARTREE_CHOOSESUBTREE float fMinOverlap = 0.0; + float overlap; +#endif int nCell = NCELL(pNode); RtreeCell cell; RtreeNode *pChild; @@ -128624,33 +128996,34 @@ */ for(iCell=0; iCell iDepth-1) ){ overlap = cellOverlapEnlargement(pRtree,&cell,pCell,aCell,nCell,iCell); + }else{ + overlap = 0.0; } if( (iCell==0) || (overlap including -** the nul-terminator bytes. +** the nul-terminator bytes as this saves SQLite from having to +** make a copy of the input string. ** ** ^If pzTail is not NULL then *pzTail is made to point to the first byte ** past the end of the first SQL statement in zSql. These routines only ** compile the first statement in zSql, so *pzTail is left pointing to ** what remains uncompiled. @@ -2848,11 +2854,11 @@ ** a schema change, on the first [sqlite3_step()] call following any change ** to the [sqlite3_bind_text | bindings] of that [parameter]. ** ^The specific value of WHERE-clause [parameter] might influence the ** choice of query plan if the parameter is the left-hand side of a [LIKE] ** or [GLOB] operator or if the parameter is compared to an indexed column -** and the [SQLITE_ENABLE_STAT2] compile-time option is enabled. +** and the [SQLITE_ENABLE_STAT3] compile-time option is enabled. ** the ** ** */ SQLITE_API int sqlite3_prepare( @@ -3018,10 +3024,17 @@ ** ^(In those routines that have a fourth argument, its value is the ** number of bytes in the parameter. To be clear: the value is the ** number of bytes in the value, not the number of characters.)^ ** ^If the fourth parameter is negative, the length of the string is ** the number of bytes up to the first zero terminator. +** If a non-negative fourth parameter is provided to sqlite3_bind_text() +** or sqlite3_bind_text16() then that parameter must be the byte offset +** where the NUL terminator would occur assuming the string were NUL +** terminated. If any NUL characters occur at byte offsets less than +** the value of the fourth parameter then the resulting string value will +** contain embedded NULs. The result of expressions involving strings +** with embedded NULs is undefined. ** ** ^The fifth argument to sqlite3_bind_blob(), sqlite3_bind_text(), and ** sqlite3_bind_text16() is a destructor used to dispose of the BLOB or ** string after SQLite has finished with it. ^The destructor is called ** to dispose of the BLOB or string even if the call to sqlite3_bind_blob(), @@ -3351,10 +3364,16 @@ ** current row of the result set of [prepared statement] P. ** ^If prepared statement P does not have results ready to return ** (via calls to the [sqlite3_column_int | sqlite3_column_*()] of ** interfaces) then sqlite3_data_count(P) returns 0. ** ^The sqlite3_data_count(P) routine also returns 0 if P is a NULL pointer. +** ^The sqlite3_data_count(P) routine returns 0 if the previous call to +** [sqlite3_step](P) returned [SQLITE_DONE]. ^The sqlite3_data_count(P) +** will return non-zero if previous call to [sqlite3_step](P) returned +** [SQLITE_ROW], except in the case of the [PRAGMA incremental_vacuum] +** where it always returns zero since each step of that multi-step +** pragma returns 0 columns of data. ** ** See also: [sqlite3_column_count()] */ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); @@ -4030,11 +4049,16 @@ ** is negative, then SQLite takes result text from the 2nd parameter ** through the first zero character. ** ^If the 3rd parameter to the sqlite3_result_text* interfaces ** is non-negative, then as many bytes (not characters) of the text ** pointed to by the 2nd parameter are taken as the application-defined -** function result. +** function result. If the 3rd parameter is non-negative, then it +** must be the byte offset into the string where the NUL terminator would +** appear if the string where NUL terminated. If any NUL characters occur +** in the string at a byte offset that is less than the value of the 3rd +** parameter, then the resulting string will contain embedded NULs and the +** result of expressions operating on strings with embedded NULs is undefined. ** ^If the 4th parameter to the sqlite3_result_text* interfaces ** or sqlite3_result_blob is a non-NULL pointer, then SQLite calls that ** function as the destructor on the text or BLOB result when it has ** finished using that result. ** ^If the 4th parameter to the sqlite3_result_text* interfaces or to @@ -5813,20 +5837,34 @@ ** - This parameter returns the approximate number of of bytes of heap ** and lookaside memory used by all prepared statements associated with ** the database connection.)^ ** ^The highwater mark associated with SQLITE_DBSTATUS_STMT_USED is always 0. **
+** +** [[SQLITE_DBSTATUS_CACHE_HIT]] ^(- SQLITE_DBSTATUS_CACHE_HIT
+**- This parameter returns the number of pager cache hits that have +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_HIT +** is always 0. +**
+** +** [[SQLITE_DBSTATUS_CACHE_MISS]] ^(- SQLITE_DBSTATUS_CACHE_MISS
+**- This parameter returns the number of pager cache misses that have +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_MISS +** is always 0. +**
** */ #define SQLITE_DBSTATUS_LOOKASIDE_USED 0 #define SQLITE_DBSTATUS_CACHE_USED 1 #define SQLITE_DBSTATUS_SCHEMA_USED 2 #define SQLITE_DBSTATUS_STMT_USED 3 #define SQLITE_DBSTATUS_LOOKASIDE_HIT 4 #define SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5 #define SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6 -#define SQLITE_DBSTATUS_MAX 6 /* Largest defined DBSTATUS */ +#define SQLITE_DBSTATUS_CACHE_HIT 7 +#define SQLITE_DBSTATUS_CACHE_MISS 8 +#define SQLITE_DBSTATUS_MAX 8 /* Largest defined DBSTATUS */ /* ** CAPI3REF: Prepared Statement Status ** @@ -5876,11 +5914,10 @@ **- ^This is the number of rows inserted into transient indices that ** were created automatically in order to help joins run faster. ** A non-zero value in this counter may indicate an opportunity to ** improvement performance by adding permanent indices that do not ** need to be reinitialized each time the statement is run.
-** ** */ #define SQLITE_STMTSTATUS_FULLSCAN_STEP 1 #define SQLITE_STMTSTATUS_SORT 2 #define SQLITE_STMTSTATUS_AUTOINDEX 3 Index: src/stash.c ================================================================== --- src/stash.c +++ src/stash.c @@ -482,11 +482,11 @@ db_finalize(&q); if( n==0 ) fossil_print("empty stash\n"); }else if( memcmp(zCmd, "drop", nCmd)==0 || memcmp(zCmd, "rm", nCmd)==0 ){ int allFlag = find_option("all", 0, 0)!=0; - if( g.argc>4 ) usage("stash apply STASHID"); + if( g.argc>4 ) usage("apply STASHID"); if( allFlag ){ db_multi_exec("DELETE FROM stash; DELETE FROM stashfile;"); }else{ stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); undo_begin(); @@ -494,30 +494,30 @@ stash_drop(stashid); undo_finish(); } }else if( memcmp(zCmd, "pop", nCmd)==0 ){ - if( g.argc>3 ) usage("stash pop"); + if( g.argc>3 ) usage("pop"); stashid = stash_get_id(0); undo_begin(); stash_apply(stashid, 0); undo_save_stash(stashid); undo_finish(); stash_drop(stashid); }else if( memcmp(zCmd, "apply", nCmd)==0 ){ - if( g.argc>4 ) usage("stash apply STASHID"); + if( g.argc>4 ) usage("apply STASHID"); stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); undo_begin(); stash_apply(stashid, 0); undo_finish(); }else if( memcmp(zCmd, "goto", nCmd)==0 ){ int nConflict; int vid; - if( g.argc>4 ) usage("stash apply STASHID"); + if( g.argc>4 ) usage("apply STASHID"); stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); undo_begin(); vid = db_int(0, "SELECT vid FROM stash WHERE stashid=%d", stashid); nConflict = update_to(vid); stash_apply(stashid, nConflict); @@ -526,20 +526,20 @@ stashid); undo_finish(); }else if( memcmp(zCmd, "diff", nCmd)==0 ){ const char *zDiffCmd = db_get("diff-command", 0); - if( g.argc>4 ) usage("stash diff STASHID"); + if( g.argc>4 ) usage("diff STASHID"); stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); stash_diff(stashid, zDiffCmd); }else if( memcmp(zCmd, "gdiff", nCmd)==0 ){ const char *zDiffCmd = db_get("gdiff-command", 0); - if( g.argc>4 ) usage("stash diff STASHID"); + if( g.argc>4 ) usage("diff STASHID"); stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); stash_diff(stashid, zDiffCmd); }else { usage("SUBCOMMAND ARGS..."); } db_end_transaction(0); } Index: src/style.c ================================================================== --- src/style.c +++ src/style.c @@ -397,10 +397,68 @@ @ vertical-align: top; @ text-align: right; @ padding: 0.2ex 2ex; @ } @ +@ /* Side-by-side diff */ +@ table.sbsdiff { +@ font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace; +@ font-size: 10pt; +@ border-collapse:collapse; +@ white-space: pre; +@ width: 98%; +@ border: 1px #000 dashed; +@ margin-left: auto; +@ margin-right: auto; +@ } +@ +@ table.sbsdiff th.diffhdr { +@ border-bottom: dotted; +@ border-width: 1px; +@ } +@ +@ table.sbsdiff tr td { +@ white-space: pre; +@ padding-left: 3px; +@ padding-right: 3px; +@ margin: 0px; +@ vertical-align: top; +@ } +@ +@ table.sbsdiff tr td.lineno { +@ text-align: right; +@ } +@ +@ table.sbsdiff tr td.srcline { +@ } +@ +@ table.sbsdiff tr td.meta { +@ background-color: rgb(170, 160, 255); +@ text-align: center; +@ } +@ +@ table.sbsdiff tr td.added { +@ background-color: rgb(180, 250, 180); +@ } +@ table.sbsdiff tr td.addedvoid { +@ background-color: rgb(190, 190, 180); +@ } +@ +@ table.sbsdiff tr td.removed { +@ background-color: rgb(250, 130, 130); +@ } +@ table.sbsdiff tr td.removedvoid { +@ background-color: rgb(190, 190, 180); +@ } +@ +@ table.sbsdiff tr td.changed { +@ background-color: rgb(210, 210, 200); +@ } +@ table.sbsdiff tr td.changedvoid { +@ background-color: rgb(190, 190, 180); +@ } +@ ; /* The following table contains bits of default CSS that must ** be included if they are not found in the application-defined @@ -803,14 +861,26 @@ ** WEBPAGE: test_env */ void page_test_env(void){ char c; int i; + int showAll; char zCap[30]; login_check_credentials(); - if( !g.perm.Admin && !g.perm.Setup ){ login_needed(); return; } + if( !g.perm.Admin && !g.perm.Setup && !db_get_boolean("test_env_enable",0) ){ + login_needed(); + return; + } style_header("Environment Test"); + showAll = atoi(PD("showall","0")); + if( !showAll ){ + style_submenu_element("Show Cookies", "Show Cookies", + "%s/test_env?showall=1", g.zTop); + }else{ + style_submenu_element("Hide Cookies", "Hide Cookies", + "%s/test_env", g.zTop); + } #if !defined(_WIN32) @ uid=%d(getuid()), gid=%d(getgid())
#endif @ g.zBaseURL = %h(g.zBaseURL)
@ g.zTop = %h(g.zTop)
@@ -820,12 +890,12 @@ zCap[i] = 0; @ g.userUid = %d(g.userUid)
@ g.zLogin = %h(g.zLogin)
@ capabilities = %s(zCap)
@
- cgi_print_all(); + cgi_print_all(atoi(PD("showall","0"))); if( g.perm.Setup ){ const char *zRedir = P("redirect"); if( zRedir ) cgi_redirect(zRedir); } style_footer(); } Index: src/th.c ================================================================== --- src/th.c +++ src/th.c @@ -1817,12 +1817,12 @@ rc = thSubstWord(interp, pExpr->zValue, pExpr->nValue); }else{ int eArgType = 0; /* Actual type of arguments */ /* Argument values */ - int iLeft; - int iRight; + int iLeft = 0; + int iRight = 0; double fLeft; double fRight; /* Left and right arguments as strings */ char *zLeft = 0; int nLeft = 0; Index: src/timeline.c ================================================================== --- src/timeline.c +++ src/timeline.c @@ -20,11 +20,10 @@ */ #include#include #include "config.h" #include "timeline.h" -#include "cson_amalgamation.h" /* ** Shorten a UUID so that is the minimum length needed to contain ** at least one digit in the range 'a'..'f'. The minimum length is 10. */ @@ -766,24 +765,24 @@ */ const char *timeline_query_for_www(void){ static char *zBase = 0; static const char zBaseSql[] = @ SELECT - @ blob.rid AS blobRid, - @ uuid AS uuid, + @ blob.rid, + @ uuid, @ datetime(event.mtime,'localtime') AS timestamp, - @ coalesce(ecomment, comment) AS comment, - @ coalesce(euser, user) AS user, - @ blob.rid IN leaf AS leaf, - @ bgcolor AS bgColor, - @ event.type AS eventType, + @ coalesce(ecomment, comment), + @ coalesce(euser, user), + @ blob.rid IN leaf, + @ bgcolor, + @ event.type, @ (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref @ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid - @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0) AS tags, - @ tagid AS tagid, - @ brief AS brief, - @ event.mtime AS mtime + @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0), + @ tagid, + @ brief, + @ event.mtime @ FROM event JOIN blob @ WHERE blob.rid=event.objid ; if( zBase==0 ){ zBase = mprintf(zBaseSql, TAG_BRANCH, TAG_BRANCH); @@ -1366,24 +1365,24 @@ ** a timeline query for display on a TTY. */ const char *timeline_query_for_tty(void){ static const char zBaseSql[] = @ SELECT - @ blob.rid AS rid, + @ blob.rid, @ uuid, - @ datetime(event.mtime,'localtime') AS mDateTime, + @ datetime(event.mtime,'localtime'), @ coalesce(ecomment,comment) @ || ' (user: ' || coalesce(euser,user,'?') @ || (SELECT case when length(x)>0 then ' tags: ' || x else '' end @ FROM (SELECT group_concat(substr(tagname,5), ', ') AS x @ FROM tag, tagxref @ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0)) - @ || ')' as comment, - @ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim) AS primPlinkCount, - @ (SELECT count(*) FROM plink WHERE cid=blob.rid) AS plinkCount, - @ event.mtime AS mtime + @ || ')', + @ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim), + @ (SELECT count(*) FROM plink WHERE cid=blob.rid), + @ event.mtime @ FROM event, blob @ WHERE blob.rid=event.objid ; return zBaseSql; } @@ -1426,12 +1425,12 @@ ** ** w = wiki commits only ** ci = file commits only ** t = tickets only ** -** The optional showfiles argument, if specified, prints the list of -** files changed in a checkin after the checkin comment. +** The optional showfiles argument if specified prints the list of +** files changed in a checkin after the checkin comment ** */ void timeline_cmd(void){ Stmt q; int n, k; @@ -1525,10 +1524,11 @@ blob_appendf(&sql, " AND blob.rid IN ok"); } if( zType && (zType[0]!='a') ){ blob_appendf(&sql, " AND event.type=%Q ", zType); } + blob_appendf(&sql, " ORDER BY event.mtime DESC"); db_prepare(&q, blob_str(&sql)); blob_reset(&sql); print_timeline(&q, n, showfilesFlag); db_finalize(&q); Index: src/tkt.c ================================================================== --- src/tkt.c +++ src/tkt.c @@ -855,11 +855,10 @@ ** ** Run the ticket report, identified by the report format title ** used in the gui. The data is written as flat file on stdout, ** using "," as separator. The separator "," can be changed using ** the -l or --limit option. -** ** If TICKETFILTER is given on the commandline, the query is ** limited with a new WHERE-condition. ** example: Report lists a column # with the uuid ** TICKETFILTER may be [#]='uuuuuuuuu' ** example: Report only lists rows with status not open @@ -866,11 +865,11 @@ ** TICKETFILTER: status != 'open' ** If the option -q|--quote is used, the tickets are encoded by ** quoting special chars(space -> \\s, tab -> \\t, newline -> \\n, ** cr -> \\r, formfeed -> \\f, vtab -> \\v, nul -> \\0, \\ -> \\\\). ** Otherwise, the simplified encoding as on the show report raw -** page in the gui is used. This has no effect in JSON mode. +** page in the gui is used. ** ** Instead of the report title its possible to use the report ** number. Using the special report number 0 list all columns, ** defined in the ticket table. ** @@ -960,19 +959,22 @@ usage("show REPORTNR"); }else{ const char *zRep = 0; const char *zSep = 0; const char *zFilterUuid = 0; + zSep = find_option("limit","l",1); zRep = g.argv[3]; if( !strcmp(zRep,"0") ){ zRep = 0; } if( g.argc>4 ){ zFilterUuid = g.argv[4]; } + rptshow( zRep, zSep, zFilterUuid, tktEncoding ); + } }else{ /* add a new ticket or update an existing ticket */ enum { set,add,history,err } eCmd = err; int i = 0; Index: src/wiki.c ================================================================== --- src/wiki.c +++ src/wiki.c @@ -630,27 +630,10 @@ manifest_destroy(pW1); manifest_destroy(pW2); style_footer(); } -/* -** prepare()s pStmt with a query requesting: -** -** - wiki page name -** - tagxref (whatever that really is!) -** -** Used by wcontent_page() and the JSON wiki code. -*/ -void wiki_prepare_page_list( Stmt * pStmt ){ - db_prepare(pStmt, - "SELECT" - " substr(tagname, 6) as name," - " (SELECT value FROM tagxref WHERE tagid=tag.tagid ORDER BY mtime DESC) as tagXref" - " FROM tag WHERE tagname GLOB 'wiki-*'" - " ORDER BY lower(tagname) /*sort*/" - ); -} /* ** WEBPAGE: wcontent ** ** all=1 Show deleted pages ** @@ -667,11 +650,17 @@ style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop); }else{ style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop); } @ - wiki_prepare_page_list(&q); + db_prepare(&q, + "SELECT" + " substr(tagname, 6)," + " (SELECT value FROM tagxref WHERE tagid=tag.tagid ORDER BY mtime DESC)" + " FROM tag WHERE tagname GLOB 'wiki-*'" + " ORDER BY lower(tagname) /*sort*/" + ); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); int size = db_column_int(&q, 1); if( size>0 ){ @
- %h(zName)
@@ -801,15 +790,13 @@ " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'" " ORDER BY x.mtime DESC LIMIT 1", zPageName ); if( rid==0 && !isNew ){ - g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; fossil_fatal("no such wiki page: %s", zPageName); } if( rid!=0 && isNew ){ - g.json.resultCode = FSL_JSON_E_RESOURCE_ALREADY_EXISTS; fossil_fatal("wiki page %s already exists", zPageName); } blob_zero(&wiki); zDate = date_in_standard_format("now"); Index: src/xfer.c ================================================================== --- src/xfer.c +++ src/xfer.c @@ -573,11 +573,11 @@ blob_zero(&combined); blob_copy(&combined, pNonce); blob_append(&combined, blob_buffer(&pw), szPw); sha1sum_blob(&combined, &hash); assert( blob_size(&hash)==40 ); - rc = blob_compare(&hash, pSig); + rc = blob_constant_time_cmp(&hash, pSig); blob_reset(&hash); blob_reset(&combined); if( rc!=0 && szPw!=40 ){ /* If this server stores cleartext passwords and the password did not ** match, then perhaps the client is sending SHA1 passwords. Try @@ -588,11 +588,11 @@ blob_zero(&combined); blob_copy(&combined, pNonce); blob_append(&combined, zSecret, -1); free(zSecret); sha1sum_blob(&combined, &hash); - rc = blob_compare(&hash, pSig); + rc = blob_constant_time_cmp(&hash, pSig); blob_reset(&hash); blob_reset(&combined); } if( rc==0 ){ const char *zCap; Index: test/merge_renames.test ================================================================== --- test/merge_renames.test +++ test/merge_renames.test @@ -3,11 +3,11 @@ # # catch {exec $::fossilexe info} res puts res=$res -if {![regexp {not within an open checkout} $res]} { +if {![regexp {use --repository} $res]} { puts stderr "Cannot run this test within an open checkout" return } ###################################### Index: win/Makefile.dmc ================================================================== --- win/Makefile.dmc +++ win/Makefile.dmc @@ -22,13 +22,13 @@ TCC = $(DMDIR)\bin\dmc $(CFLAGS) $(DMCDEF) $(SSL) $(INCL) LIBS = $(DMDIR)\extra\lib\ zlib wsock32 SQLITE_OPTIONS = -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_STAT3 -Dlocaltime=fossil_localtime -DSQLITE_ENABLE_LOCKING_STYLE=0 -SRC = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_login_.c json_timeline_.c json_wiki_.c leaf_.c login_.c main_.c manifest_.c md5_.c merge_.c merge3_.c name_.c path_.c pivot_.c popen_.c pqueue_.c printf_.c rebuild_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c skins_.c sqlcmd_.c stash_.c stat_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c update_.c url_.c user_.c verify_.c vfile_.c wiki_.c wikiformat_.c winhttp_.c xfer_.c zip_.c +SRC = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c leaf_.c login_.c main_.c manifest_.c md5_.c merge_.c merge3_.c name_.c path_.c pivot_.c popen_.c pqueue_.c printf_.c rebuild_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c skins_.c sqlcmd_.c stash_.c stat_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c update_.c url_.c user_.c verify_.c vfile_.c wiki_.c wikiformat_.c winhttp_.c xfer_.c zip_.c -OBJ = $(OBJDIR)\add$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\file$O $(OBJDIR)\finfo$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\json$O $(OBJDIR)\json_login$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\login$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\rebuild$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\setup$O $(OBJDIR)\sha1$O $(OBJDIR)\shun$O $(OBJDIR)\skins$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O +OBJ = $(OBJDIR)\add$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\file$O $(OBJDIR)\finfo$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\leaf$O $(OBJDIR)\login$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\rebuild$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\setup$O $(OBJDIR)\sha1$O $(OBJDIR)\shun$O $(OBJDIR)\skins$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O RC=$(DMDIR)\bin\rcc RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__ @@ -42,11 +42,11 @@ $(OBJDIR)\fossil.res: $B\win\fossil.rc $(RC) $(RCFLAGS) -o$@ $** $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res - +echo add allrepo attach bag bisect blob branch browse captcha cgi checkin checkout clearsign clone comformat configure content db delta deltacmd descendants diff diffcmd doc encode event export file finfo glob graph gzip http http_socket http_ssl http_transport import info json json_login json_timeline json_wiki leaf login main manifest md5 merge merge3 name path pivot popen pqueue printf rebuild report rss schema search setup sha1 shun skins sqlcmd stash stat style sync tag tar th_main timeline tkt tktsetup undo update url user verify vfile wiki wikiformat winhttp xfer zip shell sqlite3 th th_lang > $@ + +echo add allrepo attach bag bisect blob branch browse captcha cgi checkin checkout clearsign clone comformat configure content db delta deltacmd descendants diff diffcmd doc encode event export file finfo glob graph gzip http http_socket http_ssl http_transport import info leaf login main manifest md5 merge merge3 name path pivot popen pqueue printf rebuild report rss schema search setup sha1 shun skins sqlcmd stash stat style sync tag tar th_main timeline tkt tktsetup undo update url user verify vfile wiki wikiformat winhttp xfer zip shell sqlite3 th th_lang > $@ +echo fossil >> $@ +echo fossil >> $@ +echo $(LIBS) >> $@ +echo. >> $@ +echo fossil >> $@ @@ -73,13 +73,10 @@ $(TCC) -o$@ -c $** $(OBJDIR)\th_lang$O : $(SRCDIR)\th_lang.c $(TCC) -o$@ -c $** -$(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h - cp $@ $@ - VERSION.h : version$E $B\manifest.uuid $B\manifest $B\VERSION +$** > $@ page_index.h: mkindex$E $(SRC) +$** > $@ @@ -88,16 +85,10 @@ -del $(OBJDIR)\*.obj -del *.obj *_.c *.h *.map realclean: -del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E - -$(OBJDIR)\json$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h - $(OBJDIR)\add$O : add_.c add.h $(TCC) -o$@ -c add_.c @@ -324,34 +315,10 @@ $(TCC) -o$@ -c info_.c info_.c : $(SRCDIR)\info.c +translate$E $** > $@ -$(OBJDIR)\json$O : json_.c json.h - $(TCC) -o$@ -c json_.c - -json_.c : $(SRCDIR)\json.c - +translate$E $** > $@ - -$(OBJDIR)\json_login$O : json_login_.c json_login.h - $(TCC) -o$@ -c json_login_.c - -json_login_.c : $(SRCDIR)\json_login.c - +translate$E $** > $@ - -$(OBJDIR)\json_timeline$O : json_timeline_.c json_timeline.h - $(TCC) -o$@ -c json_timeline_.c - -json_timeline_.c : $(SRCDIR)\json_timeline.c - +translate$E $** > $@ - -$(OBJDIR)\json_wiki$O : json_wiki_.c json_wiki.h - $(TCC) -o$@ -c json_wiki_.c - -json_wiki_.c : $(SRCDIR)\json_wiki.c - +translate$E $** > $@ - $(OBJDIR)\leaf$O : leaf_.c leaf.h $(TCC) -o$@ -c leaf_.c leaf_.c : $(SRCDIR)\leaf.c +translate$E $** > $@ @@ -613,7 +580,7 @@ zip_.c : $(SRCDIR)\zip.c +translate$E $** > $@ headers: makeheaders$E page_index.h VERSION.h - +makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_login_.c:json_login.h json_timeline_.c:json_timeline.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h path_.c:path.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h + +makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h leaf_.c:leaf.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h path_.c:path.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h @copy /Y nul: headers Index: win/Makefile.mingw ================================================================== --- win/Makefile.mingw +++ win/Makefile.mingw @@ -110,14 +110,10 @@ $(SRCDIR)/http_socket.c \ $(SRCDIR)/http_ssl.c \ $(SRCDIR)/http_transport.c \ $(SRCDIR)/import.c \ $(SRCDIR)/info.c \ - $(SRCDIR)/json.c \ - $(SRCDIR)/json_login.c \ - $(SRCDIR)/json_timeline.c \ - $(SRCDIR)/json_wiki.c \ $(SRCDIR)/leaf.c \ $(SRCDIR)/login.c \ $(SRCDIR)/main.c \ $(SRCDIR)/manifest.c \ $(SRCDIR)/md5.c \ @@ -198,14 +194,10 @@ $(OBJDIR)/http_socket_.c \ $(OBJDIR)/http_ssl_.c \ $(OBJDIR)/http_transport_.c \ $(OBJDIR)/import_.c \ $(OBJDIR)/info_.c \ - $(OBJDIR)/json_.c \ - $(OBJDIR)/json_login_.c \ - $(OBJDIR)/json_timeline_.c \ - $(OBJDIR)/json_wiki_.c \ $(OBJDIR)/leaf_.c \ $(OBJDIR)/login_.c \ $(OBJDIR)/main_.c \ $(OBJDIR)/manifest_.c \ $(OBJDIR)/md5_.c \ @@ -286,14 +278,10 @@ $(OBJDIR)/http_socket.o \ $(OBJDIR)/http_ssl.o \ $(OBJDIR)/http_transport.o \ $(OBJDIR)/import.o \ $(OBJDIR)/info.o \ - $(OBJDIR)/json.o \ - $(OBJDIR)/json_login.o \ - $(OBJDIR)/json_timeline.o \ - $(OBJDIR)/json_wiki.o \ $(OBJDIR)/leaf.o \ $(OBJDIR)/login.o \ $(OBJDIR)/main.o \ $(OBJDIR)/manifest.o \ $(OBJDIR)/md5.o \ @@ -375,11 +363,11 @@ $(TCLSH) test/tester.tcl $(APPNAME) $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(VERSION) $(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h -EXTRAOBJ = $(OBJDIR)/sqlite3.o $(OBJDIR)/shell.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o $(OBJDIR)/cson_amalgamation.o +EXTRAOBJ = $(OBJDIR)/sqlite3.o $(OBJDIR)/shell.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o $(APPNAME): $(OBJDIR)/headers $(OBJ) $(EXTRAOBJ) $(OBJDIR)/icon.o $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) $(OBJDIR)/icon.o # This rule prevents make from using its default rules to try build @@ -400,11 +388,11 @@ $(OBJDIR)/page_index.h: $(TRANS_SRC) $(OBJDIR)/mkindex $(MKINDEX) $(TRANS_SRC) >$@ $(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/makeheaders $(OBJDIR)/VERSION.h - $(MAKEHEADERS) $(OBJDIR)/add_.c:$(OBJDIR)/add.h $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h $(OBJDIR)/configure_.c:$(OBJDIR)/configure.h $(OBJDIR)/content_.c:$(OBJDIR)/content.h $(OBJDIR)/db_.c:$(OBJDIR)/db.h $(OBJDIR)/delta_.c:$(OBJDIR)/delta.h $(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h $(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h $(OBJDIR)/event_.c:$(OBJDIR)/event.h $(OBJDIR)/export_.c:$(OBJDIR)/export.h $(OBJDIR)/file_.c:$(OBJDIR)/file.h $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h $(OBJDIR)/http_.c:$(OBJDIR)/http.h $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h $(OBJDIR)/import_.c:$(OBJDIR)/import.h $(OBJDIR)/info_.c:$(OBJDIR)/info.h $(OBJDIR)/json_.c:$(OBJDIR)/json.h $(OBJDIR)/json_login_.c:$(OBJDIR)/json_login.h $(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h $(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h $(OBJDIR)/login_.c:$(OBJDIR)/login.h $(OBJDIR)/main_.c:$(OBJDIR)/main.h $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h $(OBJDIR)/name_.c:$(OBJDIR)/name.h $(OBJDIR)/path_.c:$(OBJDIR)/path.h $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h $(OBJDIR)/report_.c:$(OBJDIR)/report.h $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h $(OBJDIR)/search_.c:$(OBJDIR)/search.h $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h $(OBJDIR)/style_.c:$(OBJDIR)/style.h $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h $(OBJDIR)/update_.c:$(OBJDIR)/update.h $(OBJDIR)/url_.c:$(OBJDIR)/url.h $(OBJDIR)/user_.c:$(OBJDIR)/user.h $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h $(OBJDIR)/VERSION.h + $(MAKEHEADERS) $(OBJDIR)/add_.c:$(OBJDIR)/add.h $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h $(OBJDIR)/configure_.c:$(OBJDIR)/configure.h $(OBJDIR)/content_.c:$(OBJDIR)/content.h $(OBJDIR)/db_.c:$(OBJDIR)/db.h $(OBJDIR)/delta_.c:$(OBJDIR)/delta.h $(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h $(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h $(OBJDIR)/event_.c:$(OBJDIR)/event.h $(OBJDIR)/export_.c:$(OBJDIR)/export.h $(OBJDIR)/file_.c:$(OBJDIR)/file.h $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h $(OBJDIR)/http_.c:$(OBJDIR)/http.h $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h $(OBJDIR)/import_.c:$(OBJDIR)/import.h $(OBJDIR)/info_.c:$(OBJDIR)/info.h $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h $(OBJDIR)/login_.c:$(OBJDIR)/login.h $(OBJDIR)/main_.c:$(OBJDIR)/main.h $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h $(OBJDIR)/name_.c:$(OBJDIR)/name.h $(OBJDIR)/path_.c:$(OBJDIR)/path.h $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h $(OBJDIR)/report_.c:$(OBJDIR)/report.h $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h $(OBJDIR)/search_.c:$(OBJDIR)/search.h $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h $(OBJDIR)/style_.c:$(OBJDIR)/style.h $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h $(OBJDIR)/update_.c:$(OBJDIR)/update.h $(OBJDIR)/url_.c:$(OBJDIR)/url.h $(OBJDIR)/user_.c:$(OBJDIR)/user.h $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h $(OBJDIR)/VERSION.h echo Done >$(OBJDIR)/headers $(OBJDIR)/headers: Makefile Makefile: $(OBJDIR)/add_.c: $(SRCDIR)/add.c $(OBJDIR)/translate @@ -671,38 +659,10 @@ $(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c info.h: $(OBJDIR)/headers -$(OBJDIR)/json_.c: $(SRCDIR)/json.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json.c >$(OBJDIR)/json_.c - -$(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/json.o -c $(OBJDIR)/json_.c - -json.h: $(OBJDIR)/headers -$(OBJDIR)/json_login_.c: $(SRCDIR)/json_login.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_login.c >$(OBJDIR)/json_login_.c - -$(OBJDIR)/json_login.o: $(OBJDIR)/json_login_.c $(OBJDIR)/json_login.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/json_login.o -c $(OBJDIR)/json_login_.c - -json_login.h: $(OBJDIR)/headers -$(OBJDIR)/json_timeline_.c: $(SRCDIR)/json_timeline.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_timeline.c >$(OBJDIR)/json_timeline_.c - -$(OBJDIR)/json_timeline.o: $(OBJDIR)/json_timeline_.c $(OBJDIR)/json_timeline.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/json_timeline.o -c $(OBJDIR)/json_timeline_.c - -json_timeline.h: $(OBJDIR)/headers -$(OBJDIR)/json_wiki_.c: $(SRCDIR)/json_wiki.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_wiki.c >$(OBJDIR)/json_wiki_.c - -$(OBJDIR)/json_wiki.o: $(OBJDIR)/json_wiki_.c $(OBJDIR)/json_wiki.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/json_wiki.o -c $(OBJDIR)/json_wiki_.c - -json_wiki.h: $(OBJDIR)/headers $(OBJDIR)/leaf_.c: $(SRCDIR)/leaf.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/leaf.c >$(OBJDIR)/leaf_.c $(OBJDIR)/leaf.o: $(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/leaf.o -c $(OBJDIR)/leaf_.c @@ -1010,18 +970,14 @@ zip.h: $(OBJDIR)/headers $(OBJDIR)/sqlite3.o: $(SRCDIR)/sqlite3.c $(XTCC) -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_STAT3 -Dlocaltime=fossil_localtime -DSQLITE_ENABLE_LOCKING_STYLE=0 -c $(SRCDIR)/sqlite3.c -o $(OBJDIR)/sqlite3.o -$(OBJDIR)/cson_amalgamation.o: $(SRCDIR)/cson_amalgamation.c - $(XTCC) -c $(SRCDIR)/cson_amalgamation.c -o $(OBJDIR)/cson_amalgamation.o - -$(OBJDIR)/json.o $(OBJDIR)/json_login.o $(OBJDIR)/json_wiki.o $(OBJDIR)/json_timeline.o : $(SRCDIR)/json_detail.h $(OBJDIR)/shell.o: $(SRCDIR)/shell.c $(SRCDIR)/sqlite3.h $(XTCC) -Dmain=sqlite3_shell -DSQLITE_OMIT_LOAD_EXTENSION=1 -c $(SRCDIR)/shell.c -o $(OBJDIR)/shell.o $(OBJDIR)/th.o: $(SRCDIR)/th.c $(XTCC) -I$(SRCDIR) -c $(SRCDIR)/th.c -o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o: $(SRCDIR)/th_lang.c $(XTCC) -I$(SRCDIR) -c $(SRCDIR)/th_lang.c -o $(OBJDIR)/th_lang.o Index: win/Makefile.msc ================================================================== --- win/Makefile.msc +++ win/Makefile.msc @@ -36,13 +36,13 @@ LIBS = $(ZLIB) ws2_32.lib advapi32.lib $(SSLLIB) LIBDIR = -LIBPATH:$(MSCDIR)\extra\lib -LIBPATH:$(ZLIBDIR) SQLITE_OPTIONS = /DSQLITE_OMIT_LOAD_EXTENSION=1 /DSQLITE_THREADSAFE=0 /DSQLITE_DEFAULT_FILE_FORMAT=4 /DSQLITE_ENABLE_STAT3 /Dlocaltime=fossil_localtime /DSQLITE_ENABLE_LOCKING_STYLE=0 -SRC = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_login_.c json_timeline_.c json_wiki_.c leaf_.c login_.c main_.c manifest_.c md5_.c merge_.c merge3_.c name_.c path_.c pivot_.c popen_.c pqueue_.c printf_.c rebuild_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c skins_.c sqlcmd_.c stash_.c stat_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c update_.c url_.c user_.c verify_.c vfile_.c wiki_.c wikiformat_.c winhttp_.c xfer_.c zip_.c +SRC = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c leaf_.c login_.c main_.c manifest_.c md5_.c merge_.c merge3_.c name_.c path_.c pivot_.c popen_.c pqueue_.c printf_.c rebuild_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c skins_.c sqlcmd_.c stash_.c stat_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c update_.c url_.c user_.c verify_.c vfile_.c wiki_.c wikiformat_.c winhttp_.c xfer_.c zip_.c -OBJ = $(OX)\add$O $(OX)\allrepo$O $(OX)\attach$O $(OX)\bag$O $(OX)\bisect$O $(OX)\blob$O $(OX)\branch$O $(OX)\browse$O $(OX)\captcha$O $(OX)\cgi$O $(OX)\checkin$O $(OX)\checkout$O $(OX)\clearsign$O $(OX)\clone$O $(OX)\comformat$O $(OX)\configure$O $(OX)\content$O $(OX)\db$O $(OX)\delta$O $(OX)\deltacmd$O $(OX)\descendants$O $(OX)\diff$O $(OX)\diffcmd$O $(OX)\doc$O $(OX)\encode$O $(OX)\event$O $(OX)\export$O $(OX)\file$O $(OX)\finfo$O $(OX)\glob$O $(OX)\graph$O $(OX)\gzip$O $(OX)\http$O $(OX)\http_socket$O $(OX)\http_ssl$O $(OX)\http_transport$O $(OX)\import$O $(OX)\info$O $(OX)\json$O $(OX)\json_login$O $(OX)\json_timeline$O $(OX)\json_wiki$O $(OX)\leaf$O $(OX)\login$O $(OX)\main$O $(OX)\manifest$O $(OX)\md5$O $(OX)\merge$O $(OX)\merge3$O $(OX)\name$O $(OX)\path$O $(OX)\pivot$O $(OX)\popen$O $(OX)\pqueue$O $(OX)\printf$O $(OX)\rebuild$O $(OX)\report$O $(OX)\rss$O $(OX)\schema$O $(OX)\search$O $(OX)\setup$O $(OX)\sha1$O $(OX)\shun$O $(OX)\skins$O $(OX)\sqlcmd$O $(OX)\stash$O $(OX)\stat$O $(OX)\style$O $(OX)\sync$O $(OX)\tag$O $(OX)\tar$O $(OX)\th_main$O $(OX)\timeline$O $(OX)\tkt$O $(OX)\tktsetup$O $(OX)\undo$O $(OX)\update$O $(OX)\url$O $(OX)\user$O $(OX)\verify$O $(OX)\vfile$O $(OX)\wiki$O $(OX)\wikiformat$O $(OX)\winhttp$O $(OX)\xfer$O $(OX)\zip$O $(OX)\shell$O $(OX)\sqlite3$O $(OX)\th$O $(OX)\th_lang$O +OBJ = $(OX)\add$O $(OX)\allrepo$O $(OX)\attach$O $(OX)\bag$O $(OX)\bisect$O $(OX)\blob$O $(OX)\branch$O $(OX)\browse$O $(OX)\captcha$O $(OX)\cgi$O $(OX)\checkin$O $(OX)\checkout$O $(OX)\clearsign$O $(OX)\clone$O $(OX)\comformat$O $(OX)\configure$O $(OX)\content$O $(OX)\db$O $(OX)\delta$O $(OX)\deltacmd$O $(OX)\descendants$O $(OX)\diff$O $(OX)\diffcmd$O $(OX)\doc$O $(OX)\encode$O $(OX)\event$O $(OX)\export$O $(OX)\file$O $(OX)\finfo$O $(OX)\glob$O $(OX)\graph$O $(OX)\gzip$O $(OX)\http$O $(OX)\http_socket$O $(OX)\http_ssl$O $(OX)\http_transport$O $(OX)\import$O $(OX)\info$O $(OX)\leaf$O $(OX)\login$O $(OX)\main$O $(OX)\manifest$O $(OX)\md5$O $(OX)\merge$O $(OX)\merge3$O $(OX)\name$O $(OX)\path$O $(OX)\pivot$O $(OX)\popen$O $(OX)\pqueue$O $(OX)\printf$O $(OX)\rebuild$O $(OX)\report$O $(OX)\rss$O $(OX)\schema$O $(OX)\search$O $(OX)\setup$O $(OX)\sha1$O $(OX)\shun$O $(OX)\skins$O $(OX)\sqlcmd$O $(OX)\stash$O $(OX)\stat$O $(OX)\style$O $(OX)\sync$O $(OX)\tag$O $(OX)\tar$O $(OX)\th_main$O $(OX)\timeline$O $(OX)\tkt$O $(OX)\tktsetup$O $(OX)\undo$O $(OX)\update$O $(OX)\url$O $(OX)\user$O $(OX)\verify$O $(OX)\vfile$O $(OX)\wiki$O $(OX)\wikiformat$O $(OX)\winhttp$O $(OX)\xfer$O $(OX)\zip$O $(OX)\shell$O $(OX)\sqlite3$O $(OX)\th$O $(OX)\th_lang$O APPNAME = $(OX)\fossil$(E) all: $(OX) $(APPNAME) @@ -88,14 +88,10 @@ echo $(OX)\http_socket.obj >> $@ echo $(OX)\http_ssl.obj >> $@ echo $(OX)\http_transport.obj >> $@ echo $(OX)\import.obj >> $@ echo $(OX)\info.obj >> $@ - echo $(OX)\json.obj >> $@ - echo $(OX)\json_login.obj >> $@ - echo $(OX)\json_timeline.obj >> $@ - echo $(OX)\json_wiki.obj >> $@ echo $(OX)\leaf.obj >> $@ echo $(OX)\login.obj >> $@ echo $(OX)\main.obj >> $@ echo $(OX)\manifest.obj >> $@ echo $(OX)\md5.obj >> $@ @@ -174,12 +170,10 @@ $(OX)\th_lang$O : $(SRCDIR)\th_lang.c $(TCC) /Fo$@ -c $** VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION $** > $@ -$(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h - cp $(SRCDIR)\cson_amalgamation.h $@ page_index.h: mkindex$E $(SRC) $** > $@ clean: @@ -188,15 +182,10 @@ -del headers linkopts realclean: -del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E -$(OBJDIR)\json$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h - $(OX)\add$O : add_.c add.h $(TCC) /Fo$@ -c add_.c add_.c : $(SRCDIR)\add.c @@ -422,34 +411,10 @@ $(TCC) /Fo$@ -c info_.c info_.c : $(SRCDIR)\info.c translate$E $** > $@ -$(OX)\json$O : json_.c json.h - $(TCC) /Fo$@ -c json_.c - -json_.c : $(SRCDIR)\json.c - translate$E $** > $@ - -$(OX)\json_login$O : json_login_.c json_login.h - $(TCC) /Fo$@ -c json_login_.c - -json_login_.c : $(SRCDIR)\json_login.c - translate$E $** > $@ - -$(OX)\json_timeline$O : json_timeline_.c json_timeline.h - $(TCC) /Fo$@ -c json_timeline_.c - -json_timeline_.c : $(SRCDIR)\json_timeline.c - translate$E $** > $@ - -$(OX)\json_wiki$O : json_wiki_.c json_wiki.h - $(TCC) /Fo$@ -c json_wiki_.c - -json_wiki_.c : $(SRCDIR)\json_wiki.c - translate$E $** > $@ - $(OX)\leaf$O : leaf_.c leaf.h $(TCC) /Fo$@ -c leaf_.c leaf_.c : $(SRCDIR)\leaf.c translate$E $** > $@ @@ -711,7 +676,7 @@ zip_.c : $(SRCDIR)\zip.c translate$E $** > $@ headers: makeheaders$E page_index.h VERSION.h - makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_login_.c:json_login.h json_timeline_.c:json_timeline.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h path_.c:path.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h + makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h leaf_.c:leaf.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h path_.c:path.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h @copy /Y nul: headers Index: www/quotes.wiki ================================================================== --- www/quotes.wiki +++ www/quotes.wiki @@ -85,6 +85,19 @@ been a smoother ride than Git was.viablepanic at [http://www.reddit.com/r/programming/comments/bxcto/why_not_fossil_scm/]+ +- In the fossil community - and hence in fossil itself - development history +is pretty much sacrosanct. The very name "fossil" was to chosen to +reflect the unchanging nature of things in that history. + +
In git (or rather, the git community), the development history is part of +the published aspect of the project, so it provides tools for rearranging +that history so you can present what you "should" have done rather +than what you actually did. + +
+Mike Meyer on the Fossil mailing list, 2011-10-04 +