Fossil SCM
Add the artifact_to_json(rid|symbolic-name) UDF. Emit check-ins with no P- or F-card as having an empty array for those cards, in the hope of simplifying host-language traversal over the results (no need to check for existence of the cards before traversal).
Commit
4416f09b420cae1486a4c02e72b954fa7204dc3308d90a3ba3f75bffefacab2d
Parent
069995d7eaf52c6…
2 files changed
+2
+81
-12
M
src/db.c
+2
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -1629,10 +1629,12 @@ | ||
| 1629 | 1629 | sqlite3_create_function(db, "chat_msg_from_event", 4, |
| 1630 | 1630 | SQLITE_UTF8 | SQLITE_INNOCUOUS, 0, |
| 1631 | 1631 | chat_msg_from_event, 0, 0); |
| 1632 | 1632 | sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0, |
| 1633 | 1633 | file_inode_sql_func,0,0); |
| 1634 | + sqlite3_create_function(db, "artifact_to_json", 1, SQLITE_UTF8, 0, | |
| 1635 | + artifact_to_json_sql_func,0,0); | |
| 1634 | 1636 | |
| 1635 | 1637 | } |
| 1636 | 1638 | |
| 1637 | 1639 | #if USE_SEE |
| 1638 | 1640 | /* |
| 1639 | 1641 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -1629,10 +1629,12 @@ | |
| 1629 | sqlite3_create_function(db, "chat_msg_from_event", 4, |
| 1630 | SQLITE_UTF8 | SQLITE_INNOCUOUS, 0, |
| 1631 | chat_msg_from_event, 0, 0); |
| 1632 | sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0, |
| 1633 | file_inode_sql_func,0,0); |
| 1634 | |
| 1635 | } |
| 1636 | |
| 1637 | #if USE_SEE |
| 1638 | /* |
| 1639 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -1629,10 +1629,12 @@ | |
| 1629 | sqlite3_create_function(db, "chat_msg_from_event", 4, |
| 1630 | SQLITE_UTF8 | SQLITE_INNOCUOUS, 0, |
| 1631 | chat_msg_from_event, 0, 0); |
| 1632 | sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0, |
| 1633 | file_inode_sql_func,0,0); |
| 1634 | sqlite3_create_function(db, "artifact_to_json", 1, SQLITE_UTF8, 0, |
| 1635 | artifact_to_json_sql_func,0,0); |
| 1636 | |
| 1637 | } |
| 1638 | |
| 1639 | #if USE_SEE |
| 1640 | /* |
| 1641 |
+81
-12
| --- src/manifest.c | ||
| +++ src/manifest.c | ||
| @@ -2931,11 +2931,14 @@ | ||
| 2931 | 2931 | } |
| 2932 | 2932 | return NULL; |
| 2933 | 2933 | } |
| 2934 | 2934 | |
| 2935 | 2935 | /* |
| 2936 | -** Creates a JSON representation of p, appending it to pOut. | |
| 2936 | +** Creates a JSON representation of p, appending it to b. | |
| 2937 | +** | |
| 2938 | +** b is not cleared before rendering, so the caller needs to do that | |
| 2939 | +** if it's important for their use case. | |
| 2937 | 2940 | ** |
| 2938 | 2941 | ** Pedantic note: this routine traverses p->aFile directly, rather than |
| 2939 | 2942 | ** using manifest_file_next(), so that delta manifests are rendered as-is |
| 2940 | 2943 | ** instead of having their derived files. If that policy is ever changed, |
| 2941 | 2944 | ** p will need to be non-const. |
| @@ -2991,10 +2994,13 @@ | ||
| 2991 | 2994 | KVP_STR(1, uuid, pF->zUuid); |
| 2992 | 2995 | KVP_STR(1, perm, pF->zPerm); |
| 2993 | 2996 | KVP_STR(1, oldName, pF->zPrior); |
| 2994 | 2997 | blob_append_char(b, '}'); |
| 2995 | 2998 | } |
| 2999 | + /* Special case: model checkins with no F-card as having an empty | |
| 3000 | + ** array, rather than no F-cards, to hypothetically simplify | |
| 3001 | + ** handling in JSON queries. */ | |
| 2996 | 3002 | blob_append_char(b, ']'); |
| 2997 | 3003 | } |
| 2998 | 3004 | CARD_STR2(G, p->zThreadRoot); |
| 2999 | 3005 | CARD_STR2(H, p->zThreadTitle); |
| 3000 | 3006 | CARD_STR2(I, p->zInReplyTo); |
| @@ -3020,17 +3026,22 @@ | ||
| 3020 | 3026 | blob_appendf(b, "%!j", p->azCChild[i]); |
| 3021 | 3027 | } |
| 3022 | 3028 | blob_append_char(b, ']'); |
| 3023 | 3029 | } |
| 3024 | 3030 | CARD_STR2(N, p->zMimetype); |
| 3025 | - if( p->nParent ){ | |
| 3031 | + ISA( CFTYPE_MANIFEST ){ | |
| 3026 | 3032 | CARD_LETTER(P); |
| 3027 | 3033 | blob_append_char(b, '['); |
| 3028 | - for( i = 0; i < p->nParent; ++i ){ | |
| 3029 | - if( i>0 ) blob_append_char(b, ','); | |
| 3030 | - blob_appendf(b, "%!j", p->azParent[i]); | |
| 3034 | + if( p->nParent ){ | |
| 3035 | + for( i = 0; i < p->nParent; ++i ){ | |
| 3036 | + if( i>0 ) blob_append_char(b, ','); | |
| 3037 | + blob_appendf(b, "%!j", p->azParent[i]); | |
| 3038 | + } | |
| 3031 | 3039 | } |
| 3040 | + /* Special case: model checkins with no P-card as having | |
| 3041 | + ** an empty array, rather than no P-card, to hypothetically | |
| 3042 | + ** simplify handling in JSON queries. */ | |
| 3032 | 3043 | blob_append_char(b, ']'); |
| 3033 | 3044 | } |
| 3034 | 3045 | if( p->nCherrypick ){ |
| 3035 | 3046 | CARD_LETTER(Q); |
| 3036 | 3047 | blob_append_char(b, '['); |
| @@ -3075,10 +3086,26 @@ | ||
| 3075 | 3086 | #undef CARD_STR2 |
| 3076 | 3087 | #undef ISA |
| 3077 | 3088 | #undef KVP_STR |
| 3078 | 3089 | #undef STR_OR_NULL |
| 3079 | 3090 | } |
| 3091 | + | |
| 3092 | +/* | |
| 3093 | +** Convenience wrapper around artifact_to_json() which expects rid to | |
| 3094 | +** be the blob.rid of any artifact type. If it can load a Manifest | |
| 3095 | +** with that rid, it returns rid, else it returns 0. | |
| 3096 | +*/ | |
| 3097 | +int artifact_to_json_by_rid(int rid, Blob *pOut){ | |
| 3098 | + Manifest * const p = manifest_get(rid, CFTYPE_ANY, 0); | |
| 3099 | + if( p ){ | |
| 3100 | + artifact_to_json(p, pOut); | |
| 3101 | + manifest_destroy(p); | |
| 3102 | + }else{ | |
| 3103 | + rid = 0; | |
| 3104 | + } | |
| 3105 | + return rid; | |
| 3106 | +} | |
| 3080 | 3107 | |
| 3081 | 3108 | /* |
| 3082 | 3109 | ** Convenience wrapper around artifact_to_json() which accepts any |
| 3083 | 3110 | ** artifact name which is legal for symbolic_name_to_rid(). On success |
| 3084 | 3111 | ** it returns the rid of the artifact. Returns 0 if no such artifact |
| @@ -3086,21 +3113,63 @@ | ||
| 3086 | 3113 | ** |
| 3087 | 3114 | ** pOut is not cleared before rendering, so the caller needs to do |
| 3088 | 3115 | ** that if it's important for their use case. |
| 3089 | 3116 | */ |
| 3090 | 3117 | int artifact_to_json_by_name(const char *zName, Blob *pOut){ |
| 3091 | - int rid; | |
| 3118 | + const int rid = symbolic_name_to_rid(zName, 0); | |
| 3119 | + return rid>0 | |
| 3120 | + ? artifact_to_json_by_rid(rid, pOut) | |
| 3121 | + : rid; | |
| 3122 | +} | |
| 3123 | + | |
| 3124 | +/* | |
| 3125 | +** SQLite UDF for artifact_to_json(). Its single argument should be | |
| 3126 | +** either an INTEGER (blob.rid value) or a TEXT symbolic artifact | |
| 3127 | +** name, as per symbolic_name_to_rid(). If an artifact is found then | |
| 3128 | +** the result of the UDF is that JSON as a string, else it evaluates | |
| 3129 | +** to NULL. | |
| 3130 | +*/ | |
| 3131 | +void artifact_to_json_sql_func( | |
| 3132 | + sqlite3_context *context, | |
| 3133 | + int argc, | |
| 3134 | + sqlite3_value **argv | |
| 3135 | +){ | |
| 3136 | + int rid = 0; | |
| 3137 | + Blob b = empty_blob; | |
| 3092 | 3138 | |
| 3093 | - rid = symbolic_name_to_rid(zName, 0); | |
| 3139 | + if(1 != argc){ | |
| 3140 | + goto error_usage; | |
| 3141 | + } | |
| 3142 | + switch( sqlite3_value_type(argv[0]) ){ | |
| 3143 | + case SQLITE_INTEGER: | |
| 3144 | + rid = artifact_to_json_by_rid(sqlite3_value_int(argv[0]), &b); | |
| 3145 | + break; | |
| 3146 | + case SQLITE_TEXT:{ | |
| 3147 | + const char * z = (const char *)sqlite3_value_text(argv[0]); | |
| 3148 | + if( z ){ | |
| 3149 | + rid = artifact_to_json_by_name(z, &b); | |
| 3150 | + } | |
| 3151 | + break; | |
| 3152 | + } | |
| 3153 | + default: | |
| 3154 | + goto error_usage; | |
| 3155 | + } | |
| 3094 | 3156 | if( rid>0 ){ |
| 3095 | - Manifest * const p = manifest_get(rid, CFTYPE_ANY, 0); | |
| 3096 | - assert(p && "Is it possible to fail this if rid is a phantom?"); | |
| 3097 | - artifact_to_json(p, pOut); | |
| 3098 | - manifest_destroy(p); | |
| 3157 | + sqlite3_result_text(context, blob_str(&b), blob_size(&b), | |
| 3158 | + SQLITE_TRANSIENT); | |
| 3159 | + blob_reset(&b); | |
| 3160 | + }else{ | |
| 3161 | + /* We should arguably error out if rid<0 (ambiguous name) */ | |
| 3162 | + sqlite3_result_null(context); | |
| 3099 | 3163 | } |
| 3100 | - return rid; | |
| 3164 | + return; | |
| 3165 | +error_usage: | |
| 3166 | + sqlite3_result_error(context, "Expecting one argument: blob.rid or " | |
| 3167 | + "artifact symbolic name", -1); | |
| 3101 | 3168 | } |
| 3169 | + | |
| 3170 | + | |
| 3102 | 3171 | |
| 3103 | 3172 | /* |
| 3104 | 3173 | ** COMMAND: test-artifact-to-json |
| 3105 | 3174 | ** |
| 3106 | 3175 | ** Usage: %fossil test-artifact-to-json ?-pretty? symbolic-name [...names] |
| 3107 | 3176 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -2931,11 +2931,14 @@ | |
| 2931 | } |
| 2932 | return NULL; |
| 2933 | } |
| 2934 | |
| 2935 | /* |
| 2936 | ** Creates a JSON representation of p, appending it to pOut. |
| 2937 | ** |
| 2938 | ** Pedantic note: this routine traverses p->aFile directly, rather than |
| 2939 | ** using manifest_file_next(), so that delta manifests are rendered as-is |
| 2940 | ** instead of having their derived files. If that policy is ever changed, |
| 2941 | ** p will need to be non-const. |
| @@ -2991,10 +2994,13 @@ | |
| 2991 | KVP_STR(1, uuid, pF->zUuid); |
| 2992 | KVP_STR(1, perm, pF->zPerm); |
| 2993 | KVP_STR(1, oldName, pF->zPrior); |
| 2994 | blob_append_char(b, '}'); |
| 2995 | } |
| 2996 | blob_append_char(b, ']'); |
| 2997 | } |
| 2998 | CARD_STR2(G, p->zThreadRoot); |
| 2999 | CARD_STR2(H, p->zThreadTitle); |
| 3000 | CARD_STR2(I, p->zInReplyTo); |
| @@ -3020,17 +3026,22 @@ | |
| 3020 | blob_appendf(b, "%!j", p->azCChild[i]); |
| 3021 | } |
| 3022 | blob_append_char(b, ']'); |
| 3023 | } |
| 3024 | CARD_STR2(N, p->zMimetype); |
| 3025 | if( p->nParent ){ |
| 3026 | CARD_LETTER(P); |
| 3027 | blob_append_char(b, '['); |
| 3028 | for( i = 0; i < p->nParent; ++i ){ |
| 3029 | if( i>0 ) blob_append_char(b, ','); |
| 3030 | blob_appendf(b, "%!j", p->azParent[i]); |
| 3031 | } |
| 3032 | blob_append_char(b, ']'); |
| 3033 | } |
| 3034 | if( p->nCherrypick ){ |
| 3035 | CARD_LETTER(Q); |
| 3036 | blob_append_char(b, '['); |
| @@ -3075,10 +3086,26 @@ | |
| 3075 | #undef CARD_STR2 |
| 3076 | #undef ISA |
| 3077 | #undef KVP_STR |
| 3078 | #undef STR_OR_NULL |
| 3079 | } |
| 3080 | |
| 3081 | /* |
| 3082 | ** Convenience wrapper around artifact_to_json() which accepts any |
| 3083 | ** artifact name which is legal for symbolic_name_to_rid(). On success |
| 3084 | ** it returns the rid of the artifact. Returns 0 if no such artifact |
| @@ -3086,21 +3113,63 @@ | |
| 3086 | ** |
| 3087 | ** pOut is not cleared before rendering, so the caller needs to do |
| 3088 | ** that if it's important for their use case. |
| 3089 | */ |
| 3090 | int artifact_to_json_by_name(const char *zName, Blob *pOut){ |
| 3091 | int rid; |
| 3092 | |
| 3093 | rid = symbolic_name_to_rid(zName, 0); |
| 3094 | if( rid>0 ){ |
| 3095 | Manifest * const p = manifest_get(rid, CFTYPE_ANY, 0); |
| 3096 | assert(p && "Is it possible to fail this if rid is a phantom?"); |
| 3097 | artifact_to_json(p, pOut); |
| 3098 | manifest_destroy(p); |
| 3099 | } |
| 3100 | return rid; |
| 3101 | } |
| 3102 | |
| 3103 | /* |
| 3104 | ** COMMAND: test-artifact-to-json |
| 3105 | ** |
| 3106 | ** Usage: %fossil test-artifact-to-json ?-pretty? symbolic-name [...names] |
| 3107 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -2931,11 +2931,14 @@ | |
| 2931 | } |
| 2932 | return NULL; |
| 2933 | } |
| 2934 | |
| 2935 | /* |
| 2936 | ** Creates a JSON representation of p, appending it to b. |
| 2937 | ** |
| 2938 | ** b is not cleared before rendering, so the caller needs to do that |
| 2939 | ** if it's important for their use case. |
| 2940 | ** |
| 2941 | ** Pedantic note: this routine traverses p->aFile directly, rather than |
| 2942 | ** using manifest_file_next(), so that delta manifests are rendered as-is |
| 2943 | ** instead of having their derived files. If that policy is ever changed, |
| 2944 | ** p will need to be non-const. |
| @@ -2991,10 +2994,13 @@ | |
| 2994 | KVP_STR(1, uuid, pF->zUuid); |
| 2995 | KVP_STR(1, perm, pF->zPerm); |
| 2996 | KVP_STR(1, oldName, pF->zPrior); |
| 2997 | blob_append_char(b, '}'); |
| 2998 | } |
| 2999 | /* Special case: model checkins with no F-card as having an empty |
| 3000 | ** array, rather than no F-cards, to hypothetically simplify |
| 3001 | ** handling in JSON queries. */ |
| 3002 | blob_append_char(b, ']'); |
| 3003 | } |
| 3004 | CARD_STR2(G, p->zThreadRoot); |
| 3005 | CARD_STR2(H, p->zThreadTitle); |
| 3006 | CARD_STR2(I, p->zInReplyTo); |
| @@ -3020,17 +3026,22 @@ | |
| 3026 | blob_appendf(b, "%!j", p->azCChild[i]); |
| 3027 | } |
| 3028 | blob_append_char(b, ']'); |
| 3029 | } |
| 3030 | CARD_STR2(N, p->zMimetype); |
| 3031 | ISA( CFTYPE_MANIFEST ){ |
| 3032 | CARD_LETTER(P); |
| 3033 | blob_append_char(b, '['); |
| 3034 | if( p->nParent ){ |
| 3035 | for( i = 0; i < p->nParent; ++i ){ |
| 3036 | if( i>0 ) blob_append_char(b, ','); |
| 3037 | blob_appendf(b, "%!j", p->azParent[i]); |
| 3038 | } |
| 3039 | } |
| 3040 | /* Special case: model checkins with no P-card as having |
| 3041 | ** an empty array, rather than no P-card, to hypothetically |
| 3042 | ** simplify handling in JSON queries. */ |
| 3043 | blob_append_char(b, ']'); |
| 3044 | } |
| 3045 | if( p->nCherrypick ){ |
| 3046 | CARD_LETTER(Q); |
| 3047 | blob_append_char(b, '['); |
| @@ -3075,10 +3086,26 @@ | |
| 3086 | #undef CARD_STR2 |
| 3087 | #undef ISA |
| 3088 | #undef KVP_STR |
| 3089 | #undef STR_OR_NULL |
| 3090 | } |
| 3091 | |
| 3092 | /* |
| 3093 | ** Convenience wrapper around artifact_to_json() which expects rid to |
| 3094 | ** be the blob.rid of any artifact type. If it can load a Manifest |
| 3095 | ** with that rid, it returns rid, else it returns 0. |
| 3096 | */ |
| 3097 | int artifact_to_json_by_rid(int rid, Blob *pOut){ |
| 3098 | Manifest * const p = manifest_get(rid, CFTYPE_ANY, 0); |
| 3099 | if( p ){ |
| 3100 | artifact_to_json(p, pOut); |
| 3101 | manifest_destroy(p); |
| 3102 | }else{ |
| 3103 | rid = 0; |
| 3104 | } |
| 3105 | return rid; |
| 3106 | } |
| 3107 | |
| 3108 | /* |
| 3109 | ** Convenience wrapper around artifact_to_json() which accepts any |
| 3110 | ** artifact name which is legal for symbolic_name_to_rid(). On success |
| 3111 | ** it returns the rid of the artifact. Returns 0 if no such artifact |
| @@ -3086,21 +3113,63 @@ | |
| 3113 | ** |
| 3114 | ** pOut is not cleared before rendering, so the caller needs to do |
| 3115 | ** that if it's important for their use case. |
| 3116 | */ |
| 3117 | int artifact_to_json_by_name(const char *zName, Blob *pOut){ |
| 3118 | const int rid = symbolic_name_to_rid(zName, 0); |
| 3119 | return rid>0 |
| 3120 | ? artifact_to_json_by_rid(rid, pOut) |
| 3121 | : rid; |
| 3122 | } |
| 3123 | |
| 3124 | /* |
| 3125 | ** SQLite UDF for artifact_to_json(). Its single argument should be |
| 3126 | ** either an INTEGER (blob.rid value) or a TEXT symbolic artifact |
| 3127 | ** name, as per symbolic_name_to_rid(). If an artifact is found then |
| 3128 | ** the result of the UDF is that JSON as a string, else it evaluates |
| 3129 | ** to NULL. |
| 3130 | */ |
| 3131 | void artifact_to_json_sql_func( |
| 3132 | sqlite3_context *context, |
| 3133 | int argc, |
| 3134 | sqlite3_value **argv |
| 3135 | ){ |
| 3136 | int rid = 0; |
| 3137 | Blob b = empty_blob; |
| 3138 | |
| 3139 | if(1 != argc){ |
| 3140 | goto error_usage; |
| 3141 | } |
| 3142 | switch( sqlite3_value_type(argv[0]) ){ |
| 3143 | case SQLITE_INTEGER: |
| 3144 | rid = artifact_to_json_by_rid(sqlite3_value_int(argv[0]), &b); |
| 3145 | break; |
| 3146 | case SQLITE_TEXT:{ |
| 3147 | const char * z = (const char *)sqlite3_value_text(argv[0]); |
| 3148 | if( z ){ |
| 3149 | rid = artifact_to_json_by_name(z, &b); |
| 3150 | } |
| 3151 | break; |
| 3152 | } |
| 3153 | default: |
| 3154 | goto error_usage; |
| 3155 | } |
| 3156 | if( rid>0 ){ |
| 3157 | sqlite3_result_text(context, blob_str(&b), blob_size(&b), |
| 3158 | SQLITE_TRANSIENT); |
| 3159 | blob_reset(&b); |
| 3160 | }else{ |
| 3161 | /* We should arguably error out if rid<0 (ambiguous name) */ |
| 3162 | sqlite3_result_null(context); |
| 3163 | } |
| 3164 | return; |
| 3165 | error_usage: |
| 3166 | sqlite3_result_error(context, "Expecting one argument: blob.rid or " |
| 3167 | "artifact symbolic name", -1); |
| 3168 | } |
| 3169 | |
| 3170 | |
| 3171 | |
| 3172 | /* |
| 3173 | ** COMMAND: test-artifact-to-json |
| 3174 | ** |
| 3175 | ** Usage: %fossil test-artifact-to-json ?-pretty? symbolic-name [...names] |
| 3176 |